1. 程式人生 > >從事件來看委托

從事件來看委托

ray var 實體 blog this ati [] list 發布

事件是基於委托,為委托提供了一種發布/訂閱機制,在dotNet到處都能看到事件,一個簡單的例子就是在windows應用程序中,Button類提供了Click事件,這類事件就是委托,觸發Click事件時調用的處理程序方法需要定義,其參數也是由委托類型定義的,事件模型可以用下圖簡要說明。

技術分享

在這個模型中,事件的響應者通過訂閱關系直接關聯在事件擁有者的事件上,我們把這種事件模型或者CLR事件模型。因為CLR事件本質上是一個委托實例,我們暫且模仿CLR屬性的說法,把CLR事件定義為一個委托類型實例的包裝器。

下面一個示例,事件用於連接CarDealer類和Consumer類,CarDealer類提供了一個新車到達時的觸發事件,Consumer類訂閱該事件,以獲得新車到達的通知。從CarDealer類開始,它基於事件提供一個訂閱,CarDealer類用event關鍵字定義了類型為EventHandler<CarInfoEventArgs>的NewCarInfo事件,在NewCar()中,通過調用RaiseNewCarInfo方法觸發NewCarInfo事件,這個方法的實現檢查委托是否為空,如果不為空,就引發事件。

 public class CarInfoEventArgs : EventArgs
    {
        public string Car { get; private set; }
        public CarInfoEventArgs(string car)
        {
            this.Car = car;
        }
    }

    public class CarDealer
    {
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
        
public void NewCar(string car) { Console.WriteLine("CarDealer,new car {0}", car); RaiseNewCarInfo(car); } protected virtual void RaiseNewCarInfo(string car) { NewCarInfo?.Invoke(this, new CarInfoEventArgs(car)); } }

Consumer類用作事件偵聽器,這個類訂閱了CarDealer類的事件,並定義了NewCarIsHere方法,該方法滿足EeventHandler<CarInfoEventArgs>委托的要求,其參數類型是object和CarInfoEventArgs.

 public class Consumer
    {
        private string name;
        public Consumer(string name)
        {
            this.name = name;
        }
        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
            Console.WriteLine($"{name}:car {e.Car} is new");
        }
    }

需要連接事件發布程序和訂閱器,為此使用CarDealer類的NewCarInfo事件,通過“+=”創建一個訂閱,然後通過“-=”取消訂閱

 var dealer = new CarDealer();
            var myCar = new Consumer("MyCar");
            dealer.NewCarInfo += myCar.NewCarIsHere;
            dealer.NewCar("OneCar");
            dealer.NewCarInfo -= myCar.NewCarIsHere;
            dealer.NewCar("OtherCar");

通過事件,直接連接到發布程序和偵聽器,但垃圾回收器有個問題,如果偵聽器不在直接引用,發布程序就仍有一個引用,垃圾回收器不能清空偵聽器占用的內存,因為發布程序仍有一個引用,會針對偵聽器觸發事件,這種強連接的模式可以通過弱引用事件模式來解決,即使用WeakEventManager作為發布程序和偵聽器之間的中介。

更改Consumer的代碼實現IWeakEventListener接口

public class Consumer : IWeakEventListener
    {
        private string name;

        public Consumer(string name)
        {
            this.name = name;
        }

        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
            Console.WriteLine("{0}: car {1} is new", name, e.Car);
        }

        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            NewCarIsHere(sender, e as CarInfoEventArgs);
            return true;
        }
    }

更改訂閱事件的代碼

 var dealer = new CarDealer();
            var myCar = new Consumer("MyCar");
            WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", myCar.NewCarIsHere);

            //dealer.NewCarInfo += myCar.NewCarIsHere;
            dealer.NewCar("OneCar");
            WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", myCar.NewCarIsHere);
            //dealer.NewCarInfo -= myCar.NewCarIsHere;
            dealer.NewCar("OtherCar");

WPF使用弱事件模式和事件管理器,在dotNet中委托是類型安全的類,它定義了返回類型和類型參數的類型,委托不僅半酣方法的引用,也可以包含對多個方法引用,lambda表達式與委托直接相關,當參數是委托類型時,就可以直接使用lambda表達式實現委托引用的方法,除了為每個參數和返回類型定義一個新委托之外,還可以使用Action<T>和Func<T>委托,泛型Action<T>委托表示引用一個void返回類型的方法,Func<T>允許調用帶返回類型的方法,下面用委托實現經典的冒泡排序,定義一個實體類,在類中定義一個返回bool類型的靜態方法,定義一個實現排序方法的BubbleSorter類

Employee[] employees =
            {
            new Employee("Bugs Bunny", 20000),
            new Employee("Elmer Fudd", 10000),
            new Employee("Daffy Duck", 25000),
            new Employee("Wile Coyote", 1000000.38m),
            new Employee("Foghorn Leghorn", 23000),
            new Employee("RoadRunner", 50000)
            };
            BubbleSorter.Sort(employees, Employee.CompareSalary);
            foreach (var employee in employees)
            {
                Console.WriteLine(employee);
            }

 public class BubbleSorter
    {
        static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
        {
            bool swapped = true;
            do
            {
                swapped = false;
                for (int i = 0; i < sortArray.Count - 1; i++)
                {
                    if (comparison(sortArray[i + 1], sortArray[i]))
                    {
                        T temp = sortArray[i];
                        sortArray[i] = sortArray[i + 1];
                        sortArray[i + 1] = temp;
                        swapped = true;
                    }
                }
            } while (swapped);
        }
    }
    public class Employee
    {
        public Employee(string name, decimal salary)
        {
            this.Name = name;
            this.Salary = salary;
        }

        public string Name { get; private set; }
        public decimal Salary { get; private set; }

        public override string ToString()
        {
            return string.Format("{0}, {1:C}", Name, Salary);
        }

        public static bool CompareSalary(Employee e1, Employee e2)
        {
            return e1.Salary < e2.Salary;
        }
    }

實際上,定義一個委托是指定義一個新類,委托實現為派生自基類的System.MulticastDelegate的類,System.MulticastDelegate又派生自基類System.Delegate,C#編譯器會識別這個類,並使用其委托語法。

從事件來看委托