C# 从1到Core--委托与事件 一、C#1.0 从委托开始 二、C#1.0 引入事件 三、随着C#版本改变

2020-07-01

一、C#1.0 从委托开始

1. 基本方式

  什么是委托,就不说概念了,用例子说话。

  某HR说他需要招聘一个6年 .NET5 研发经验的“高级”工程师,他想找人(委托)别人把这条招聘消息发出去。这样的HR很多,所以大家定义了一个通用的发消息规则:

public delegate string SendDelegate(string message);

  这就像一个接口的方法,没有实际的实现代码,只是定义了这个方法有一个string的参数和返回值。所有想发招聘消息的HR只要遵守这样的规则即可。

委托本质上是一个类,所以它可以被定义在其他类的内部或外部,根据实际引用关系考虑即可。本例单独定义在外部。

为HR定义了一个名为HR的类:

public class HR
{
    public SendDelegate sendDelegate;
    public void SendMessage(string msg)
    {
        sendDelegate(msg);
    }
}

  HR有一个SendDelegate类型的成员,当它需要发送消息(SendMessage)的时候,只需要调用这个sendDelegate方法即可。而不需要实现这个方法,也不需要关心这个方法是怎么实现的。

当知道这个HR需要发送消息的时候,猎头张三接了这个帮忙招人的工作。猎头的类为Sender,他有一个用于发送消息的方法Send,该方法恰好符合众人定义的名为SendDelegate的发消息规则。这有点像实现了一个接口方法,但这里不要求方法名一致,只是要求方法的签名一致。

public class Sender
{
    public Sender(string name)
    {
        this.senderName = name;
    }
    private readonly string senderName;
    public string Send(string message)
    {
        string serialNumber = Guid.NewGuid().ToString();
        Console.WriteLine(senderName + " sending....");
        Thread.Sleep(2000);
        Console.WriteLine("Sender: " + senderName + " , Content: " + message + ", Serial Number: "  + serialNumber);
        return serialNumber;
    }
}

猎头帮助HR招人的逻辑如下:

public void Test()
{
    //一个HR
    HR hr = new HR();
    //猎头张三来监听,听到HR发什么消息后立刻传播出去
    Sender senderZS = new Sender("张三");
    hr.sendDelegate = senderZS.Send;    //HR递交消息
    hr.SendMessage("Hello World");
}

猎头将自己的发消息方法“赋值”给了HR的SendDelegate方法,为什么可以“赋值”? 因为二者都遵守SendDelegate规则。 就像A和B两个变量都是int类型的时候,A可以赋值给B一样。

这就是一个简单的委托过程,HR将招人的工作委托给了猎头,自己不用去做招人的工作。

但经常一个招聘工作经常会有多个猎头接单,那就有了多播委托。

2. 多播委托

 看一下下面的代码:

public void Test()
{
    //一个HR
    HR hr = new HR();
    //猎头张三来监听,听到HR发什么消息后立刻传播出去
    Sender senderZS = new Sender("张三");
    hr.sendDelegate = senderZS.Send;
    //快嘴李四也来了
    Sender senderLS = new Sender("李四");
    hr.sendDelegate += senderLS.Send;
    //HR递交消息
    hr.SendMessage("Hello World");
}

与之前的代码改变不大, 只是添加了李四的方法绑定,这样HR发消息的时候,张三和李四都会发出招人的消息。

这里要注意李四绑定方法的时候,用的是+=而不是=,就像拼接字符串一样,是拼接而不是赋值,否则会覆盖掉之前张三的方法绑定。

对于第一个绑定的张三,可以用=号也可以用+=(记得之前好像第一个必须用=,实验了一下现在二者皆可)。

这同时也暴露了一些问题:

  • 如果后面的猎头接单的时候不小心(故意)用了=号, 那么最终前面的人的绑定都没有了,那么他将独占这个HR客户,HR发出的消息只有他能收到。

  • 可以偷偷的调用猎头的hr.sendDelegate

    public void Test()
    {
        //一个HR
        HR hr = new HR();
        //大嘴张三来监听,听到HR发什么消息后立刻传播出去
        Sender senderZS = new Sender("张三");
        //hr.sendDelegate -= senderZS.Send; //即使未进行过+=  直接调用-=,也不会报错
        hr.sendDelegate += senderZS.Send;
        //快嘴李四也来了
        Sender senderLS = new Sender("李四");
        hr.sendDelegate += senderLS.Send;
        //移除
        //hr.sendDelegate -= senderZS.Send;
        //风险:注意上面用的符号是+=和-=   如果使用=,则是赋值操作,
        //例如下面的语句会覆盖掉之前所有的绑定
        //hr.sendDelegate = senderWW.Send;
        //HR递交消息
        hr.SendMessage("Hello World");
        //风险:可以偷偷的以HR的名义偷偷的发了一条消息    sendDelegate应该只能由HR调用   
        hr.sendDelegate("偷偷的发一条");
    }
    

    3. 通过方法避免风险

      很自然想到采用类似Get和Set的方式避免上面的问题。既然委托可以像变量一样赋值,那么也可以通过参数来传值,将一个方法作为参数传递。

    public class HRWithAddRemove
        {
            private SendDelegate sendDelegate;
            public void AddDelegate(SendDelegate sendDelegate)
            {
                this.sendDelegate += sendDelegate; //如果需要限制最多绑定一个,此处可以用=号
            }
            public void RomoveDelegate(SendDelegate sendDelegate)
            {
                this.sendDelegate -= sendDelegate;
            }
            public void SendMessage(string msg)
            {
                sendDelegate(msg);
            }
        }
    

    经过改造后的HR,SendDelegate方法被设置为了private,之后只能通过Add和Remove的方法进行方法绑定。

    4.模拟多播委托机制

    通过上面委托的表现来看,委托就像是保存了一个相同方法名的集合 List ,可以向集合中添加或移除方法,当调用这个委托的时候,会逐一调用该集合中的各个方法。

    例如下面的代码( 注意这里假设SendDelegate只对应一个方法 ):

    public class HR1
    {
        public void Delegate(SendDelegate sendDelegate)
        {
            sendDelegateList = new List { sendDelegate }; //对应=
        }
        public void AddDelegate(SendDelegate sendDelegate)
        {
            sendDelegateList.Add(sendDelegate); //对应+=
        }
        public void RomoveDelegate(SendDelegate sendDelegate)
        {
            sendDelegateList.Remove(sendDelegate);//对应-=
        }
        public List sendDelegateList;
        public void SendMessage(string msg)
        {
            foreach (var item in sendDelegateList)
            {