基于委托的事件
.NET中的事件支持依赖于delegates(委托)。概念上,一个委托只不过是一个类型安全的方法的引用。如名称显示的那样,一个委托允许将调用方法的行为委托给其他方法来执行。委托可以调用静态或实例方法,例如,委托NumberChangedEventHandler定义如下:
这个委托可以被用来调用任何具有匹配签名的方法(void返回类型和一个int参数)。委托的命名,目标方法的命名以及方法中参数的命名都是不必严格匹配的。惟一的要求是被调用的方法必须有委托期望的正确的签名。通常用一个有意义的名称来命名委托,以给代码的阅读者表达它的目的。例如,NumberChangedEventHandler的命名就表明这个委托是当监视的某一数字的值发生改变时,发布事件通知订阅者。
事件发布者有一个委托类型的公共成员变量:
public delegate void NumberChangedEventHandler(int number);
public class MyPublisher
{
public NumberChangedEventHandler NumberChanged;
/* Other methods and members */
}
事件订阅者必须实现拥有匹配签名的方法:
public class MySubscriber
{
public void OnNumberChanged(int number)
{
string message = "New value is " + number;
Message.Show(message, "MySubscriber");
}
}
编译器会以一个提供了接收列表实现的类取代委托类型定义。该生成的委托类继承于抽象类Delegate:
public abstract class Delegate
{
public static Delegate Combine(Delegate a, Delegate b);
public static Delegate Remove(Delegate source, Delegate value);
public object DynamicInvoke(object[] args);
public virtual Delegate[] GetInvocationList();
//Other members
}
可以使用=,+=和-=操作符来管理目标方法列表。该列表实际上是一个委托对象列表,每个对象引用了一个目标方法。+=操作符新增一个订阅者到列表末端。为了新增一个目标方法,需要创建一个委托对象来包装目标方法。-=操作符是从列表中移除一个目标方法。=操作符可以初始化类别,通常是使用一个委托指向单个目标方法。编译器会转换操作符的使用到委托类中相匹配的静态方法调用上,如Combine()和Remove()。当想要激发事件的时候,直接调用委托并传入参数。这就会导致委托迭代其内部目标方法列表,使用参数调用每个目标方法:
MyPublisher publisher = new MyPublisher();
MySubscriber subscriber1 = new MySubscriber();
MySubscriber subscriber2 = new MySubscriber();
//新增订阅者
publisher.NumberChanged += new NumberChangedEventHandler(subscriber1.OnNumberChanged);
publisher.NumberChanged += new NumberChangedEventHandler(subscriber2.OnNumberChanged);
//激发事件
publisher.NumberChanged(3);
//移除订阅者
publisher.NumberChanged -= new NumberChangedEventHandler(subscriber2.OnNumberChanged);
需要注意的是,可以多次新增同一订阅者的目标方法到委托列表。这样委托就可以多次调用该目标方法。当从列表中移除目标方法的时候,如果它多次出现,那么第一个被发现的将会被移除(最靠近列表头的那一个):
publisher.NumberChanged += new NumberChangedEventHandler(subscriber1.OnNumberChanged);
publisher.NumberChanged += new NumberChangedEventHandler(subscriber1.OnNumberChanged);
//或者
NumberChangedEventHandler
publisher.NumberChanged +=
publisher.NumberChanged +=
委托推断
在C# 2.0中,当新增或移除一个目标方法到委托列表的时候,编译器可以推断委托类型到相应的实例。除了显式实例化一个委托对象外,还可以直接声明一个方法命名到委托变量中,而不必先用一个委托对象来包装它。这被称为“delegate inference(委托推断)”:
//新增订阅者
publisher.NumberChanged += subscriber1.OnNumberChanged;
publisher.NumberChanged += subscriber2.OnNumberChanged;
//激发事件
publisher.NumberChanged(3);
//移除订阅者
publisher.NumberChanged -= subscriber2.OnNumberChanged;
当声明一个方法命名到一个委托变量时,编译器首先推断委托的类型,然后编译器会验证是否存在该命名的方法以及是否与委托类型的方法签名相匹配。最后,编译器会创建一个推断的委托类型的实例对象来包装该方法,并声明到委托上。
泛型委托
在类中定义的委托可以利用该类上的泛型类型参数,例如:
public class MyClass<T>
{
public delegate void GenericEventHandler(T t);
public void SomeMethod(T t)
{...}
}
当为该报含类指定一个类型参数时,它同样会作用于委托:
MyClass<int> obj = new MyClass<int>();
需要注意的是,编译器能够推断当前委托类型到当前使用的泛型类型参数的实例对象。因此,这样的声明:
编译时会实际转换成如下声明:
像类,接口,结构和方法一样,委托也可以定义泛型类型参数:
{
public delegate void GenericEventHandler(T t, X x);
}
在类范围外定义的委托也可以使用泛型类型参数。在这种情况下,当声明和初始化时必须指定类型参数:
public delegate void GenericEventHandler<T>(T t); public class MyClass
{
public void SomeMethod(int number);
} GenericEventHandler<int>
MyClass obj = new MyClass();
event关键字
使用原始委托管理事件是十分简单的,但是这样存在一个缺陷:发布者类会把委托成员作为公共成员暴露,这样其他类才可以新增订阅到委托列表。但是,这同样允许任何类访问并激发事件,即使并没有事件在发布者对象端发生。为了弥补这个缺陷,C#使用event关键字净化用于事件订阅和通知的委托类型。当使用event关键字定义一个委托成员变量的时候,即使该成员是公共的,也只能够由发布者类激发事件。这就可以让发布者类的开发者自由决定是否提供激发事件的公共方法:
public class MyPublisher
{
public event NumberChangedEventHandler NumberChanged;
public void FireEvent(int number)
{
NumberChanged(number);
}
/* Other methods and members */
}
现在同样可以使用+=和-=操作符来管理订阅。使用事件取代原始委托还可以降低发布者和订阅者之间的耦合度,因为在发布者端触发事件的业务逻辑对于订阅者来说是不可见的。
根据原版英文翻译,所以不足和错误之处请大家不吝指正,谢谢:)