首页 > 代码库 > 从事件来看委托
从事件来看委托
事件是基于委托,为委托提供了一种发布/订阅机制,在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#编译器会识别这个类,并使用其委托语法。
从事件来看委托