Effective C#笔记 - 用null条件运算符调用事件处理程序

使用事件的常用步骤:

  1. 定义事件参数;
  2. 事件源类型中声明事件;
  3. 注册处理事件的方法,即监听;
  4. 再需要是,触发事件。
public class CustomEventArgs: System.EventArgs
{
    public string EventData { get; private set; }

    public CustomEventArgs(string eventData)
    {
        EventData = eventData;
    }
}

public class CustomEventSource
{
    public System.EventHandler CustomEvent;

    public void DoWork()
    {
        // do other work

        // 最初的写法
        if(null != CustomEvent)
        {
            CustomEvent(this, new CustomEventArgs("event argument"));
        }

        // 有经验的同事会告诉我们,需要这样写
        //var handler = CustomEvent;
        //if(null != handler)
        //{
        //    handler(this, new CustomEventArgs("event argument"));
        //}
    }
}

public class EventUsage
{
    private CustomEventSource handlerDemo;

    public void Init()
    {
        handlerDemo = new CustomEventSource();
        handlerDemo.CustomEvent += HandleCustomEvent;
    }

    private void HandleCustomEvent(object sender, CustomEventArgs e)
    {
        Console.WriteLine($"event: {e.EventData}");
    }

    public void UnInit()
    {
        if(null != handlerDemo)
        {
            handlerDemo.CustomEvent -= HandleCustomEvent;
        }
    }
}

其实那会不是很理解为什么这么写,然后他们就告诉我,这样可以避免多线程使用时带来的bug,而且不好查。就是当前程序执行完if语句之后,会被另一个线程打断,并且另一个线程解除事件监听,也就是解除了事件订阅,这样事件处理程序成了null,这样就引发了空引用的异常。所以会先赋值一个,用赋值后的内容去处理事件。原理是:该赋值会对赋值符号右边的内容做浅拷贝(创建新引用,并令其指向原来的事件处理程序),当另一个线程注销事件处理程序的时候,只会修改类实例中的handlerDemo字段,而不会把该处理程序同时从局部变量handler里面移走,这样,handler中还保存着早前执行浅拷贝时所记录的事件订阅者,这样就不会出错了。

这样的处理是线程安全的,但是会复杂一些,C#引入的null条件运算符,可以解决这个问题,即:

CustomEvent?.Invoke(this, new CustomEventArgs("event argument"));

现在编译器也会智能提示我们这样修改,这样很方便,而且在语义上,与早期的if结构类似,但区别在于?.运算符左侧内容只会计算一次。现在我已经慢慢习惯这种使用方式了。

你可能感兴趣的:(Effective C#笔记 - 用null条件运算符调用事件处理程序)