委托

1、什么是委托

  1.1 生活中的委托

  委托,汉语中的解释是把事情托付给别人或别的机构(办理)。生活中的委托挺多的,比如幼儿园,你可以把孩子委托给幼儿园,让他们帮你看管孩子;比如律师,你可以把案件委托给律师,让他帮你打官司;比如快递员,你可以把快递委托他们,让他们帮你送;这些都有一个共同点就是你可以告诉他们你要做什么,让他们去帮你做。下面我们说一下程序中的委托。

  1.2 委托的概念

  委托(Delegate)是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,委托能够拥有一个签名(signature),并且它"只能持有与它的签名相匹配的方法的引用"。简单来说委托是一个类型,它能接收一个与它签名相同的方法。

2、委托详解

  2.1 语法

  定义委托需要使用delegate关键字,还需要返回值类型、参数列表。

        delegate 返回值 委托名(可选的参数列表);

  2.2 详解

  上面我们介绍了委托的语法,下面我们来自己定义一个委托。

        // 无参数无返回值
        delegate void NoReturnNoParam();

  看完有的小伙伴可能会有疑问了,这跟定义一个方法差不多啊,除了没有delegate关键字和方法体,那为什么说委托是一个类。单这么看可能看不出来啥,下面我们在反编译工具里面看一下我们上面定义的几个委托生成的IL语言。

  通过上图我们看到了编译器实际上是为我们生成了一个NoReturnNoParam类,继承自System.MulticastDelegate类。它里面包含一个构造函数和BeginInvoke、EndInvoke、Invoke三个方法。现在就很明白了,虽然我们定义的时候很简单,其实编译器都给我们补全了。

3、委托的使用

  上面我们知道了,委托就是一个类,那我们猜测一下是不是类怎么用,委托就可以怎么用?

  3.1 自定义委托的使用

  首先我们定义一个委托,再定义一个与之方法签名一致的方法。

        // 无参数无返回值委托
        delegate void NoReturnNoParam();

        /// <summary>
        /// 无参数无返回值方法
        /// </summary>
        public void NoReturnNoParamMethod()
        {
            Console.WriteLine("This is NoReturnNoParam Method .");
        }

  类使用的时候需要先实例化,那么委托也需要先实例化,但委托实例化的时候必须传递与方法签名相同的方法。

        NoReturnNoParam noReturnNoParam = new NoReturnNoParam(new Example().NoReturnNoParamMethod);
        // 也可以去掉new,直接写方法
        NoReturnNoParam noReturnNoParam1 = new Example().NoReturnNoParamMethod;

  类实例化完了可以调用类中的实例方法,那么委托也一样。上面我们知道了委托类中包含三个方法,下面我们调用一下Invoke方法,Invoke表示执行委托,也就是执行委托传递的方法。调用的时候Invoke可以省略。

        // 执行委托
        noReturnNoParam.Invoke();
        // 省略Invoke版,与上面等价
        noReturnNoParam();

  3.2 异步委托

  通过BeginInvoke和EndInvoke可以实现异步编程。

    3.2.1 BeginInvoke

    BeginInvoke表示开始调用。执行该方法时,会开启新线程,所以不会阻塞主线程。 对于无参数委托,BeginInvoke接收两个参数,AsyncCallback表示异步委托调用结束后的回调方法,如没有回调方法则传null;第二个参数是 object类型的参数,作为额外参数,传入异步委托中,可以是任何值,一般用来标识委托。对于有参数的委托,调用BeginInvoke时需先传入方法参数,再传AsyncCallback类型和object类型参数。该方法还返回一个IAsyncResult对象。

    3.2.2 EndInvoke

    EndInvoke表示结束调用。该方法必须接受一个IAsyncResult对象。要注意的是,一旦调用EndEnvoke方法,就会进入阻塞模式,等待委托执行完毕,当然如果委托已经执行完毕,就可以立马取得结果。

    3.2.3 使用

    异步调用:

            noReturnNoParam.BeginInvoke(null, null);

    异步回调:

        /// <summary>
        /// 异步回调方法
        /// </summary>
        /// <param name="ar"></param>
        private void SendCallBack(IAsyncResult result)
        {
            object obj = result.AsyncState;
            Console.WriteLine($"执行了回调方法,AsyncState:{obj}");

            // to do
        }

        AsyncCallback asyncCallBack = new AsyncCallback(this.SendCallBack);
        IAsyncResult asyncResult = noReturnNoParam.BeginInvoke(asyncCallBack, "obj");
        noReturnNoParam.EndInvoke(asyncResult);

