C# 事件(Event)
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。 .NET 中的事件遵循观察者设计模式。 引发事件的类称为发布者,接收通知的类称为订阅者。 一个事件可以有多个订阅者。 通常,发布者会在发生某些操作时引发事件。 有兴趣的订阅者应该注册一个事件并处理它。
在 C# 中,事件是封装的委托。 它依赖于委托。 事件发布者和订阅者都要遵循委托的签名。 下图说明了 C# 中的事件。

声明一个事件
一个事件可以分两步声明:
- 声明一个委托 delegate。
- 使用 event 关键字声明委托的变量。 以下示例显示了如何在发布者类中声明事件。
csharp
public class DemoPublisher
{
// Action是系统自带的 delegate
public event Action ProcessCompleted; // event
}在上面的示例中,我们使用了系统的委托Action,然后使用 DemoPublisher 类中的“event”关键字声明了一个委托类型 Action 的事件 ProcessCompleted。 因此,DemoPublisher 类称为发布者。 Action 指定 ProcessCompleted 事件处理程序的签名。 它指定订阅者类中的事件处理程序方法必须具有 void 返回类型且没有参数。
现在,让我们看看如何引发 ProcessCompleted 事件。 考虑以下实现。
csharp
class Program
{
public class DemoPublisher
{
public event Action ProcessCompleted; // 事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 其它的代码
OnProcessCompleted();
}
protected virtual void OnProcessCompleted() // 保护级别的虚方法
{
//?. 如果 ProcessCompleted不为null则调用它
ProcessCompleted?.Invoke();
}
}
}上面的 StartProcess() 方法在最后调用了 onProcessCompleted() 方法,从而引发了一个事件。 通常,要引发事件,应使用名称 On<EventName> 定义受保护和虚方法。 受保护和virtual使派生类能够重写引发事件的逻辑。 但是,派生类应始终调用基类的 On<EventName> 方法以确保注册的委托接收事件。
OnProcessCompleted() 方法使用 ProcessCompleted?.Invoke(); 调用委托。 这会调用所有注册ProcessCompleted事件的处理程序方法。
订阅者类必须注册到 ProcessCompleted 事件并使用签名与 Action 委托匹配的方法处理它,如下所示。
csharp
class Program
{
public static void Main()
{
var bl = new DemoPublisher();
bl.ProcessCompleted += bl_ProcessCompleted; // 注册事件处理方法
bl.StartProcess();
}
// event handler
public static void bl_ProcessCompleted()
{
Console.WriteLine("Process Completed!");
}
}上面的 Program 类是 ProcessCompleted 事件的订阅者。 它使用 += 运算符向事件注册。 请记住,这与我们在多播委托的调用列表中添加方法的方式相同。 bl_ProcessCompleted() 方法处理该事件,因为它与 Action 委托的签名相匹配。
内置 EventHandler 委托
.NET 包括用于最常见事件的内置委托类型 EventHandler 和 EventHandler<TEventArgs>。 通常,任何事件都应该包括两个参数:事件的来源和事件数据。 对所有不包含事件数据的事件使用 EventHandler 委托。 对包含要发送到处理程序的数据的事件使用 EventHandler<TEventArgs> 委托。
上面显示的示例可以使用 EventHandler 委托来改写,如下所示。
csharp
class Program
{
public static void Main()
{
var bl = new DemoPublisher();
bl.ProcessCompleted += Bl_ProcessCompleted; ; // 注册事件处理方法
bl.StartProcess();
}
private static void Bl_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
public class DemoPublisher
{
public event EventHandler ProcessCompleted; // 事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 其它的代码
OnProcessCompleted(EventArgs.Empty);
}
protected virtual void OnProcessCompleted(EventArgs e) // 保护级别的虚方法
{
//?. 如果 ProcessCompleted不为null则调用它
ProcessCompleted?.Invoke(this, e);
}
}
}自定义一个EventArgs
上面的例子我们只传递了一个空的事件参数。很多时候我们需要传递一些有用的参数给事件订阅者。 下面我就定义了一个自己的MyEventArgs。
csharp
class Program
{
public static void Main()
{
var bl = new DemoPublisher();
bl.ProcessCompleted += Bl_ProcessCompleted; ; // 注册事件处理方法
bl.StartProcess();
}
private static void Bl_ProcessCompleted(object sender, MyEventArgs e)
{
Console.WriteLine("Process " + (e.IsSuccessful ? "Completed Successfully" : "failed"));
Console.WriteLine("Completion Time: " + e.CompletionTime.ToLongDateString());
}
public class DemoPublisher
{
public event EventHandler<MyEventArgs> ProcessCompleted; // 事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 其它的代码
var args = new MyEventArgs()
{
IsSuccessful = true,
CompletionTime = DateTime.UtcNow
};
OnProcessCompleted(args);
}
protected virtual void OnProcessCompleted(MyEventArgs e) // 保护级别的虚方法
{
//?. 如果 ProcessCompleted不为null则调用它
ProcessCompleted?.Invoke(this, e);
}
}
}
public class MyEventArgs : EventArgs
{
public bool IsSuccessful { get; set; }
public DateTime CompletionTime { get; set; }
}传递事件数据2
使用Action来定义事件。 当一个学生注册成功的时候,我们希望给它发送一个邮件,然后还有发送优惠券。等。这些都可以放在事件里面。
csharp
class Program
{
public static void Main()
{
var service = new StudentService();
service.RegisterCompleted += SendEmail; ; // 注册事件处理方法
service.RegisterCompleted += GiveVoucher;
var s = new Student();
service.Register(s);
}
private static void GiveVoucher(Student obj)
{
Console.WriteLine("给学生赠送优惠券");
}
private static void SendEmail(Student s)
{
Console.WriteLine("发送注册成功邮件给学生");
}
public class StudentService
{
public event Action<Student> RegisterCompleted; // 事件
public void Register(Student s)
{
//把 student数据插入到数据库
OnRegisterCompleted(s);
}
protected virtual void OnRegisterCompleted(Student s) // 保护级别的虚方法
{
//?. 如果 ProcessCompleted不为null则调用它
RegisterCompleted?.Invoke(s);
}
}
}注意这种事件的处理是同步的方式。一个事件处理方法没有完成会卡住其它的事件处理方法,和事件发布者的方法。
要记住的要点:
- 事件是委托的包装器。 它依赖于委托。
- 使用带有委托类型变量的“event”关键字来声明一个事件。
- 对常见事件使用内置委托 EventHandler, EventHandler<TEventArgs>,或 Action<T>
- 发布者类引发一个事件,订阅者类注册一个事件并提供事件处理的方法。
- 触发事件的方法名称通常为 On<EventName>。
- 处理事件的方法签名必须与委托签名匹配。
- 使用 += 运算符注册事件。 使用 -= 运算符取消订阅。 不能使用 = 运算符。
- 使用 EventHandler<TEventArgs> 传递事件数据或者Action传递数据。
- 派生 EventArgs 基类以创建自定义事件数据类。
- 事件可以声明为静态的、虚拟的、密封的和抽象的。
- 接口可以将事件作为成员包含在内。
- 如果有多个订阅者,则会同步调用事件处理程序,这个会卡住其它的事件方法。
