.Net事件
.NET 事件
事件概述
在發(fā)生其他類或?qū)ο箨P(guān)注的事情時(shí),類或?qū)ο罂赏ㄟ^事件通知它們。發(fā)送(或引發(fā))事件的類稱為“發(fā)行者”,接收(或處理)事件的類稱為“訂戶”。
- 特點(diǎn)
- 發(fā)行者確定何時(shí)引發(fā)事件,訂戶確定執(zhí)行何種操作來(lái)響應(yīng)該事件。
- 一個(gè)事件可以有多個(gè)訂戶。一個(gè)訂戶可處理來(lái)自多個(gè)發(fā)行者的多個(gè)事件。
- 沒有訂戶的事件永遠(yuǎn)不會(huì)被調(diào)用。
- 事件通常用于通知用戶操作
- 如果一個(gè)事件有多個(gè)訂戶,當(dāng)引發(fā)該事件時(shí),會(huì)同步調(diào)用多個(gè)事件處理程序,也可以設(shè)置異步調(diào)用事件。
- 可以利用事件同步線程。
- 事件是基于 EventHandler 委托和 EventArgs 基類的。
事件的訂閱和取消
- 訂閱事件
- VS IDE 訂閱事件
- 如果“屬性”窗口不可見,請(qǐng)?jiān)?ldquo;設(shè)計(jì)”視圖中,右擊要?jiǎng)?chuàng)建事件處理程序的窗體或控件,然后選擇“屬性”。
- 在“屬性”窗口的頂部,單擊“事件”圖標(biāo)。
- 雙擊要?jiǎng)?chuàng)建的事件,Visual C# 會(huì)創(chuàng)建一個(gè)空事件處理程序方法,并將其添加到您的代碼中?;蛘?,您也可以在“代碼”視圖中手動(dòng)添加代碼。
- VS IDE 訂閱事件
- 編程方式訂閱事件
-
定義一個(gè)事件處理程序方法,其簽名與該事件的委托簽名匹配。例如,如果事件基于 EventHandler 委托類型,則下面的代碼表示方法存根
-
- oid HandleCustomEvent(object sender, CustomEventArgs a){ }
-
使用加法賦值運(yùn)算符 (+=) 來(lái)為事件附加事件處理程序。在下面的示例中,假設(shè)名為 publisher 的對(duì)象擁有一個(gè)名為 RaiseCustomEvent 的事件。請(qǐng)注意,訂戶類需要引用發(fā)行者類才能訂閱其事件。
-
- publisher.RaiseCustomEvent += HandleCustomEvent;
- publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);
- 匿名方法訂閱事件
- 使用加法賦值運(yùn)算符 (+=) 來(lái)為事件附加匿名方法。在下面的示例中,假設(shè)名為 publisher 的對(duì)象擁有一個(gè)名為 RaiseCustomEvent 的事件,并且還定義了一個(gè) CustomEventArgs 類以承載某些類型的專用事件信息。請(qǐng)注意,訂戶類需要引用 publisher 才能訂閱其事件。
- publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
- {
- string s = o.ToString() + " " + e.ToString();
- Console.WriteLine(s);
- };
- 取消訂閱
要防止在引發(fā)事件時(shí)調(diào)用事件處理程序,您只需取消訂閱該事件。要防止資源泄露,請(qǐng)?jiān)卺尫庞啈魧?duì)象之前取消訂閱事件,這一點(diǎn)很重要。在取消訂閱事件之前,在發(fā)布對(duì)象中作為該事件的基礎(chǔ)的多路廣播委托會(huì)引用封裝了訂戶的事件處理程序的委托。只要發(fā)布對(duì)象包含該引用,就不會(huì)對(duì)訂戶對(duì)象執(zhí)行垃圾回收。
使用減法賦值運(yùn)算符 (-=) 取消訂閱事件。所有訂戶都取消訂閱某事件后,發(fā)行者類中的事件實(shí)例會(huì)設(shè)置為 null。
- publisher.RaiseCustomEvent -= HandleCustomEvent;
發(fā)布標(biāo)準(zhǔn)事件
下面的過程演示了如何將符合標(biāo)準(zhǔn) .NET Framework 模式的事件添加到您自己的類和結(jié)構(gòu)中。.NET Framework 類庫(kù)中的所有事件均基于 EventHandler 委托,定義如下。
- public delegate void EventHandler(object sender, EventArgs e);
- 采用 EventHandler 模式發(fā)布事件
- (如果不需要發(fā)送含事件的自定義數(shù)據(jù),請(qǐng)?zhí)^此步驟,直接進(jìn)入步驟 3。)在發(fā)行者類和訂戶類均可看見的范圍中聲明類,并添加保留自定義事件數(shù)據(jù)所需的成員。在此示例中,會(huì)返回一個(gè)簡(jiǎn)單字符串。
- public class CustomEventArgs : EventArgs
- {
- public CustomEventArgs(string s)
- {
- msg = s;
- }
- private string msg;
- public string Message
- {
- get { return msg; }
- }
- }
- (如果您使用的是 EventHandler 的泛型版本,請(qǐng)?zhí)^此步驟。)在發(fā)布類中聲明一個(gè)委托。為它指定以 EventHandler 結(jié)尾的名稱。第二個(gè)參數(shù)指定自定義 EventArgs 類型。
- public delegate void CustomEventHandler(object sender, CustomEventArgs a);
- 如果沒有自定義 EventArgs 類,事件類型就是非泛型 EventHandler 委托。它無(wú)需聲明,因?yàn)樗言?C# 項(xiàng)目默認(rèn)包含的 System 命名空間中進(jìn)行了聲明
- public event EventHandler RaiseCustomEvent;
- class Publisher
- {
- public event CustomEventHandler RaiseCustomEvent;
- }
- public event EventHandler<CustomEventArgs> RaiseCustomEvent;
#p#
引發(fā)派生類中的基類事件
以下簡(jiǎn)單示例演示了在基類中聲明可從派生類引發(fā)的事件的標(biāo)準(zhǔn)方法。此模式廣泛應(yīng)用于 .NET Framework 基類庫(kù)中的 Windows 窗體類。
在創(chuàng)建可用作其他類的基類的類時(shí),必須考慮如下事實(shí):事件是特殊類型的委托,只可以從聲明它們的類中調(diào)用。派生類無(wú)法直接調(diào)用基類中聲明的事件。盡管有時(shí)您可能希望某個(gè)事件只能通過基類引發(fā),但在大多數(shù)情形下,您應(yīng)該允許派生類調(diào)用基類事件。為此,您可以在包含該事件的基類中創(chuàng)建一個(gè)受保護(hù)的調(diào)用方法。通過調(diào)用或重寫此調(diào)用方法,派生類便可以間接調(diào)用該事件。
- namespace BaseClassEvents
- {
- using System;
- using System.Collections.Generic;
- public class ShapeEventArgs : EventArgs
- {
- private double newArea;
- public ShapeEventArgs(double d)
- {
- newArea = d;
- }
- public double NewArea
- {
- get { return newArea; }
- }
- }
- public abstract class Shape
- {
- protected double area;
- public double Area
- {
- get { return area; }
- set { area = value; }
- }
- public event EventHandler<ShapeEventArgs> ShapeChanged;
- public abstract void Draw();
- protected virtual void OnShapeChanged(ShapeEventArgs e)
- {
- EventHandler<ShapeEventArgs> handler = ShapeChanged;
- if (handler != null)
- {
- handler(this, e);
- }
- }
- }
- public class Circle : Shape
- {
- private double radius;
- public Circle(double d)
- {
- radius = d;
- area = 3.14 * radius;
- }
- public void Update(double d)
- {
- radius = d;
- area = 3.14 * radius;
- OnShapeChanged(new ShapeEventArgs(area));
- }
- protected override void OnShapeChanged(ShapeEventArgs e)
- {
- base.OnShapeChanged(e);
- }
- public override void Draw()
- {
- Console.WriteLine("Drawing a circle");
- }
- }
- public class Rectangle : Shape
- {
- private double length;
- private double width;
- public Rectangle(double length, double width)
- {
- this.length = length;
- this.width = width;
- area = length * width;
- }
- public void Update(double length, double width)
- {
- this.length = length;
- this.width = width;
- area = length * width;
- OnShapeChanged(new ShapeEventArgs(area));
- }
- protected override void OnShapeChanged(ShapeEventArgs e)
- {
- base.OnShapeChanged(e);
- }
- public override void Draw()
- {
- Console.WriteLine("Drawing a rectangle");
- }
- }
- public class ShapeContainer
- {
- List<Shape> _list;
- public ShapeContainer()
- {
- _list = new List<Shape>();
- }
- public void AddShape(Shape s)
- {
- _list.Add(s);
- s.ShapeChanged += HandleShapeChanged;
- }
- private void HandleShapeChanged(object sender, ShapeEventArgs e)
- {
- Shape s = (Shape)sender;
- Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);
- s.Draw();
- }
- }
- class Test
- {
- static void Main(string[] args)
- {
- Circle c1 = new Circle(54);
- Rectangle r1 = new Rectangle(12, 9);
- ShapeContainer sc = new ShapeContainer();
- sc.AddShape(c1);
- sc.AddShape(r1);
- c1.Update(57);
- r1.Update(7, 7);
- Console.WriteLine();
- Console.WriteLine("Press Enter to exit");
- Console.ReadLine();
- }
- }
- }
#p#
實(shí)現(xiàn)接口事件
接口可聲明事件。下面的示例演示如何在類中實(shí)現(xiàn)接口事件。接口事件的實(shí)現(xiàn)規(guī)則與任何接口方法或?qū)傩缘膶?shí)現(xiàn)規(guī)則基本相同。
- 在類中實(shí)現(xiàn)接口事件
在類中聲明事件,然后在適當(dāng)?shù)奈恢谜{(diào)用該事件。
- public interface IDrawingObject
- {
- event EventHandler ShapeChanged;
- }
- public class MyEventArgs : EventArgs {…}
- public class Shape : IDrawingObject
- {
- event EventHandler ShapeChanged;
- void ChangeShape()
- {
- // Do something before the event…
- OnShapeChanged(new MyEventsArgs(…));
- // or do something after the event.
- }
- protected virtual void OnShapeChanged(MyEventArgs e)
- {
- if(ShapeChanged != null)
- {
- ShapeChanged(this, e);
- }
- }
- }
下面的示例演示如何處理以下的不常見情況:您的類是從兩個(gè)以上的接口繼承的,每個(gè)接口都含有同名事件)。在這種情況下,您至少要為其中一個(gè)事件提供顯式接口實(shí)現(xiàn)。為事件編寫顯式接口實(shí)現(xiàn)時(shí),必須編寫 add 和 remove 事件訪問器。這兩個(gè)事件訪問器通常由編譯器提供,但在這種情況下編譯器不能提供。
您可以提供自己的訪問器,以便指定這兩個(gè)事件是由您的類中的同一事件表示,還是由不同事件表示。例如,根據(jù)接口規(guī)范,如果事件應(yīng)在不同時(shí)間引發(fā),則可以將每個(gè)事件與類中的一個(gè)單獨(dú)實(shí)現(xiàn)關(guān)聯(lián)。在下面的示例中,訂戶將形狀引用強(qiáng)制轉(zhuǎn)換為 IShape 或 IDrawingObject,從而確定自己將會(huì)接收哪個(gè) OnDraw 事件。
- namespace WrapTwoInterfaceEvents
- {
- using System;
- public interface IDrawingObject
- {
- event EventHandler OnDraw;
- }
- public interface IShape
- {
- event EventHandler OnDraw;
- }
- public class Shape : IDrawingObject, IShape
- {
- event EventHandler PreDrawEvent;
- event EventHandler PostDrawEvent;
- event EventHandler IDrawingObject.OnDraw
- {
- add { PreDrawEvent += value; }
- remove { PreDrawEvent -= value; }
- }
- event EventHandler IShape.OnDraw
- {
- add { PostDrawEvent += value; }
- remove { PostDrawEvent -= value; }
- }
- public void Draw()
- {
- EventHandler handler = PreDrawEvent;
- if (handler != null)
- {
- handler(this, new EventArgs());
- }
- Console.WriteLine("Drawing a shape.");
- handler = PostDrawEvent;
- if (handler != null)
- {
- handler(this, new EventArgs());
- }
- }
- }
- public class Subscriber1
- {
- public Subscriber1(Shape shape)
- {
- IDrawingObject d = (IDrawingObject)shape;
- d.OnDraw += new EventHandler(d_OnDraw);
- }
- void d_OnDraw(object sender, EventArgs e)
- {
- Console.WriteLine("Sub1 receives the IDrawingObject event.");
- }
- }
- public class Subscriber2
- {
- public Subscriber2(Shape shape)
- {
- IShape d = (IShape)shape;
- d.OnDraw += new EventHandler(d_OnDraw);
- }
- void d_OnDraw(object sender, EventArgs e)
- {
- Console.WriteLine("Sub2 receives the IShape event.");
- }
- }
- public class Program
- {
- static void Main(string[] args)
- {
- Shape shape = new Shape();
- Subscriber1 sub = new Subscriber1(shape);
- Subscriber2 sub2 = new Subscriber2(shape);
- shape.Draw();
- Console.WriteLine("Press Enter to close this window.");
- Console.ReadLine();
- }
- }
- }
使用字典存儲(chǔ)事件實(shí)例
accessor-declarations 的一種用法是公開大量的事件但不為每個(gè)事件分配字段,而是使用字典來(lái)存儲(chǔ)這些事件實(shí)例。這只有在具有非常多的事件、但您預(yù)計(jì)大部分事件都不會(huì)實(shí)現(xiàn)時(shí)才有用。
- public delegate void EventHandler1(int i);
- public delegate void EventHandler2(string s);
- public class PropertyEventsSample
- {
- private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;
- public PropertyEventsSample()
- {
- eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
- eventTable.Add("Event1", null);
- eventTable.Add("Event2", null);
- }
- public event EventHandler1 Event1
- {
- add
- {
- eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
- }
- remove
- {
- eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
- }
- }
- public event EventHandler2 Event2
- {
- add
- {
- eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
- }
- remove
- {
- eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
- }
- }
- internal void RaiseEvent1(int i)
- {
- EventHandler1 handler1;
- if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
- {
- handler1(i);
- }
- }
- internal void RaiseEvent2(string s)
- {
- EventHandler2 handler2;
- if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
- {
- handler2(s);
- }
- }
- }
- public class TestClass
- {
- public static void Delegate1Method(int i)
- {
- System.Console.WriteLine(i);
- }
- public static void Delegate2Method(string s)
- {
- System.Console.WriteLine(s);
- }
- static void Main()
- {
- PropertyEventsSample p = new PropertyEventsSample();
- p.Event1 += new EventHandler1(TestClass.Delegate1Method);
- p.Event1 += new EventHandler1(TestClass.Delegate1Method);
- p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
- p.RaiseEvent1(2);
- p.Event2 += new EventHandler2(TestClass.Delegate2Method);
- p.Event2 += new EventHandler2(TestClass.Delegate2Method);
- p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
- p.RaiseEvent2("TestString");
- }
- }
事件的異步模式
有多種方式可向客戶端代碼公開異步功能?;谑录漠惒侥J綖轭愐?guī)定了用于顯示異步行為的建議方式。對(duì)于相對(duì)簡(jiǎn)單的多線程應(yīng)用程序,BackgroundWorker 組件提供了一個(gè)簡(jiǎn)單的解決方案。對(duì)于更復(fù)雜的異步應(yīng)用程序,請(qǐng)考慮實(shí)現(xiàn)一個(gè)符合基于事件的異步模式的類。
- “在后臺(tái)”執(zhí)行耗時(shí)任務(wù)(例如下載和數(shù)據(jù)庫(kù)操作),但不會(huì)中斷您的應(yīng)用程序。
- 同時(shí)執(zhí)行多個(gè)操作,每個(gè)操作完成時(shí)都會(huì)接到通知。
- 等待資源變得可用,但不會(huì)停止(“掛起”)您的應(yīng)用程序。
- 使用熟悉的事件和委托模型與掛起的異步操作通信。