Programming .NET Components 2nd 学习笔记(八)
2012-07-16 07:57
232 查看
6.1.Delegate-Based Events
在描述.NET事件支持前,这里有一些术语。发布事件的对象称作源(source)或发布服务器(publisher),任何对该事件有兴趣的部分称作事件接收器(sink)或订阅服务器(subscribers)。事件通知的形式是发布者调用订阅者服务器的方法。发布事件也称作触发事件。.NET通过提供特殊的CLR类型和基类实现来提供对事件的本地支持。.NET为源与事件接收器定义了连接设置和断开的标准机制,一个标准、简明的方式触发事件,和一个已经实现的事件接收器列表。
.NET事件支持依赖于委托。概念上讲,委托仅仅是一个类型安全的方法引用,你可以把它想成一个类型安全的C函数指针或C++函数对象。正如名字所暗示,一个委托允许你将调用一个方法的动作委托给其他人。委托可以调用静态或实例方法。例如:这里有个叫做NumberChangedEventHandler委托,定义如下:
这个委托可以用来调用任何匹配签名的方法(一个void返回类型和一个int参数)。委托的名字,目标方法的名字,还有方法参数的名字都不重要。方法被调用的唯一要求是拥有委托期望的精确签名(相同的类型)。在定义委托时给它一个有意义的名字,向阅读你的代码的读者传递它的目的。例如:NumberChangedEventHandler这个名字表示这个委托是用来发布一个事件去通知订阅服务器他们监视的数发生了改变。
在事件仅仅被订阅的情况下,事件发布服务器有一个委托类型的公有成员变量:
事件订阅服务器需要实现一个符合签名的方法:
编译器用一个提供了事件接收器列表实现的复杂类替代委托类型定义。生成的委托类继承于抽象类Delegate。
你可以使用=、+=、-=操作符管理目标方法列表。这个列表实际上是委托对象的列表,每一个元素引用一个单独目标方法。+=操作符添加一个新订阅服务器(事实上,仅仅是一个目标方法被封装到委托中)到列表的末端。为了添加一个新的目标,你需要创建一个封装了目标方法的新委托对象。-=操作符将一个目标方法从列表中移除(不管是新创建的封装了目标方法的委托对象还是已存在的委托对象)。=操作符用一个指向一个目标的委托来初始化列表。编译器将操作符的使用转换为Delegate类中相应的静态方法调用,例如Combine()或Remove()。当你要触发事件时,只需传递参数并调用委托。这会使委托遍历内部的目标列表,使用相应参数调用每个目标方法。
下面展示了怎么添加向事件接收器列表添加两个订阅服务器,怎样触发事件,怎样从列表中移除一个订阅服务器:
例子6-2.使用委托管理事件订阅和发布
上面展示的代码将调用多个订阅服务器的方法的动作委托给变量NumberChanged。注意,你可以多次添加同一个订阅服务器的目标方法:
或者:
该委托会调用订阅服务器相应的次数。当你从列表中移除一个目标方法时,如果在列表中存在多个该方法,则找到的第一个方法(即最靠近列表头结点的那个)被移除。
警告:委托广泛应用在.NET中,并不只用作管理事件,它还处理其他任务,例如异步方法调用和创建新线程。
6.1.1. Delegate Inference
在C#2.0中,当添加或移除目标方法时,编译器可以从实例推断出委托类型。替代直接实例化一个新的委托对象,可以直接将方法名赋值给委托变量,而不用先封装到一个委托变量里。我将这个特性称作委托推断。例子6-3展示的代码和例子6-2相似,区别在于使用了委托推断。
例子6-3.使用委托推断
使用委托推断,你可以只将方法名传给任何期望委托参数的方法。例如:
当你将一个方法名赋值给一个委托时,编译器首先会推断该委托的类型,然后编译器会验证此处有该名字的方法且方法的签名匹配推断出的委托类型。最后,编译器会创建一个推断出的委托类型的对象来封装该方法,并将该对象赋值给该委托。编译器只能推断除了抽象类型Delegate之外的委托类型。委托推断能生成可读、简洁的代码,本书的代码将沿用此编程风格。
6.1.2. Generic Delegates
泛型的进入为事件的定义和管理开启了一系列新的可能性。你将在本章的后面部分看到,定义泛型委托意味着你将很少为了处理事件去定义新的委托类型。定义在泛型类中的委托能从类型参数中获益。例如:
当你为该类制定类型参数时,同样会影响该委托:
注意,编译器有能力推断出要实例化的正确的委托类型和泛型类型参数。因此,这样赋值:
会在编译时转换为:
和类一样,接口、结构体、方法、委托也能定义泛型类型参数:
在类范围外定义的委托也能使用泛型类型参数。这种情况下,你可以在声明和实例化该委托时提供特殊的类型:
同样的,委托可以给泛型类型参数定义约束:
委托级的约束仅在使用方面强制,当声明委托变量和实例化委托对象时使用该约束,这和其他级别的约束相似。
6.1.3. The event Keyword
用未加工的委托管理事件足够简单,但它有一个缺陷:作为发布服务器的类需要将委托成员设置为公有成员变量,因此任何部分都能将订阅服务器添加进委托列表。将委托暴露为公有成员允许任何人获取它并发布事件甚至是在对象内部并未发生该事件时。为了处理该缺陷,C#改善了用于事件订阅和通知的委托类型,使用关键字event。当你将委托成员变量定义为事件时,即使该成员是公有的,也只有发布服务器类(该类的子类也不能)才能触发此事件(虽然任何人能添加目标方法到该委托列表中)。接下来由发布服务器类的开发者决定是否提供一个共有的方法去触发该事件:
将订阅服务器与发布服务器连接的代码和代码6-2或6-3相同(使用+=和-=操作符)。使用事件替代未加工的委托也能降低发布服务器与订阅服务器之间的耦合度,因为发布服务器方面触发事件的业务逻辑对于订阅服务器隐藏。
.NET Loosely Coupled Events
.NET事件支持减轻了管理事件的工作,并使你从提供管理订阅服务器列表代码的负担中解脱出来。.NET基于委托的事件有几个缺点:
· 订阅服务器(或者客户端添加订阅)必须重复向想要接收事件的发布服务器对象添加订阅的代码。没有能订阅一种类型并将该事件传递给订阅服务器且不管发布服务器是谁的方法。
· 订阅服务器没有过滤已触发事件的方法(举个例子,说“当某个条件发生时才通知我该事件发生”)。
· 订阅服务器为了订阅事件必须能接触到发布服务器对象,这样会使客户端与对象及不同客户端之间产生耦合。
· 发布服务器和订阅服务器在生命周期上会产生耦合,它们必须在相同时间一起运行。订阅服务器无法向.NET说,“如果某个对象触发了这个事件,请创建一个我的实例,并让我处理它。”
· 没有一种简单的方法处理非连接工作,即发布服务器对象在一个离线的机器上触发事件,随后当该机器连线时向订阅服务器客户端传递事件。反过来也是不可能的。
· 必须用编程方法来设置连接,而不能用管理方式去设置发布服务器与订阅服务器的连接。
为了弥补这些缺点,.NET支持一种特殊的事件,叫做低耦合事件(LCE)。LCE在System.EnterpriseServices命名空间。虽然LCE并不基于委托,但它们的使用简单、直接且提供额外的好处,例如将事件与事务、安全相结合。低耦合事件将会在9、10章讨论。
在描述.NET事件支持前,这里有一些术语。发布事件的对象称作源(source)或发布服务器(publisher),任何对该事件有兴趣的部分称作事件接收器(sink)或订阅服务器(subscribers)。事件通知的形式是发布者调用订阅者服务器的方法。发布事件也称作触发事件。.NET通过提供特殊的CLR类型和基类实现来提供对事件的本地支持。.NET为源与事件接收器定义了连接设置和断开的标准机制,一个标准、简明的方式触发事件,和一个已经实现的事件接收器列表。
.NET事件支持依赖于委托。概念上讲,委托仅仅是一个类型安全的方法引用,你可以把它想成一个类型安全的C函数指针或C++函数对象。正如名字所暗示,一个委托允许你将调用一个方法的动作委托给其他人。委托可以调用静态或实例方法。例如:这里有个叫做NumberChangedEventHandler委托,定义如下:
public delegate void NumberChangedEventHandler(int number);
这个委托可以用来调用任何匹配签名的方法(一个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; MessageBox.Show(message,"MySubscriber"); } }
编译器用一个提供了事件接收器列表实现的复杂类替代委托类型定义。生成的委托类继承于抽象类Delegate。
public abstract class Delegate : //Interface list { 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 }
你可以使用=、+=、-=操作符管理目标方法列表。这个列表实际上是委托对象的列表,每一个元素引用一个单独目标方法。+=操作符添加一个新订阅服务器(事实上,仅仅是一个目标方法被封装到委托中)到列表的末端。为了添加一个新的目标,你需要创建一个封装了目标方法的新委托对象。-=操作符将一个目标方法从列表中移除(不管是新创建的封装了目标方法的委托对象还是已存在的委托对象)。=操作符用一个指向一个目标的委托来初始化列表。编译器将操作符的使用转换为Delegate类中相应的静态方法调用,例如Combine()或Remove()。当你要触发事件时,只需传递参数并调用委托。这会使委托遍历内部的目标列表,使用相应参数调用每个目标方法。
下面展示了怎么添加向事件接收器列表添加两个订阅服务器,怎样触发事件,怎样从列表中移除一个订阅服务器:
例子6-2.使用委托管理事件订阅和发布
MyPublisher publisher = new MyPublisher( ); MySubscriber subscriber1 = new MySubscriber( ); MySubscriber subscriber2 = new MySubscriber( ); //Adding subscriptions: publisher.NumberChanged += new NumberChangedEventHandler (subscriber1.OnNumberChanged); publisher.NumberChanged += new NumberChangedEventHandler (subscriber2.OnNumberChanged); //Firing the event: publisher.NumberChanged(3); //Removing a subscription: publisher.NumberChanged -= new NumberChangedEventHandler (subscriber2.OnNumberChanged);
上面展示的代码将调用多个订阅服务器的方法的动作委托给变量NumberChanged。注意,你可以多次添加同一个订阅服务器的目标方法:
publisher.NumberChanged += new NumberChangedEventHandler (subscriber1.OnNumberChanged); publisher.NumberChanged += new NumberChangedEventHandler (subscriber1.OnNumberChanged);
或者:
NumberChangedEventHandler del; del = new NumberChangedEventHandler(subscriber1.OnNumberChanged); publisher.NumberChanged += del; publisher.NumberChanged += del;
该委托会调用订阅服务器相应的次数。当你从列表中移除一个目标方法时,如果在列表中存在多个该方法,则找到的第一个方法(即最靠近列表头结点的那个)被移除。
警告:委托广泛应用在.NET中,并不只用作管理事件,它还处理其他任务,例如异步方法调用和创建新线程。
6.1.1. Delegate Inference
在C#2.0中,当添加或移除目标方法时,编译器可以从实例推断出委托类型。替代直接实例化一个新的委托对象,可以直接将方法名赋值给委托变量,而不用先封装到一个委托变量里。我将这个特性称作委托推断。例子6-3展示的代码和例子6-2相似,区别在于使用了委托推断。
例子6-3.使用委托推断
MyPublisher publisher = new MyPublisher( ); MySubscriber subscriber1 = new MySubscriber( ); MySubscriber subscriber2 = new MySubscriber( ); //Adding subscriptions: publisher.NumberChanged += subscriber1.OnNumberChanged; publisher.NumberChanged += subscriber2.OnNumberChanged; //Firing the event: publisher.NumberChanged(3); //Removing a subscription: publisher.NumberChanged -= subscriber2.OnNumberChanged;
使用委托推断,你可以只将方法名传给任何期望委托参数的方法。例如:
delegate void SomeDelegate( ); class SomeClass { public void SomeMethod( ) {...} public void InvokeDelegate(SomeDelegate del) { del( ); } } SomeClass obj = new SomeClass( ); obj.InvokeDelegate(obj.SomeMethod);
当你将一个方法名赋值给一个委托时,编译器首先会推断该委托的类型,然后编译器会验证此处有该名字的方法且方法的签名匹配推断出的委托类型。最后,编译器会创建一个推断出的委托类型的对象来封装该方法,并将该对象赋值给该委托。编译器只能推断除了抽象类型Delegate之外的委托类型。委托推断能生成可读、简洁的代码,本书的代码将沿用此编程风格。
6.1.2. Generic Delegates
泛型的进入为事件的定义和管理开启了一系列新的可能性。你将在本章的后面部分看到,定义泛型委托意味着你将很少为了处理事件去定义新的委托类型。定义在泛型类中的委托能从类型参数中获益。例如:
public class MyClass<T> { public delegate void GenericEventHandler(T t); public void SomeMethod(T t) {...} }
当你为该类制定类型参数时,同样会影响该委托:
MyClass<int>.GenericEventHandler del; MyClass<int> obj = new MyClass<int>( ); del = obj.SomeMethod; del(3);
注意,编译器有能力推断出要实例化的正确的委托类型和泛型类型参数。因此,这样赋值:
del = obj.SomeMethod;
会在编译时转换为:
del = new MyClass<int>.GenericEventHandler(obj.SomeMethod);
和类一样,接口、结构体、方法、委托也能定义泛型类型参数:
public class MyClass<T> { public delegate void GenericEventHandler<X>(T t,X x); }
在类范围外定义的委托也能使用泛型类型参数。这种情况下,你可以在声明和实例化该委托时提供特殊的类型:
public delegate void GenericEventHandler<T>(T t); public class MyClass { public void SomeMethod(int number) {...} } GenericEventHandler<int> del; MyClass obj = new MyClass( ); del = obj.SomeMethod; del(3);
同样的,委托可以给泛型类型参数定义约束:
public delegate void GenericEventHandler<T>(T t) where T : IComparable<T>;
委托级的约束仅在使用方面强制,当声明委托变量和实例化委托对象时使用该约束,这和其他级别的约束相似。
6.1.3. The event Keyword
用未加工的委托管理事件足够简单,但它有一个缺陷:作为发布服务器的类需要将委托成员设置为公有成员变量,因此任何部分都能将订阅服务器添加进委托列表。将委托暴露为公有成员允许任何人获取它并发布事件甚至是在对象内部并未发生该事件时。为了处理该缺陷,C#改善了用于事件订阅和通知的委托类型,使用关键字event。当你将委托成员变量定义为事件时,即使该成员是公有的,也只有发布服务器类(该类的子类也不能)才能触发此事件(虽然任何人能添加目标方法到该委托列表中)。接下来由发布服务器类的开发者决定是否提供一个共有的方法去触发该事件:
public delegate void NumberChangedEventHandler(int number); public class MyPublisher { public event NumberChangedEventHandler NumberChanged; public void FireEvent(int number) { NumberChanged(number); } /* Other methods and members */ }
将订阅服务器与发布服务器连接的代码和代码6-2或6-3相同(使用+=和-=操作符)。使用事件替代未加工的委托也能降低发布服务器与订阅服务器之间的耦合度,因为发布服务器方面触发事件的业务逻辑对于订阅服务器隐藏。
.NET Loosely Coupled Events
.NET事件支持减轻了管理事件的工作,并使你从提供管理订阅服务器列表代码的负担中解脱出来。.NET基于委托的事件有几个缺点:
· 订阅服务器(或者客户端添加订阅)必须重复向想要接收事件的发布服务器对象添加订阅的代码。没有能订阅一种类型并将该事件传递给订阅服务器且不管发布服务器是谁的方法。
· 订阅服务器没有过滤已触发事件的方法(举个例子,说“当某个条件发生时才通知我该事件发生”)。
· 订阅服务器为了订阅事件必须能接触到发布服务器对象,这样会使客户端与对象及不同客户端之间产生耦合。
· 发布服务器和订阅服务器在生命周期上会产生耦合,它们必须在相同时间一起运行。订阅服务器无法向.NET说,“如果某个对象触发了这个事件,请创建一个我的实例,并让我处理它。”
· 没有一种简单的方法处理非连接工作,即发布服务器对象在一个离线的机器上触发事件,随后当该机器连线时向订阅服务器客户端传递事件。反过来也是不可能的。
· 必须用编程方法来设置连接,而不能用管理方式去设置发布服务器与订阅服务器的连接。
为了弥补这些缺点,.NET支持一种特殊的事件,叫做低耦合事件(LCE)。LCE在System.EnterpriseServices命名空间。虽然LCE并不基于委托,但它们的使用简单、直接且提供额外的好处,例如将事件与事务、安全相结合。低耦合事件将会在9、10章讨论。
相关文章推荐
- Programming .NET Components 2nd 学习笔记(二)
- Programming .NET Components 2nd 学习笔记(十一)
- Programming .NET Components 2nd 学习笔记(十二)
- Programming .NET Components 2nd 学习笔记(三)
- Programming .NET Components 2nd 学习笔记(一)
- Programming .NET Components 2nd 学习笔记(九)
- Programming .NET Components, 2nd Edition [ILLUSTRATED]
- Programming .NET Components 2nd 学习笔记(七)
- Programming ASP.NET 学习笔记(要点)第3章 控件:基本概念
- Programming .NET Components 2nd 学习笔记(四)
- Programming .NET Components 2nd 学习笔记(五)
- Programming .NET Components 2nd 学习笔记(六)
- Programming .NET Components 2nd 学习笔记(十)
- 暑假ASP.NET学习笔记——7月20号
- ASP.NET 路由设置 学习笔记
- 黑马程序员之ASP.NET学习笔记:TREEVIEW中动态增加结点
- ASP.NET 3.5核心编程学习笔记(47):ASP.NET的安全性之安全性上下文与信任级别
- [导入].NET深入学习笔记(4):深拷贝与浅拷贝(Deep Copy and Shallow Copy)
- Json.Net学习笔记(十) 保持对象引用
- MXNet动手学深度学习笔记:卷积神经网络实现