C#高级编程之委托

委托的引入

当我们要实现一个简单的对不同国籍的人SayHi的功能。

初想的方案:

         //那如果此时是不同国家的人打招呼。增加了美国跟日本
        /// <summary>
        ///SayHi/// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        public void SayHello(string name, int type)
        {
            if (type == 1)
            {
                Console.WriteLine($"{name}:晚上好");
            }
            else if (type == 2)
            {
                Console.WriteLine($"{name}:good evening");
            }
            else
            {
                Console.WriteLine($"{name}:こんばんは");
            }
        }

但是直接加参数Int,这里编译器编译时无法检测到不存在的type类型,不是很安全。于是选择另一种方式:不同type类型使用枚举进行区分。如下:

public enum PeopleType
    {
        Chinese = 1,
        American = 2,
        Japanese = 3
    }
        public void SayHello(string name, PeopleType peopleType)
        {
            Console.WriteLine("招手");
            switch (peopleType)
            {
                case PeopleType.Chinese:
                    Console.WriteLine($"{name}:晚上好");
                    break;
                case PeopleType.American:
                    Console.WriteLine($"{name}:good evening");
                    break;
                case PeopleType.Japanese:
                    Console.WriteLine($"{name}:こんばんは");
                    break;
                //....但是增加其他的类型时,case语句还是会增加,这个函数代码还是会修改,不符合开闭原则
                default:
                    throw new Exception("枚举不存在!");
            }
        }

采用枚举参数类型,可以实现编译时检查type类型不匹配的错误,但是其通过枚举判断,执行各自的业务逻辑,增加其他的类型时,case语句还是会增加,这个函数代码还是会修改,不符合开闭原则。如果业务升级,方法需要频繁的修改,方法很不稳定

这里我们传参数peopleType是为了判断之后执行不同的逻辑,既然如此,能不能直接把这个逻辑作为参数传过来?传递逻辑(功能),即传递方法?=》

那如何把一个方法包裹之后传递呢,==》委托

委托的定义

MSDN的定义:

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 你可以通过委托实例调用方法。

委托概述

委托具有以下属性:

  • 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。

  • 委托允许将方法作为参数进行传递。

  • 委托可用于定义回调方法。

  • 委托可以链接在一起;例如,可以对一个事件调用多个方法。

  • 方法不必与委托类型完全匹配,可以使用委托中的变体(协变和逆变)。

  • C# 2.0 版引入了匿名方法的概念,可以将代码块作为参数(而不是单独定义的方法)进行传递。 C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。 匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。 这些功能现在统称为匿名函数。

委托的使用

委托的使用一般按照如下步骤(以解决上述问题为例):

1.声明一个委托

    public delegate void SayHiDelegate(string name);

 

2.委托的实例化

    class Program
    {
        static void Main(string[] args)
        {
            //CustomDelegate customDelegate = new CustomDelegate();
            //customDelegate.Show();
            People people = new People();
            SayHiDelegate sayHiDelegate = new SayHiDelegate(people.SayChineseHello);
            people.SayHiShow("lilei", sayHiDelegate);
            SayHiDelegate sayHiDelegateAmerican = new SayHiDelegate(people.SayAmericanHello);
            people.SayHiShow("lilei", sayHiDelegateAmerican);
            SayHiDelegate sayHiDelegateJapen = new SayHiDelegate(people.SayJapaneseHello);
            people.SayHiShow("lilei", sayHiDelegateJapen);
        }
    }

3.调用方法

上面代码中已经调用了SayHiShow方法,该方法如下:

        ///如果要在打招呼之前,执行其他动作,比如“招手”
        ///面对当前情况:能不能有个好方法,既能增加公共逻辑方便,而且代码能够很稳定??可以使用委托
        ///自上而下比较:逻辑解耦,方便维护升级,代码稳定
        ///自下往上:去掉重复代码Console.WriteLine("招手"),代码复用
        public void SayHiShow(string name, SayHiDelegate method)
        {
            //Console.WriteLine("招手");
            method.Invoke(name);
        }

比如其他类型的委托执行:

public class CustomDelegate
    {
        public delegate void NoReturnNoParam();
        public delegate void NoReturnWithParam(int x, int y);
        public delegate int WithReturnNoParam();
    
NoReturnNoParam noReturnNoParam = new NoReturnNoParam(NoReturnNoParamMethod);
            noReturnNoParam.Invoke();//执行这个委托,就把当前传递给委托的这个方法执行一下。
            noReturnNoParam();//与noReturnNoParam.Invoke()一样

            NoReturnWithParam noReturnWithParam = new NoReturnWithParam(NoReturnWithParamMethod);
            noReturnWithParam.Invoke(1, 2);

            WithReturnNoParam withReturnNoParam = new WithReturnNoParam(WithReturnWithNoParamMethod);
            var result = withReturnNoParam.Invoke();

            WithReturnWithParam withReturnWithParam = new WithReturnWithParam(WithReturnWithParamMethod);
            int x = 1, y = 1;
            int result1 = withReturnWithParam(out x, ref y);

多播委托

多播委托是指在一个委托中注册多个方法,在注册方法时可以在委托中使用加号运算符或者减号运算符来实现添加或撤销方法。,我们自定义的任何一个委托都是多播委托,其继承至基类MulticastDelegate。

多播委托可以在实例化以后,通过+=添加当前委托参数返回值完全一致的多个方法;形成方法链

在调用invoke方法的时候,会依次按照+=调用方法。如下例所示:

 NoReturnNoParam method = new NoReturnNoParam(NoReturnNoParamMethod);
            method += DoNothingStatic;
            method += () => { Console.WriteLine("this is lamda"); };//lamda表达式在IL里面还是会生成不同的方法
                                                                   //method.Invoke();
                                                                   //多播委托:可以通过-=移除方法
            method -= DoNothingStatic;
            method -= () => { Console.WriteLine("this is lamda"); };//lamda表达式在IL里面还是会生成不同的方法
            method.Invoke();

结果如下:

 可以看到“this is lamda"”还是打印出来了,是因为增加+和移除-的两个委托(通过匿名方式传入),实际的方法名是不一样的,这点可以用IL看到(系统帮我们构造了这样的一个方法名,但两个方法的签名是一样的)。

所以其实际打印的是方法b__3_0();

但是多播委托不支持异步。如下用BeginInvoke异步执行委托。

 但是可以调用GetInvocationList方法来实现多播委托的异步调用。

public void TestMethodAsync()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("异步TestMethodAsync开始");
            for (int i = 0; i < 5; i++)
            {   // 模拟耗时操作
                Thread.Sleep(100);
                Console.WriteLine("TestMethodAsync:" + i.ToString());
            }
            sw.Stop();
            int threadId = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine($"线程ID为{threadId}");
            Console.WriteLine(string.Format("耗时{0}ms.", sw.ElapsedMilliseconds.ToString()));
        }
    NoReturnNoParam method = new NoReturnNoParam(TestMethodAsync);
            method += DoNothingStatic;
            method += () => { Console.WriteLine("this is lamda"); };//lamda表达式在IL里面还是会生成不同的方法
                                                                    //method.Invoke();
                                                                    //多播委托:可以通过-=移除方法
            method -= DoNothingStatic;
            method -= () => { Console.WriteLine("this is lamda"); };//lamda表达式在IL里面还是会生成不同的方法
                                                                    //method.Invoke();
                                                                    //method.BeginInvoke(null, null);//开启一个新的线程去执行,此处会报错,因为多播委托不允许异步。可以采用下面的方式
            foreach (NoReturnNoParam item in method.GetInvocationList())
            {
                item.BeginInvoke(null, null);
            }
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("do other things.." + i.ToString());
            }

结果如下:

 可以看到其的确是异步执行。后面章节会详细讲解BeginInvoke(涉及多线程、异步调用下的同步、回调函数)。

内置委托类型的引入

比如我们定义一个无参数无返回值的委托类型。

    public delegate void NoReturnNoParam();

与其匹配的方法签名如下:

    public void NoReturnNoParamMethod()
        {
            Console.WriteLine("这是一个无参数无返回值的方法。。。");
        }

系统有一些API的委托类型与NoReturnNoParam类似。比如启动一个现场:

new Thread(new ThreadStart(NoReturnNoParamMethod));

namespace System.Threading
{
    //
    // 摘要:
    //     Represents the method that executes on a System.Threading.Thread.
    public delegate void ThreadStart();
}

那按照同理性理解,这里ThreadStart,NoReturnNoParam都是//public delegate void ThreadStart();无参数无返回值的委托,为啥后面的语句会报错呢

 是因为委托是一个类,这两个不同类的实例肯定不一样的!!从IL中就可以看出:

WithReturnNoParam委托类在类外。

 MulticastDelegate多播委托类是一个abstract类。

 (在使用ILSpy时,对于.net framework框架下的程序可以反编译,但是.net core版本的代码无法编译。 )

那ThreadStart跟NoReturnNoParam委托,对于NoReturnNoParamMethod这是不是重复声明了这种无参数无返回值的委托?

故系统提供了Action Func两个委托,系统肯定是希望在以后的封装中能够直接使用这两个委托。

Action委托

msdn解释:封装一个方法,该方法不具有参数且不返回值。

public delegate void Action();

其可以是非泛型,也可以是泛型委托。对于泛型委托,可以有参数(参数可有可无),最多可以有16个泛型参数。

Func委托

msdn 解释:Func委托:封装一个方法,该方法不具有参数,且返回由 TResult 参数指定的类型的值。

public delegate TResult Func<out TResult>();

Func是系统给我们提供 一个泛型委托,是一个必须有返回值,最后一个参数列表为返回值。可以有参数的委托,最多可以有16个泛型参数

predicate委托

msdn解释:表示定义一组条件并确定指定对象是否满足那些条件的方法。其为一个泛型委托,T支持逆变。

public delegate bool Predicate<in T>(T obj);

总结

委托是一种特殊的引用类型,该类型表示对具有特定参数列表和返回类型的方法(该方法签名不一定要跟委托一模一样,因为其支持变体)的引用(类比C++中函数指针),在C# IL中其本质上是一个继承自MultiCastDelegate基类的子类。

 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。,可以通过+或者-实现多播委托。

同时可以通过委托实例同步或者异步调用方法。

 
posted @ 2020-11-23 16:40  liweiyin  阅读(153)  评论(0编辑  收藏  举报