4、 内置委托

  C#中内置了两个委托Action和Func,当然既然是委托,那用起来跟我们上面自定义的委托并没有区别。

  4.1 Action

    Action是一个无返回值的委托,它可以接收0-16个泛型的参数。

  4.2 Func

    Func是一个有返回值的委托,它可以接收0-16个泛型参数并返回一个泛型值。

  4.3 用法示例

            // Action
            Action action1 = new DelegateExample().NoReturnNoParamMethod;
            action1.Invoke();
            Action<string> action2 = new DelegateExample().NoReturnWithParamMethod;
            action2.Invoke("string Param");

            // Func
            Func<int> func1 = new DelegateExample().WithReturnNoParamMethod;
            int result1 = func1.Invoke();
            Func<string, int> func2 = new DelegateExample().WithReturnWithParamMethod;
            int result2 = func2.Invoke("string Param");

5、多播委托

  多播委托(MulticastDelegate)的初始化可以像普通委托一样,传入一个签名相同的实例方法。同时,多播委托重载了 += 运算符和 -= 运算符,用来向其调用列表中添加或者删除方法。调用多播委托时,方法将按照添加的顺序被依次调用。

  5.1 添加方法

    下面我们给多播委托添加三个方法,添加完后可通过GetInvocationList方法获取到方法列表,然后依次执行。

        // 多播委托
        var delegateExample = new DelegateExample();

        // 添加实例方法1
        MulticastDelegateTest mdTest = new DelegateExample().NoReturnNoParamMethod;
        // 添加实例方法2
        mdTest += delegateExample.NoReturnNoParamMethod_1;
        // 添加静态方法
        mdTest += StaticNoReturnNoParamMethod;

        foreach (MulticastDelegateTest item in mdTest.GetInvocationList())
        {
            item.Invoke();
        }

  5.2 删除方法

    既然可以添加,那肯定可以删除,我们直接把上面的+=改为-=,大家可以猜一下下面代码删除了几个方法。

        // 删除实例方法1
        mdTest -= new DelegateExample().NoReturnNoParamMethod;
        // 删除实例方法2
        mdTest -= delegateExample.NoReturnNoParamMethod_1;
        // 删除静态方法
        mdTest -= StaticNoReturnNoParamMethod;

        foreach (MulticastDelegateTest item in mdTest.GetInvocationList())
        {
            item.Invoke();
        }

    事实证明只删除了实例方法2和静态方法。因为实例方法1添加和删除的方法对应的实例,在内存中的地址不同,所以删不掉,这点需要注意一下。

  5.3 需要注意的点

    1. 多播委托的返回类型最好定义为void,有返回值时只会返回最后一个方法的返回值。
    2. 删除实例方法时,要删除的方法需要与添加的方法属于同一实例。

6、事件

  6.1 声明事件

    1. 声明该事件的委托类型
    2. 基于委托定义事件
        // 在类的内部声明事件,首先必须声明该事件的委托类型。
        public delegate void NoReturnNoParam();
        // 基于上面的委托定义事件,使用 event 关键字 
        public event NoReturnNoParam eventRun;

   6.2 事件与委托的区别

    1. 事件是一个委托实例。
    2. 事件不为包容类外部的对象提供对赋值运算符,也就是事件只能出现在+=、-=左边。
    3. 事件确保只有包容类才能触发一个事件通知。

7、总结

  本节列出委托和事件的用法,涉及代码可至GitHub下载。

 

posted @ 2019-03-14 10:43  gaozejie  阅读(465)  评论(0编辑  收藏  举报