事件
在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接受(或处理)事件的类称为“订户”。
在典型的C# Windows窗体或Web应用程序中,可订阅由控件(如按钮和列表框)引发的事件。可使用Visual C#集成开发环境(IDE)来浏览控件发布的事件,选择要处理的事件。IDE会自动添加空事件处理程序方法和订阅事件的代码。
事件具有以下特点:
l 发行者确定何时引发事件,订户确定执行何种操作来相应该事件
l 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件
l 没有订户的事件永远不会被调用
l 事件通常用于通知用户操作(如:图形用户界面中的按钮单击或菜单选择操作)
l 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序。要异步调用事件
l 可以利用事件同步线程
l 在.NET Framework类库中,事件是基于EventHandler委托和EventArgs基类的
如何:订阅和取消订阅事件
如果您向编写引发事件时调用的自定义代码,则可以订阅由其他类发布的事件。例如,可以订阅某个按钮的“单击”事件,以使应用程序在用户单击该按钮时执行一些有用的操作。
使用Visual Studio 2005 IDE订阅事件
双击要创建的事件,例如Load事件。
Visual C#会创建一个空事件处理程序方法,并将其添加到您的代码中。或者,您也可以在“代码”视图中手动添加代码。例如,下面的代码行声明了一个在Form类引发Load事件时调用的事件处理程序方法。
private void Form1_Load(object sender, System.EventArgs e)
{
// Add your form load event handling code here.
}
订阅该事件所需的代码行也会在您项目的Form1.Designer.cs文件的InitializeComponent方法中自动生成。该代码行类似于:
this.Load += new System.EventHandler(this.Form1_Load);
以编程方式订阅事件
1. 定义一个事件处理程序方法,其签名与该事件的委托签名匹配。例如,如果事件基于EventHandler委托类型,则下面的代码表示方法存根:
void HandleCustomEvent(object sender, CustomEventArgs a)
{
// Do something useful here.
}
2. 使用加法赋值运算符(+=)来为事件附加事件处理程序。在下面的示例中,假设名为publisher的对象拥有一个名为RaiseCustomEvent的事件。请注意,订户类需要引发发行者类才能订阅其事件。
publisher.RaiseCustomEvent += HandleCustomEvent;
请注意,上面的语法是C# 2.0中的新语法。它完全等效于C# 1.0语法,必须使用以下新关键字显式创建封装委托:
publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);
使用匿名方法订阅事件
使用加法赋值运算符(+=)来为事件附加匿名方法。在下面的示例中,假设名为publisher的对象拥有一个名为RaiseCustomEvent的事件,并且还定义了一个CustomEventArgs类以承载某些类型的专用事件信息。请注意,订户类需要引用publisher才能订阅其事件。
publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
{
string s = o.ToString() + " " + e.ToString();
Console.WriteLine(s);
};
注意,如果使用匿名方法订阅事件,该事件的取消过程就比较麻烦。此时要取消订阅,请返回到该事件的订阅代码,将该匿名方法存储在委托变量中,然后将委托添加到该事件中。
取消订阅
要防止在引发事件时调用事件处理程序,只要取消订阅该事件。要防止资源泄露,请在释放订户对象之前取消订阅事件,这一点很重要。在取消订阅事件之前,在发布对象中作为该事件的基础的多路广播委托会引用封装了订户的事件处理程序的委托。只要发布对象包含该引用,就不会对订户对象执行垃圾回收。
取消订阅事件
使用减法赋值运算符(-=)取消订阅事件:
publisher.RaiseCustomEvent -= HandleCustomEvent;
所有订户取消订阅某事件后,发行者类中的事件实例会设置为null。
如何:发布符合.NET Framework准则的事件
下面的过程演示了如何将符合标准.NET Framework模式的事件添加到您自己的类和结构中。.NET Framework类库中的所有事件均基于EventHandler委托,定义如下:
public delegate void EventHandler(object sender, EventArgs e);
注意
.NET Framework 2.0引入了此委托的一个泛型版本,即EventHandler<T>。下面的示例显示如何使用这两个版本。
虽然您定义的类中的事件可采用任何有效委托类型(包括会返回值的委托),但是,通常建议您使用EventHandler让事件采用.NET Framework模式,如下面的示例所示:
采用EventHandler模式发布事件
1.(如果不需要发送含事件的自定义数据,请跳过此步骤,直接进入步骤 3a 。)在发行者和订户类均可看见的范围中声明类,并添加保留自定义事件数据所需的成员。在此示例中,会返回一个简单字符串。
public class CustomEventArgs : EventArgs
{
public CustomEventArgs(string s)
{
msg = s;
}
private string msg;
public string Message
{
get { return msg; }
}
}
2.(如果您使用的是EventHandler的泛型版本,请跳过此步骤。)在发布类中声明一个委托。为它指定以EventHandler结尾的名称。第二个参数指定自定义EventArgs类型。
public delegate void CustomEventHandler(object sender, CustomEventArgs a);
3.使用以下任一步骤,在发布类中声明事件。
a.如果没有自定义EventArgs类,事件类型就是非泛型EventHandler委托。它无需声明,因为它已在C#项目默认包含的System命名空间中进行了声明:
public event EventHandler RaiseCustomEvent;
b.如果使用的是EventHandler的非泛型版本,并且您有一个由EventArgs派生的自定义类,请在发布类中声明您的事件,并且将您的委托用作类型:
class Publisher
{
public event CustomEventHandler RaiseCustomEvent;
}
c.如果使用的是泛型版本,则不需要自定义委托。相反,应将事件类型指定为EventHandler<CustomEventArgs>,在尖括号内放置您自己的类的名称。
public event EventHandler<CustomEventArgs> RaiseCustomEvent;
示例
下面的示例演示了上述步骤,它将自定义EventArgs类和EventHandler<T>用作事件类型。
namespace DotNetEvents ... { using System; using System.Collections.Generic; //Define a class to hold custom event info public class CustomEventArgs : EventArgs...{ public CustomEventArgs(string s)...{ message = s; } private string message; public string Message...{ get...{ return message; } set...{ message = value; } } //Class that publishes an event class Publisher...{ //Declare the event using EventHandler<T> public event EventHandler<CustomEventArgs> RaiseCustomEvent; public void DoSomething()...{ //Write some code that does something useful here //then raise the event. You can also raise an event //before you execute a block of code. OnRaiseCustomEvent(new CustomEventArgs("Did something")); } //Wrap event invocations inside a protected virtual method //to allow derived classes to override the event invocation behavior protected virtual void OnRaiseCustomEvent(CustomEventArgs e)...{ //Make a temporary copy of the event to avoid possibility of //a race condition if the last subscriber unsubscribes //immediately after the null check and before the event is raised. EventHandler<CustomEventArgs> handler = RaiseCustomEvent; //Event will be null if there are no subscribers if (handler != null)...{ //Format the string to send inside the CustomEventArgs parameter e.Message += string.Format(" at {0}",DateTime.Now.ToString()); //Use the () operator to raise the event. handler(this,e); } } } //Class that subscribes to an event class Subscriber...{ private string id; public Subscriber(string ID,Publisher pub)...{ id = ID; //Subscribe to the event using C# 2.0 syntax pub.RaiseCustomEvent += HandlerCustomEvent; } //Define what actions to take when the event is raised. void HandlerCustomEvent(object sender,CustomEventArgs e)...{ Console.WriteLine(id + " received this message: {0}",e.Message); } } class Program...{ static void Main(string[] args)...{ Publisher pub = new Publisher(); Subscriber sub1 = new Subscriber("sub1",pub); Subscriber sub2 = new Subscriber("sub2",pub); //Call the method that raises the event. pub.DoSomething(); //keep the console window open Console.WriteLine("Press Enter to close this window."); Console.ReadLine(); } } } }如何:引发派生类中的基类事件
以下简单示例演示了在基类中声明可以从派生类引发的事件的标准方法。此模式广泛应用于.NET Framework基类库中的Windows窗体类。
在创建可用作其他类的基类的类时,必须考虑如下事实:事件是特殊类型的委托,只可以从声明它们的类中调用。派生类无法直接调用基类中声明的事件。尽管有时可能希望某个事件只能通过基类引发,但在多数情况下,应该允许派生类调用基类事件。为此,可以在包含该事件的基类中创建一个受保护的调用方法。通过调用或重写此调用方法,派生类便可以间接调用该事件。
示例
namespace BaseClassEvents ... { using System; using System.Collections.Generic; //Special EventArgs class to hold info about Shapes. public class ShapeEventArgs : EventArgs...{ private double newArea; public ShapeEventArgs(double d)...{ newArea = d; } public double NewArea...{ get...{ return newArea; } } } //Base class event publisher public abstract class Shape...{ protected double area; public double Area...{ get...{ return area; } set...{ area = value; } } //The event. Note that by using the generic EventHandler<T> event type //we do not need to declare a separate delegate type. public event EventHandler<ShapeEventArgs> ShapeChanged; public abstract void Draw(); //The event-invoking method that derived classes can override. protected virtual void OnShapeChanged(ShapeEventArgs e)...{ //Make a temporary copy of the event to avoid possibility of //a race condition if the last subscriber unsubscribes //immediately after the null check and before the event is raised. 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)...{ //Do any circle-specific processing here. //Call the base class event invocation method. 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)...{ //Do any rectangle-specific processing here. //Call the base class event invocation method. base.OnShapeChanged(e); } public override void Draw()...{ Console.WriteLine("Drawing a rectangle"); } } //Represents the surface on which the shapes are drawn //Subscribes to shape events so that it knows //when to redraw a shape. public class ShapeContainer...{ List<Shape> _list; public ShapeContainer()...{ _list = new List<Shape>(); } public void AddShape(Shape s)...{ _list.Add(s); //Subscribe to the base class event. s.ShapeChanged += HandleShapeChanged; } //...Other methods to draw, resize, etc. private void HandleShapeChanged(object sender, ShapeEventArgs e)...{ Shape s = (Shape)sender; //Diagnostic message for demonstration purposes. Console.WriteLine("Received event. Shape area is now {0}",e.NewArea); //Redraw the shape here. s.Draw(); } } class Test...{ static void Main(string[] args)...{ //Create the event publishers and subscriber Circle c1 = new Circle(54); Rectangle r1 = new Rectangle(12,9); ShapeContainer sc = new ShapeContainer(); //Add the shapes to the container sc.AddShape(c1); sc.AddShape(r1); //Cause some events to be raised. c1.Update(57); r1.Update(7,7); //Keep the console window open. Console.WriteLine(); Console.WriteLine("Press Enter to exit"); Console.ReadLine(); } } }