C# - 事件

本文关键字:事件 | 更新日期: 2023-09-14 19:55:51

事件是对象发送的通知,用于指示操作的发生。 .NET 中的事件遵循观察者设计模式。

引发事件的类称为发布服务器,接收通知的类称为订阅服务器。单个事件可以有多个订阅者。 通常,发布者会在发生某些操作时引发事件。有兴趣在发生操作时收到通知的订阅者应注册事件并处理它。

在 C# 中,事件是一个封装的delegate。它取决于委托。delegate 定义订阅者类的事件处理程序方法的签名。

下图说明了 C# 中的事件。

Event Publisher & Subscriber

声明事件

可以通过两个步骤声明事件:

  1. 声明委托。
  2. 使用 event 关键字声明委托的变量。

下面的示例演示如何在发布者类中声明事件。

示例: Declaring an Event

public delegate void Notify();  // delegate
                    
public class ProcessBusinessLogic
{
    public event Notify ProcessCompleted; // event
}

在上面的示例中,我们声明了一个委托Notify,然后在 ProcessBusinessLogic 类中使用 "event" 关键字声明了一个委托类型 Notify 的事件ProcessCompleted。 因此,ProcessBusinessLogic类称为发布服务器。 Notify委托指定ProcessCompleted事件处理程序的签名。它指定订阅者类中的事件处理程序方法必须具有 void 返回类型且没有参数。

现在,让我们看看如何引发ProcessCompleted事件。请考虑以下实现。

示例: Raising an Event

public delegate void Notify();  // delegate
                    
public class ProcessBusinessLogic
{
    public event Notify ProcessCompleted; // event
    public void StartProcess()
    {
        Console.WriteLine("Process Started!");
        // some code here..
        OnProcessCompleted();
    }
    protected virtual void OnProcessCompleted() //protected virtual method
    {
        //if ProcessCompleted is not null then call delegate
        ProcessCompleted?.Invoke(); 
    }
}

上面,StartProcess() 方法在末尾调用onProcessCompleted()的方法,从而引发一个事件。 通常,要引发事件,应使用名称 On<EventName> 定义受保护和虚拟方法。 受保护和虚拟使派生类能够重写引发事件的逻辑。但是,派生类应始终调用基类的 On<EventName> 方法,以确保注册的委托接收事件。

OnProcessCompleted() 方法使用 ProcessCompleted?.Invoke(); 调用委托。这将调用向ProcessCompleted事件注册的所有事件处理程序方法。

订阅者类必须注册到ProcessCompleted事件,并使用其签名与委托匹配的方法处理它Notify如下所示。

示例: Consume an Event

class Program
{
    public static void Main()
    {
        ProcessBusinessLogic bl = new ProcessBusinessLogic();
        bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
        bl.StartProcess();
    }
    // event handler
    public static void bl_ProcessCompleted()
    {
        Console.WriteLine("Process Completed!");
    }
}

上面,Program类是ProcessCompleted事件的订阅者。 它使用 += 运算符向事件注册。请记住,这与我们在多播委托的调用列表中添加方法的方式相同。 bl_ProcessCompleted() 方法处理事件,因为它与Notify委托的签名匹配。

内置事件处理程序委托

.NET Framework 包括内置委托类型EventHandler 和EventHandler用于最常见的事件。 通常,任何事件都应包含两个参数:事件源和事件数据。对不包含事件数据的所有事件使用 EventHandler 委托。对包含要发送到处理程序的数据的事件使用EventHandler委托。

上面显示的示例可以使用EventHandler委托,而无需声明自定义Notify委托,如下所示。

示例: EventHandler

class Program
{
    public static void Main()
    {
        ProcessBusinessLogic bl = new ProcessBusinessLogic();
        bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
        bl.StartProcess();
    }
    // event handler
    public static void bl_ProcessCompleted(object sender, EventArgs e)
    {
        Console.WriteLine("Process Completed!");
    }
}
public class ProcessBusinessLogic
{
    // declaring an event using built-in EventHandler
    public event EventHandler ProcessCompleted; 
    public void StartProcess()
    {
        Console.WriteLine("Process Started!");
        // some code here..
        OnProcessCompleted(EventArgs.Empty); //No event data
    }
    protected virtual void OnProcessCompleted(EventArgs e)
    {
        ProcessCompleted?.Invoke(this, e);
    }
}

