2.7C#函数成员之事件(Event)

1.事件委托声明

.NET 类库中的所有事件均基于 EventHandler 委托、EventHandler<TEventArgs> 委托,一般不建议自定义委托。

//未生成事件数据的委托
public delegate void EventHandler(object? sender, EventArgs e);

//生成事件数据的委托
public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);

//自定义委托
public delegate void CustomEventHandler(object sender, CustomEventArgs args);

第一个参数为object类型,表示能够引发事件的类的实例。

第二个参数为从 EventArgs 基类派生的子类型的实例。如果事件不生成事件数据,第二个参数就是 EventArgs.Empty 字段的值。如果事件生成事件数据,则提供保存事件数据所需的任何字段或属性。

2.事件声明

事件的声明方式与字段相似,区别是事件声明包括 event 关键字,且类型必须是委托类型。

event 委托类型 事件名;
//EventHandler委托类型
public event EventHandler RaiseCustomEvent;

//EventHandler<CustomEventArgs>委托类型
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

//自定义委托类型
public event CustomEventHandler RaiseCustomEvent;

3.订阅事件和取消订阅

通过使用 += 运算符订阅事件,使用 -= 运算符取消订阅。

pub.RaiseCustomEvent += HandleCustomEvent;
pub.RaiseCustomEvent -= HandleCustomEvent;

4.事件访问器

实际上,目标方法是通过事件访问器添加到委托的调用列表中,事件访问器类似于属性访问器,不同之处在于事件访问器命名为 add 和 remove。 在大多数情况下,无需提供自定义事件访问器。 如果代码中没有提供自定义事件访问器,编译器将自动添加它们。 但在某些情况下,编译器不自动添加它们,可能需要我们自己提供自定义行为,比如:类继承自两个或多个接口,且每个接口都具有相同名称的事件。 在这种情况下,我们必须为至少其中一个事件提供显式接口实现。为事件编写显式接口实现时,还必须编写 add 和 remove 事件访问器。

event EventHandler IDrawingObject.OnDraw
{
    add
    {
        lock (objectLock)
        {
            PreDrawEvent += value;
        }
    }
    remove
    {
        lock (objectLock)
        {
            PreDrawEvent -= value;
        }
    }
}

5.完整示例

using System;

namespace DotNetEvents
{
    // Define a class to hold custom event info
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; set; }
    }

    // Class that publishes an event
    class Publisher
    {
        // Declare the event using EventHandler<T>
        public event EventHandler<CustomEventArgs> RaiseCustomEvent;

        public void DoSomething()
        {
            // Write some code that does something useful here
            // then raise the event. You can also raise an event
            // before you execute a block of code.
            OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
        }

        // Wrap event invocations inside a protected virtual method
        // to allow derived classes to override the event invocation behavior
        protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<CustomEventArgs> raiseEvent = RaiseCustomEvent;

            // Event will be null if there are no subscribers
            if (raiseEvent != null)
            {
                // Format the string to send inside the CustomEventArgs parameter
                e.Message += $" at {DateTime.Now}";

                // Call to raise the event.
                raiseEvent(this, e);
            }
        }
    }

    //Class that subscribes to an event
    class Subscriber
    {
        private readonly string _id;

        public Subscriber(string id, Publisher pub)
        {
            _id = id;

            // Subscribe to the event
            pub.RaiseCustomEvent += HandleCustomEvent;
        }

        // Define what actions to take when the event is raised.
        void HandleCustomEvent(object sender, CustomEventArgs e)
        {
            Console.WriteLine($"{_id} received this message: {e.Message}");
        }
    }

    class Program
    {
        static void Main()
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            var sub2 = new Subscriber("sub2", pub);

            // Call the method that raises the event.
            pub.DoSomething();

            // Keep the console window open
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }
    }
}

6.修饰符

事件支持 abstract、new、override、sealed、static、virtual 修饰符。

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/csharp/csharplang/8221.html

(0)
上一篇 2021年2月6日 11:46
下一篇 2021年2月7日 17:15

相关推荐

  • 3.12C#类型之记录结构(record struct)和记录类(record class)

    1.记录声明 声明记录结构(值类型)或记录类(引用类型)的语法与声明结构或类的语法相似,可使用位置参数或属性语法声明记录类型。 记录类型可以是不可变类型,也可以是可变类型,但记录类型的发明主要是为创建自定义不可变类型提供简洁的语法。 编译器会为记录的每个位置参数自动创建public自动实现的属性,如果生成的自动实现的属…

    C#语言教程 2021年3月12日
    05540
  • 3.9C#引用类型之特性(Attribute)

    1.特性声明 所有特性类都直接或间接派生自 System.Attribute 基类。 以下示例声明了 HelpAttribute 特性。 2.调用特性 调用特性时,通过用方括号 ([]) 将特性名称以及实参括起来,并置于应用该特性的实体的声明上方。可以将多个特性附加到实体的声明上。对于给定实体,一些…

    C#语言教程 2021年3月9日
    02080
  • 3.11C#类型之元组(ValueTuple)值类型和元组(Tuple)引用类型

    1.元组声明和初始化 元组提供了简洁的语法来将多个数据元素组合在一个轻型数据结构里。 通过声明 ( 和 ) 之间的成员的类型和名称来实例化元组。 2.访问成员 3.析构(Deconstruct)元组 析构的过程恰恰与元组的声明和初始化相反,析构是将已存在的元组析构成多个单独的变量…

    C#语言教程 2021年3月11日
    05970

发表回复

登录后才能评论