C# 事件(Event)

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

在 C# 中,事件是封装的委托。 它依赖于委托。 事件发布者和订阅者都要遵循委托的签名。 下图说明了 C# 中的事件。

声明一个事件

一个事件可以分两步声明:

  • 声明一个委托 delegate。
  • 使用 event 关键字声明委托的变量。 以下示例显示了如何在发布者类中声明事件。
public class DemoPublisher
{
    // Action是系统自带的 delegate
    public event Action ProcessCompleted; // event 
}

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

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

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 定义受保护和虚方法。 受保护和virtual使派生类能够重写引发事件的逻辑。 但是,派生类应始终调用基类的 On 方法以确保注册的委托接收事件。

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

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

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。 通常,任何事件都应该包括两个参数:事件的来源和事件数据。 对所有不包含事件数据的事件使用 EventHandler 委托。 对包含要发送到处理程序的数据的事件使用 EventHandler 委托。

上面显示的示例可以使用 EventHandler 委托来改写,如下所示。

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。

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来定义事件。 当一个学生注册成功的时候,我们希望给它发送一个邮件,然后还有发送优惠券。等。这些都可以放在事件里面。

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,或 Action
  • 发布者类引发一个事件,订阅者类注册一个事件并提供事件处理的方法。
  • 触发事件的方法名称通常为 On
  • 处理事件的方法签名必须与委托签名匹配。
  • 使用 += 运算符注册事件。 使用 -= 运算符取消订阅。 不能使用 = 运算符。
  • 使用 EventHandler 传递事件数据或者Action传递数据。
  • 派生 EventArgs 基类以创建自定义事件数据类。
  • 事件可以声明为静态的、虚拟的、密封的和抽象的。
  • 接口可以将事件作为成员包含在内。
  • 如果有多个订阅者,则会同步调用事件处理程序,这个会卡住其它的事件方法。
下一篇:C# 协变逆变
最近更新的
...