在上面的示例中,事件处理程序bl_ProcessCompleted()方法包括两个与事件处理程序委托匹配的参数。 此外,当我们在 OnProcessCompleted() 方法中使用 Invoke() 引发事件时,将this作为发送者和EventArgs.Empty传递。 因为我们的事件不需要任何数据,所以它只是通知订阅者该过程的完成,所以我们通过了EventArgs.Empty

传递事件数据

大多数事件会向订阅者发送一些数据。EventArgs 类是所有事件数据类的基类。 .NET 包含许多内置的事件数据类,例如 SerialDataReceivedEventArgs。 它遵循使用 EventArgs 结束所有事件数据类的命名模式。可以通过派生 EventArgs 类来为事件数据创建自定义类。

使用EventHandler将数据传递给处理程序,如下所示。

示例: Passing Event Data

class Program
{
    public static void Main()
    {
        ProcessBusinessLogic bl = new ProcessBusinessLogic();
        bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
        bl.StartProcess();
    }
    // event handler
    public static void bl_ProcessCompleted(object sender, bool IsSuccessful)
    {
        Console.WriteLine("Process " + (IsSuccessful? "Completed Successfully": "failed"));
    }
}
public class ProcessBusinessLogic
{
    // declaring an event using built-in EventHandler
    public event EventHandler<bool> ProcessCompleted; 
    public void StartProcess()
    {
        try
        {
            Console.WriteLine("Process Started!");
			
            // some code here..
            OnProcessCompleted(true);
        }
        catch(Exception ex)
        {
            OnProcessCompleted(false);
        }
    }
    protected virtual void OnProcessCompleted(bool IsSuccessful)
    {
        ProcessCompleted?.Invoke(this, IsSuccessful);
    }
}

在上面的示例中,我们将单个布尔值传递给指示流程是否成功完成的处理程序。

如果要将多个值作为事件数据传递,请创建一个从 EventArgs 基类派生的类,如下所示。

示例: Custom EventArgs Class

class ProcessEventArgs : EventArgs
{
    public bool IsSuccessful { get; set; }
    public DateTime CompletionTime { get; set; }
}

下面的示例演示如何将自定义ProcessEventArgs类传递给处理程序。

示例: Passing Custom EventArgs

class Program
{
    public static void Main()
    {
        ProcessBusinessLogic bl = new ProcessBusinessLogic();
        bl.ProcessCompleted += bl_ProcessCompleted; // register with an event
        bl.StartProcess();
    }
    // event handler
    public static void bl_ProcessCompleted(object sender, ProcessEventArgs e)
    {
        Console.WriteLine("Process " + (e.IsSuccessful? "Completed Successfully": "failed"));
        Console.WriteLine("Completion Time: " + e.CompletionTime.ToLongDateString());
    }
}
public class ProcessBusinessLogic
{
    // declaring an event using built-in EventHandler
    public event EventHandler<ProcessEventArgs> ProcessCompleted; 
    public void StartProcess()
    {
        var data = new ProcessEventArgs();
		
        try
        {
            Console.WriteLine("Process Started!");
			
            // some code here..
            
            data.IsSuccessful = true;
            data.CompletionTime = DateTime.Now;
            OnProcessCompleted(data);
        }
        catch(Exception ex)
        {
            data.IsSuccessful = false;
            data.CompletionTime = DateTime.Now;
            OnProcessCompleted(data);
        }
    }
    protected virtual void OnProcessCompleted(ProcessEventArgs e)
    {
        ProcessCompleted?.Invoke(this, e);
    }
}

因此,可以在 C# 中创建、引发、注册和处理事件。

注意:

  1. 事件是委托的包装器。这取决于代表。
  2. 使用带有委托类型变量的"event"关键字来声明事件。
  3. 对常见事件使用内置委托 EventHandlerEventHandler<TEventArgs>
  4. 发布者类引发事件,订阅者类注册事件并提供事件处理程序方法。
  5. 将引发以"On"为前缀的事件的方法命名为事件名称。
  6. 处理程序方法的签名必须与委托签名匹配。
  7. 使用 += 运算符注册事件。使用 -= 运算符取消订阅。不能使用 = 运算符。
  8. 使用 EventHandler<TEventArgs>传递事件数据。
  9. 派生 EventArgs 基类以创建自定义事件数据类。
  10. 事件可以声明为静态、虚拟、密封和抽象。
  11. 接口可以将事件作为成员包含在内。
  12. 如果有多个订阅者,则会同步调用事件处理程序。