C# 事件

时间:2025-04-17 22:05:02

C# 和 .NET 是支持事件驱动编程的,但它的处理需要委托实现来调度事件,所以事件和委托是紧密关联(耦合性高)。

事件是允许类或对象的某些状态发生改变时通知其它类或对象。

.NET 中的事件遵循观察者设计模式,常用的设计模式不熟悉的可以网上查查。

发送或引发事件的类称为发布者,接收或处理事件的类称为订阅者。

声明事件步骤

要在类中声明事件,首先必须声明事件的委托类型,如下:

public delegate void Notify();

其次,声明事件,如下:

public event Notify ProcessCompleted;

第三,调用事件,如下:

if(ProcessCompleted != null)
    ProcessCompleted();

//或者
//ProcessCompeleted?.Invoke();

只能在声明事件的类中调用事件。

第四,注册事件,如下:

 += Sample_ProcessCompeleted;

如果是要取消事件,如下:

 -= Sample_ProcessCompeleted;

完整代码:  

public class EventSample
{
    public delegate void Notify();

    public event Notify ProcessCompeleted;

    public void Start()
    {
        ("开始作业");
        //业务逻辑

        //处理完毕后触发事件
        OnProcessCompeleted();
    }

    protected virtual void OnProcessCompeleted()
    {
        ProcessCompeleted?.Invoke();
        //判断是否订阅了
        //if (ProcessCompeleted != null)
        //    ProcessCompeleted();
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventSample sample = new EventSample();
         += Sample_ProcessCompeleted;
        ();

        ();
    }

    private static void Sample_ProcessCompeleted()
    {
        ("处理完成");
    }
}

上面,Start() 方法最后一行调用了 OnProcessCompeleted() ,这引发了一个事件,通常,要引发事件,应使用名称定义受保护的虚拟方法 On<EventName> 。protected 和 virtual 使派生类能够覆盖引发事件的逻辑。但是,派生类应始终调用 On<EventName> 基类方法,以确保注册的委托接收事件。

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

Program 类是 ProcessCompeleted 事件的订阅者。它使用 += 运算符向事件注册。Sample_ProcessCompeleted() 是处理事件方法且与 Notify 委托声明的一致。

内置 EventHandler 委托

.NET 还内置了两个常用的委托类型 EventHandler 和 EventHandler<TEventArgs> 。通常,任何事件都应包括两个参数:事件源和事件数据。EventHandler 主要用于不包含事件数据;对包含事件数据的使用 EventHandler<TEventArgs> 。

先看 EventHandler 的使用。

public class EventSample
{
    //public delegate void Notify();

    public event EventHandler ProcessCompeleted;

    public void Start()
    {
        ("开始作业");
        //业务逻辑

        //处理完毕后触发事件
        OnProcessCompeleted();
    }

    protected virtual void OnProcessCompeleted(EventArgs e)
    {
        ProcessCompeleted?.Invoke(this, e);
    }
}

class Program
{
    static void Main(string[] args)
    {
        EventSample sample = new EventSample();
         += Sample_ProcessCompeleted;
        ();

        ();
    }

    private static void Sample_ProcessCompeleted(object sender,EventArgs e)
    {
        ("处理完成");
    }
}

上面代码中,Sample_ProcessCompeleted() 方法多了两个与 EventHandler 匹配的参数。此外,当我们在方法中使用引发事件时,this 作为发送者传递。另外,我们的事件不需要数据,所以使用 。

传递事件数据

大多数事件都会向订阅者发送一些数据。EventArgs 类是所有事件数据的基类。.NET 还包含了其它的内置事件数据类,如:SerialDataReceivedEventArgs 。它遵循以 EventArgs 结尾的命名方式。我们可以通过继承 EventArgs 来自定义事件数据。

public class ProcessCompeletedEventArgs : EventArgs
{
    public bool IsCompeleted { get; set; }

    /// <summary>
    /// 完成时间
    /// </summary>
    public DateTime CompeletedAt { get; set; }
}

public class EventSample
{
    //public delegate void Notify();

    public event EventHandler<ProcessCompeletedEventArgs> ProcessCompeleted;

    public void Start()
    {
        ("开始作业");
        //业务逻辑

        ProcessCompeletedEventArgs e = new ProcessCompeletedEventArgs
        {
            IsCompeleted = true,
            CompeletedAt =
        };

        //处理完毕后触发事件
        OnProcessCompeleted(e);
    }

    protected virtual void OnProcessCompeleted(ProcessCompeletedEventArgs e)
    {
        ProcessCompeleted?.Invoke(this, e);
    }
}
class Program
{
    static void Main(string[] args)
    {
        EventSample sample = new EventSample();
         += Sample_ProcessCompeleted;
        ();

        ();
    }

    private static void Sample_ProcessCompeleted(object sender, ProcessCompeletedEventArgs e)
    {
        ("IsCompeleted:{0},CompletedAt:{1}", , );

        ("处理完成");
    }
}

 总结

我们总结下事件的一些要点:

  1. 事件是委托的封装,以防止委托的用户重置委托及其调用列表,并且只允许在调用列表中添加或删除目标

  2. 一些常用的事件尽量使用内置 EventHandler 或 EventHandler<TEventArgs>

  3. 发布者类引发一个事件(发布者确定何时引发事件),订阅者类注册一个事件并提供事件的处理方法(订阅者确定采取什么动作来响应)

  4. 处理事件的方法必须与委托声明的保持一致

  5. 使用 += 运算符注册事件;使用 -= 运算符取消订阅

  6. 尽量使用 EventHandler<TEventArgs> 来传递事件数据,TEventArgs 是泛型保证类型安全

  7. 可以继承 EventArgs 来自定义事件数据

  8. 事件可以声明为静态、虚拟、密封和抽象

  9. 接口可以包含事件

  10. 一个事件可以有多个订阅者

  11. 如果有多个订阅者,则同步调用事件处理程序。

 最后,祝大家学习愉快!