委托、事件
官方文档:https://docs.microsoft.com/zh-cn/dotnet/standard/events/
事件
class Program {
public static void Main(string[] args) { Counter c = new Counter(new Random().Next(10)); c.ThresholdReached += c_ThresholdReached; Console.WriteLine("press 'a' key to increase total"); while (Console.ReadKey(true).KeyChar == 'a') { Console.WriteLine("adding one"); c.Add(1); } Console.WriteLine("ok"); Console.ReadKey(); } static void c_ThresholdReached(object sender, EventArgs e) { Console.WriteLine("The threshold was reached."); } } class Counter { private int threshold; private int total; public event EventHandler ThresholdReached; public Counter(int passedThreshold) { threshold = passedThreshold; } public void Add(int x) { total += x; if (total >= threshold) { OnThresholdReached(EventArgs.Empty); } } protected virtual void OnThresholdReached(EventArgs e) { EventHandler handler = ThresholdReached; if (handler != null) { handler(this, e); } } }
class Program { public static void Main(string[] args) { Counter c = new Counter(new Random().Next(10)); c.ThresholdReached += c_ThresholdReached; Console.WriteLine("press 'a' key to increase total"); while (Console.ReadKey(true).KeyChar == 'a') { Console.WriteLine("adding one"); c.Add(1); } Console.WriteLine("ok"); Console.ReadKey(); } static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e) { Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached); } } class Counter { private int threshold; private int total; public event EventHandler<ThresholdReachedEventArgs> ThresholdReached; public Counter(int passedThreshold) { threshold = passedThreshold; Console.WriteLine("threshold:"+threshold); } public void Add(int x) { total += x; if (total >= threshold) { ThresholdReachedEventArgs args = new ThresholdReachedEventArgs(); args.Threshold = threshold; args.TimeReached = DateTime.Now; OnThresholdReached(args); } } protected virtual void OnThresholdReached(ThresholdReachedEventArgs e) { var handler = ThresholdReached; if (handler != null) { handler(this, e); } } } public class ThresholdReachedEventArgs : EventArgs { public int Threshold { get; set; } public DateTime TimeReached { get; set; } }
事件在.net中是基于委托模型的。委托模型遵循观察者模式以允许订阅者注册和从供应商那里接收信息。一个事件发送者在事件发生的时候会推送一个通知,一个事件接收者接收通知并且做出响应。
一个事件是一个对象发出的一个消息用来标记一个行为的发生。这个行为可以由用户交互来引起,比如一个按钮的点击,或者可以由其它程序逻辑引起,像属性值的改变。引发事件的对象称为 event sender.
event sender 不知道它将调起哪个对象或者哪个方法。event 是一个典型的event sender 成员;例如,点击事件是button类的成员, PropertyChanged 事件是实现 INotifyPropertyChanged 接口的类的成员。
要定义一个事件,在C#事件类的签名中你需使用 event 关键字,并指定事件的委托类型。
通常,为引发一个事件,你需要将方法标记为 protected 和 virtual。将这个方法以 On+XXX(时间名) 的格式命名,如 OnDataReceived。 这个方法应该有一个参数来指定一个事件的数据对象,这个对象是 EventArgs 类型或者其派生类型。
你提供这个方法来让派生类去重写方法以调用事件。一个派生类应该总是调用基类的 On
EventName 方法来确保注册委托接收事件。
下面的例子展示了如何定义一个名为 ThresholdReached 的事件。事件关联 EventHandler 委托并且 在OnThresholdReached 中被调起。
class Counter { public event EventHandler ThresholdReached; protected virtual void OnThresholdReached(EventArgs e) { EventHandler handler = ThresholdReached; handler?.Invoke(this, e); } // provide remaining implementation for the class }
委托
一个委托是一个引用一个方法的类型。委托定义一个签名来显示它所引用方法的返回类型和参数,它只能引用和它签名匹配的方法。委托就相当于一个指向方法的类型安全的指针。一个委托声名足以定义一个委托类。
委托在.NET中被广泛使用。在事件的背景下,委托是事件源和操作事件代码之间的媒介(类似指针机制)。你通过在事件声名里包括委托类型来将委托和事件联系起来,正如上面代码所示。要想了解更多关于委托的信息,查看 Delegate class.
.net 提供 EventHandler 和 EventHandler<TEventArgs> 委托以支持更多的时间场景。当事件不需要事件数据时使用EventHandler 委托。当事件需要事件数据时使用 EventHandler<TEventArgs> delegate 。这些委托没有返回类型并且有两个参数(一个是事件源对象,一个是数据源对象)。
委托是多播( multicast)的,意味着可以被不止一个事件方法所使用。委托提供灵活且细腻的控制事件的方法。委托在类里面作为事件的调度员通过维护已经注册的事件来调起事件。
对于 EventHandler and EventHandler<TEventArgs> 委托不工作的场景,你可以定义一个委托。需要你去定义委托的情况是很少的,比如当你必须在不识别泛型下的代码里工作时。你声明一个委托关键字。下面这段代码向你展示了一个名为ThresholdReachedEventHandler 的委托定义。
public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Event data
关联事件的数据可以通过时间数据类的事件提供。.net 提供了许多时间数据类供你在应用中使用。例如, SerialDataReceivedEventArgs 是一个可用于SerialPort.DataReceived事件的事件数据。.net遵循所有事件数据类以EventArgs结尾命名这个命名规则。
你可以通过查看事件的委托来决定哪个事件数据和事件进行关联。例如,SerialDataReceivedEventHandler委托包含 SerialDataReceivedEventArgs 类,作为它的一个参数。
EventArgs 类是所有事件数据类的基类。 EventArgs 也是一个当你使用的那个事件没有任何关联事件数据类时的类。当你创建一个只需要在其它类发生一些事之后通知其但不需要传递数据的事件时,在委托的第二个参数中传递 EventArgs这个类。当没有提供数据时你可以传递 EventArgs.Empty 。 EventHandler 委托 中有一个参数是 EventArgs类。
当你想创建一个定制化的事件数据类时,需要创建一个类继承自 EventArgs, 然后创建任意需要数量的数据关联事件。通常,你应该遵循.net的命名规范,并且将事件数据以 EventArgs 命名结尾。
下面这个例子展示了 名为 ThresholdReachedEventArgs 的事件数据。它包含特定的属性去调起事件。
public class ThresholdReachedEventArgs : EventArgs { public int Threshold { get; set; } public DateTime TimeReached { get; set; } }
Event handlers 事件句柄
为了响应事件,你在事件接收器那里定义了一个事件句柄方法。这个方法必须和你在事件里定义的委托方法签名一致。在事件句柄中,你引发一个事件时需要执行一个动作,例如在用户点击一个按钮之后收集用户的输入。为了 在事件发生时接收通知,
你的事件句柄方法必须在事件中订阅。
下面这个例子展示了一个匹配 EventHandler 委托签名的 名为c_ThresholdReached
的事件句柄方法。这个方法订阅在ThresholdReached
事件中。
class Counter { public event EventHandler ThresholdReached; protected virtual void OnThresholdReached(EventArgs e) { EventHandler handler = ThresholdReached; handler?.Invoke(this, e); } // provide remaining implementation for the class } class Program { static void Main() { var c = new Counter(); c.ThresholdReached += c_ThresholdReached; // provide remaining implementation for the class } static void c_ThresholdReached(object sender, EventArgs e) { Console.WriteLine("The threshold was reached."); } }
Static and dynamic event handlers 静态和动态事件句柄
.net 允许订阅者注册静态或者动态事件通知。静态事件句柄影响它操作的事件所在类的整个生命周期。动态事件句柄在程序运行期间有明确的有效和无效时间,通常用来响应一些带条件的逻辑程序。例如,它们可以被用在事件通知条件确定的情况下
或者一个应用提供多个事件句柄并且运行时条件定义合适的一个去使用。
Raising multiple events 调起多个事件
如果你的类调起多个事件,编译器将会为每个事件委托实例产生一个字段。如果事件的数量很多,储存每个委托生成的字段的开销可能是不被接受的。在此种情况下,.net 提供了事件属性,你可以使用其它数据结构来储存事件委托。
事件属性由伴随着事件访问器的事件声名组成。可以通过定义事件访问器从储存数据结构里添加或者移除事件委托实例。注意事件属性要比事件字段速度慢,因为每个事件委托在它被调用之前必须重新取回。在内存和速度之间的权衡。
如果你的类定义了很多不经常被调起的事件,你最好使用时间属性。想要了解更多信息,查看 How to: Handle Multiple Events Using Event Properties.。