目录
C# 事件(Event)
事件概述
如何订阅和取消订阅事件
以编程方式订阅事件
使用匿名函数订阅事件
取消订阅
如何发布符合 .NET 准则的事件
发布基于 EventHandler 模式的事件
如何在派生类中引发基类事件
如何实现接口事件
如何实现自定义事件访问器
示例
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
C# 中使用事件机制实现线程间的通信。
事件是一种在软件开发中常用的机制,用于实现对象间的通信和协作。一个事件有两个主要的参与者:事件发布者和事件订阅者。
事件发布者是引发事件的对象或类,它定义了一个事件以及触发该事件的条件。事件发布者通过调用事件来通知其他对象事件的发生。
事件订阅者是希望接收并处理事件的对象或类。订阅者需要将自己的方法注册到事件上,以便在事件发生时被调用。这个方法被称为事件处理程序,用于处理事件发生时的逻辑。
在.NET类库中,事件通常基于EventHandler委托和EventArgs基类。EventHandler委托定义了事件处理程序方法的签名,它接受两个参数:事件发布者和包含事件数据的EventArgs对象。通过使用标准的EventHandler委托,可以方便地在事件订阅者之间共享事件处理程序。
事件的使用可以带来很多好处。它可以将代码分离为更小、更可维护的部分,并提供了一种松耦合的方式来实现对象间的通信。通过事件,可以实现多个对象对同一个事件作出响应的情况,从而提高了代码的灵活性和可扩展性。
总结起来,事件是一种在软件开发中常用的机制,用于实现对象间的通信和协作。通过事件,事件发布者可以通知事件订阅者事件的发生,并且订阅者可以对事件作出响应。事件基于委托和事件参数类,可以提供更灵活、可维护和可扩展的代码结构。
在C#中,可以使用+=运算符以编程方式订阅事件。下面是一个简单的示例:
using System;
class Program
{
static void Main(string[] args)
{
MyEventPublisher publisher = new MyEventPublisher();
MyEventHandler handler = new MyEventHandler();
// 订阅事件
publisher.MyEvent += handler.HandleMyEvent;
// 触发事件
publisher.TriggerMyEvent();
Console.ReadLine();
}
}
class MyEventPublisher
{
// 定义事件
public event EventHandler MyEvent;
// 触发事件
public void TriggerMyEvent()
{
MyEvent?.Invoke(this, EventArgs.Empty);
}
}
class MyEventHandler
{
// 处理事件
public void HandleMyEvent(object sender, EventArgs e)
{
Console.WriteLine("由我的事件处理程序处理的事件");
}
}
在上面的示例中,我们定义了一个名为MyEventPublisher的事件发布者类和一个名为MyEventHandler的事件处理程序类。事件发布者类包含一个名为MyEvent的事件,它是基于标准的EventHandler委托定义的。事件处理程序类包含一个名为HandleMyEvent的方法,用于处理事件。
在Main方法中,我们创建了一个事件发布者实例和一个事件处理程序实例。然后,我们使用+=运算符将事件处理程序注册到事件上。最后,我们调用TriggerMyEvent方法触发事件,并在事件处理程序中输出一条消息。
总之,在C#中,可以使用+=运算符以编程方式订阅事件。这使得我们能够动态地向事件添加处理程序,并在不再需要处理程序时将其从事件中删除。
当需要快速订阅事件且事件处理逻辑相对简单时,使用匿名函数订阅事件是一种方便的方式。下面是一个示例,演示了如何在C#中使用匿名函数订阅事件:
using System;
class Program
{
static void Main(string[] args)
{
MyEventPublisher publisher = new MyEventPublisher();
// 使用匿名函数订阅事件
publisher.RaiseCustomEvent += (sender, e) =>
{
Console.WriteLine("正在处理带有消息的自定义事件: " + e.Message);
};
// 触发事件
publisher.TriggerCustomEvent();
Console.ReadLine();
}
}
class MyEventPublisher
{
// 定义事件
public event EventHandler RaiseCustomEvent;
// 触发事件
public void TriggerCustomEvent()
{
OnRaiseCustomEvent(new CustomEventArgs("事件已触发"));
}
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
RaiseCustomEvent?.Invoke(this, e);
}
}
// 自定义事件参数类
public class CustomEventArgs : EventArgs
{
public string Message { get; }
public CustomEventArgs(string message)
{
Message = message;
}
}
在上面的示例中,我们使用匿名函数来订阅RaiseCustomEvent事件。匿名函数直接定义在订阅操作中,用于处理事件的触发。这样可以避免显式地定义命名的事件处理程序方法,特别适用于只需要进行简单处理的情况。
注意:如果使用匿名函数订阅事件,事件的取消订阅过程将比较麻烦。 这种情况下若要取消订阅,请返回到该事件的订阅代码,将该匿名函数存储在委托变量中,然后将此委托添加到该事件中。 如果必须在后面的代码中取消订阅某个事件,则建议不要使用匿名函数订阅此事件。 有关匿名函数的详细信息,请参阅 Lambda 表达式。
通过匿名函数订阅事件,可以更加简洁地表示事件订阅和处理逻辑,提高代码的可读性和简洁性。
若要防止在引发事件时调用事件处理程序,请取消订阅该事件。 为了防止资源泄露,应在释放订户对象之前取消订阅事件。 在取消订阅事件之前,在发布对象中作为该事件的基础的多播委托会引用封装了订户的事件处理程序的委托。 只要发布对象保持该引用,垃圾回收功能就不会删除订户对象。
在C#中,取消订阅事件可以通过使用减法赋值运算符(-=)来实现。
下面是一个示例,演示了如何取消订阅之前示例中的事件:
using System;
class Program
{
static void Main(string[] args)
{
MyEventPublisher publisher = new MyEventPublisher();
// 定义匿名函数作为事件处理程序
EventHandler eventHandler = (sender, e) =>
{
Console.WriteLine("正在处理带有消息的自定义事件: " + e.Message);
};
// 订阅事件
publisher.RaiseCustomEvent += eventHandler;
// 触发事件
publisher.TriggerCustomEvent();
// 取消订阅事件
publisher.RaiseCustomEvent -= eventHandler;
// 再次触发事件
publisher.TriggerCustomEvent();
Console.ReadLine();
}
}
class MyEventPublisher
{
// 定义事件
public event EventHandler RaiseCustomEvent;
// 触发事件
public void TriggerCustomEvent()
{
OnRaiseCustomEvent(new CustomEventArgs("事件已触发"));
}
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
RaiseCustomEvent?.Invoke(this, e);
}
}
// 自定义事件参数类
public class CustomEventArgs : EventArgs
{
public string Message { get; }
public CustomEventArgs(string message)
{
Message = message;
}
}
在上面的示例中,我们首先定义了一个匿名函数作为事件处理程序,并将其保存在一个变量eventHandler中。然后,我们使用+=运算符订阅了RaiseCustomEvent事件,并在事件触发后又使用-=运算符取消了对事件的订阅。这样就实现了事件的订阅和取消订阅操作。
通过使用-=运算符取消订阅事件,可以避免事件处理程序继续接收事件通知,从而实现了对事件的动态管理。
注意:所有订户都取消订阅事件后,发行者类中的事件实例将设置为 null。
发布事件是一种常见的设计模式,它允许一个对象通知其他对象发生了某些事情。在 C# 中,可以使用 EventHandler 模式来实现事件发布。
基于 EventHandler 模式的事件发布包括以下步骤:
定义自定义事件参数类:通常情况下,事件需要携带一些数据。可以通过自定义事件参数类来实现这个功能。自定义事件参数类必须继承自 EventArgs 类,并且应该包含至少一个属性以保存事件数据。
声明委托类型:在发布类中声明一个委托类型,用于定义事件处理程序的签名。对于非泛型版本的 EventHandler 委托,无需声明,因为它已经在 System 命名空间中定义。对于泛型版本的 EventHandler
声明事件类型:在发布类中声明一个事件类型,使用步骤 2 中定义的委托类型作为事件处理程序的类型。如果使用泛型版本的 EventHandler
引发事件:在发布类中编写一个受保护的虚方法,用于执行实际的事件引发操作。在这个方法中,通过调用事件类型的 Invoke 方法来触发事件,并将自定义事件参数对象传递给事件处理程序。
订阅事件:在订阅方代码中,使用 += 运算符订阅事件。订阅方可以是类、结构体、接口或委托类型。当事件引发时,所有订阅了该事件的订阅方都会接收到通知,并执行相应的事件处理程序。
下面是一个基于 EventHandler 模式的事件发布的示例代码:
// 定义自定义事件参数类
public class CustomEventArgs : EventArgs
{
public string Message { get; private set; }
public CustomEventArgs(string message)
{
Message = message;
}
}
// 声明委托类型
public delegate void CustomEventHandler(object sender, CustomEventArgs e);
// 发布类
public class MyEventPublisher
{
// 声明事件类型
public event CustomEventHandler RaiseCustomEvent;
// 引发事件
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
RaiseCustomEvent?.Invoke(this, e);
}
// 触发事件的方法
public void TriggerCustomEvent()
{
OnRaiseCustomEvent(new CustomEventArgs("Hello, World!"));
}
}
// 订阅方代码
class Program
{
static void Main(string[] args)
{
MyEventPublisher publisher = new MyEventPublisher();
// 订阅事件
publisher.RaiseCustomEvent += Publisher_RaiseCustomEvent;
// 触发事件
publisher.TriggerCustomEvent();
Console.ReadLine();
}
// 事件处理程序
private static void Publisher_RaiseCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine(e.Message);
}
}
在上面的示例中,我们首先定义了一个自定义事件参数类 CustomEventArgs,它包含一个 Message 属性来保存事件数据。然后,我们声明了一个名为 CustomEventHandler 的委托类型,并用作 MyEventPublisher 类中 RaiseCustomEvent 事件的处理程序类型。在 MyEventPublisher 类中,我们使用 OnRaiseCustomEvent 方法来引发 RaiseCustomEvent 事件,然后在 TriggerCustomEvent 方法中调用该方法。
在 Main 方法中,我们创建 MyEventPublisher 实例 publisher,并使用 += 运算符订阅 RaiseCustomEvent 事件。之后,我们调用 publisher 的 TriggerCustomEvent 方法来触发事件。当 RaiseCustomEvent 事件被触发时,我们定义的事件处理程序 Publisher_RaiseCustomEvent 将被调用,并输出 "Hello, World!" 字符串到控制台。
在派生类中引发基类事件的过程与在基类中引发事件的过程类似,只需要通过基类的事件来触发即可。下面是在派生类中引发基类事件的基本步骤:
下面是一个示例代码,演示了如何在派生类中引发基类事件:
using System;
// 基类
public class BaseClass
{
// 声明基类事件类型
public event EventHandler BaseEventRaised;
// 引发基类事件的方法
protected virtual void OnBaseEventRaised(EventArgs e)
{
BaseEventRaised?.Invoke(this, e);
}
}
// 派生类
public class DerivedClass : BaseClass
{
public void DoSomething()
{
// 在派生类中进行一些操作
// 触发基类事件
OnBaseEventRaised(EventArgs.Empty);
}
}
// 主程序
class Program
{
static void Main(string[] args)
{
DerivedClass derived = new DerivedClass();
// 订阅基类事件
derived.BaseEventRaised += BaseEvent_Handler;
// 在派生类中触发事件
derived.DoSomething();
Console.ReadLine();
}
// 基类事件处理程序
private static void BaseEvent_Handler(object sender, EventArgs e)
{
Console.WriteLine("在派生类中处理的基事件。");
}
}
在上面的示例中,我们首先定义了一个基类 BaseClass,它包含一个名为 BaseEventRaised 的事件以及相应的触发方法 OnBaseEventRaised。然后,我们创建了一个派生类 DerivedClass,在该类中我们调用了基类事件触发方法 OnBaseEventRaised 来触发基类事件。
在主程序中,我们创建了 DerivedClass 的实例 derived,并订阅了基类的事件 BaseEventRaised。之后,我们调用 derived 的 DoSomething 方法来触发事件。当基类事件被触发时,我们定义的事件处理程序 BaseEvent_Handler 将被调用,并输出 "在派生类中处理的基事件。" 到控制台。
通过这种方式,派生类可以很容易地引发基类中已定义的事件,从而实现事件传播和处理。
在 C# 中,接口可以定义事件,需要使用 event关键字来标记接口中的事件。实现接口事件的类必须提供事件的实现。下面是实现接口事件的基本步骤:
定义一个包含事件的接口,并使用 event 关键字来标记事件。
实现接口的类必须提供事件的实现。为此,可以在实现类中声明一个事件,并将其与接口中的事件关联起来。
在实现类中引发事件的方法。
下面是一个示例代码,演示了如何实现接口事件:
using System;
// 定义包含事件的接口
public interface IMyInterface
{
event EventHandler MyEventRaised;
}
// 实现接口的类
public class MyClass : IMyInterface
{
// 声明事件
public event EventHandler MyEventRaised;
// 引发事件的方法
public void RaiseEvent()
{
OnMyEventRaised(EventArgs.Empty);
}
// 触发事件的方法
protected virtual void OnMyEventRaised(EventArgs e)
{
MyEventRaised?.Invoke(this, e);
}
}
// 主程序
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
// 订阅事件
myClass.MyEventRaised += MyEventHandler;
// 触发事件
myClass.RaiseEvent();
Console.ReadLine();
}
// 事件处理程序
private static void MyEventHandler(object sender, EventArgs e)
{
Console.WriteLine("我的事件处理好了。");
}
}
在上面的示例中,我们首先定义了一个包含事件的接口 IMyInterface,并使用 event 关键字来标记事件。然后,我们创建了一个实现接口的类 MyClass,在该类中声明了一个名为 MyEventRaised 的事件。在 MyClass 中,我们使用 OnMyEventRaised 方法来触发 MyEventRaised 事件。
在主程序中,我们创建了 MyClass 的实例 myClass,并订阅了 MyEventRaised 事件。之后,我们调用 myClass 的 RaiseEvent 方法来触发事件。当 MyEventRaised 事件被触发时,我们定义的事件处理程序 MyEventHandler 将被调用,并输出 "我的事件处理好了。" 到控制台。
通过这种方式,我们可以在接口中定义事件,并在实现类中提供事件的实现。因此,可以轻松地将事件的行为分离到不同的类和接口中。
事件是一种特殊的多播委托,只能从声明它的类中进行调用。 客户端代码通过提供对应在引发事件时调用的方法的引用来订阅事件。 这些方法通过事件访问器添加到委托的调用列表中,事件访问器类似于属性访问器,不同之处在于事件访问器命名为 add 和 remove。 在大多数情况下,无需提供自定义事件访问器。 如果代码中没有提供自定义事件访问器,编译器将自动添加它们。 但在某些情况下,可能需要提供自定义行为。
下面的示例演示如何实现自定义的 add 和 remove 事件访问器来控制事件处理程序的添加和移除行为。
using System;
public delegate void MyEventHandler(object sender, EventArgs e);
public class MyClass
{
private event MyEventHandler myEvent;
public event MyEventHandler MyEvent
{
add
{
Console.WriteLine("添加事件处理程序");
myEvent += value;
}
remove
{
Console.WriteLine("正在删除事件处理程序");
myEvent -= value;
}
}
public void DoSomething()
{
Console.WriteLine("正在做某事。。。");
myEvent?.Invoke(this, EventArgs.Empty);
}
}
public class Program
{
static void Main(string[] args)
{
MyClass myObj = new MyClass();
myObj.MyEvent += MyEventHandler1;
myObj.MyEvent += MyEventHandler2;
myObj.DoSomething();
myObj.MyEvent -= MyEventHandler2;
myObj.DoSomething();
Console.ReadLine();
}
private static void MyEventHandler1(object sender, EventArgs e)
{
Console.WriteLine("事件处理程序处理的事件1");
}
private static void MyEventHandler2(object sender, EventArgs e)
{
Console.WriteLine("事件处理程序处理的事件2");
}
}
在上面的示例中,我们首先声明了一个自定义委托MyEventHandler,它定义了事件处理程序的签名。
然后,我们在MyClass类中声明了一个私有的MyEventHandler类型的事件myEvent。接下来,在MyEvent属性中定义了自定义的事件访问器。
在add访问器中,我们输出添加事件处理程序的消息,并将事件处理程序添加到myEvent事件中。在remove访问器中,我们输出移除事件处理程序的消息,并将其从myEvent事件中移除。
通过自定义事件访问器,我们可以在添加和移除事件处理程序时执行自定义的逻辑。在上面的示例中,我们简单地输出一条消息,但你可以根据需要在这些访问器中添加更复杂的逻辑。
最后,在主程序中,我们创建了一个MyClass的实例myObj,并订阅了MyEvent事件。在调用myObj.DoSomething()时,事件处理程序被调用,并输出相应的消息。然后,我们使用-=操作符将一个事件处理程序从事件中移除,并再次调用myObj.DoSomething(),只有一个事件处理程序被调用。