C# 委托详解

文章目录

  • 前言
  • 一、委托是什么?
    • 1 官方关于委托的概述
    • 2 通俗解释
  • 二、如何使用委托
    • 1 使用委托详解
      • 1) 申明委托
      • 2) 编写委托对应的方法
      • 3) 实例化委托
      • 4) 使用委托
      • 5) 委托使用的综合案例
    • 2 使用多播委托详解(委托链)
      • 1) 关于多播委托
      • 2)案例讲解
      • 3)扩展
    • 3 【?.Invoke】小知识点
  • 三、为什么使用委托
    • 1 原因
    • 2 实例说明
      • 1) 需求:实现不同地域的人不同打招呼的方式
      • 2) 第一次需求变更,新增加一个地区人打招呼的方式
      • 3) 第二次需求变更,需要每次打招呼之前都先招手
      • 4) 方案对比
      • 5) 小结
  • 四、泛型委托
    • 1 自定义泛型委托
    • 2 Action
    • 3 Func
  • 总结


前言

该文主要讲解委托的使用,让我们在编程的时候更加得心应手的使用委托


一、委托是什么?

1 官方关于委托的概述

1 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。
2 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 委托可以链接在一起,一次性调用多个方法
3 你可以通过委托实例调用方法。
4 委托用于将方法作为参数传递给其他方法,可用于定义回调方法
5 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。
6 委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类

2 通俗解释

1 委托相当于是对具有相同返回类型和参数列表这一类方法进行了封装
2 由于委托本质是也是一个派生自Delegate类的类,其本质也是类,因此类可以申明在哪里,委托就可以申明在哪里

二、如何使用委托

1 使用委托详解

1) 申明委托

代码如下(示例):

        public delegate void ShowDelegate();

        public void Show()
        {
            Debug.WriteLine("test");
        }

        public delegate string ShowStringDelegate(string str);

        public string ShowString(string str)
        {
            return str + "test";
        }

由上可知委托和申明和方法类似,只不过多了一个关键字delegate 表示申明的是委托,少了方法体

2) 编写委托对应的方法

如1中的示例代码,如果委托是void并且没有参数列表的,那么对应的方法也要求是void并且没有参数列表的。

3) 实例化委托

方式1:通过new 创建委托实例,必须传入一个方法作为参数,否则会报错
因为委托内部的构造函数,需求传递一个方法作为参数
在这里插入图片描述

   #传入符合委托返回类型和参数列表的方法 可完成委托的实例化
   ShowDelegate showDelegate = new ShowDelegate(test.Show);

方式2:使用赋值的方式
这是C#2.0提供一个更简单的给委托创建实例的方式
同样要求:赋值的是 符合委托返回类型和参数列表的方法

   ShowDelegate showDelegate = test.Show;

方式3:匿名委托
要求匿名委托和当前的委托具有同样的返回类型和参数列表

   ShowDelegate showDelegate = delegate ()
   {
         Console.WriteLine("匿名委托");
   };

方式4:Lambda,其实就是赋予了一个匿名方法
该匿名方法同样需要:当前的委托具有同样的返回类型和参数列表

   ShowDelegate2 showDelegate2 = (string s, int v) => { Console.WriteLine($"{s}&{v}"); };
   showDelegate2("s",2);

4) 使用委托

方式1:直接调用委托的变量 如showDelegate2("s",2);
方式2:invoke(),如showDelegate2.Invoke("s",2);
【BeginInvoke 和EndInvoke的使用方式,待后续完善】

5) 委托使用的综合案例

综合案例如下(增强理解):

    //【1】申明委托
    public class DelegateTest
    {
        // 1.无参数无返回值委托
        public delegate void NoReturnNoPara();

        // 2.有参数无返回值委托
        public delegate void NoReturnWithPara(int x, int y);

        // 3.无参数有返回值的委托
        public delegate int WithReturnNoPara();

        // 4.带参数带返回值的委托
        public delegate int WithReturnWithPara(out int x, out int y);
    }
 class Program
    {
        //【2】定义委托相关的方法
        private static void NoReturnNoParaMethod()
        {
            Console.WriteLine("无参数,无返回值的方法");
        }

        private static void NoReturnWithParaMethod(int s, int t)
        {
            Console.WriteLine("有参数,无返回值的方法");
        }

        static void Main(string[] args)
        {
            //【3】实例化委托
            //使用new 实例
            DelegateTest.NoReturnNoPara noReturnNoPara= new DelegateTest.NoReturnNoPara(NoReturnNoParaMethod);

            //使用赋值的方式实例
            DelegateTest.NoReturnWithPara noReturnWithPara = NoReturnWithParaMethod;

            //使用匿名委托实例
            DelegateTest.WithReturnNoPara withReturnNoPara = delegate() 
            {
                Console.WriteLine("无参数,有返回值的方法");
                return default(int);
            };

            //使用lambda 匿名方法实例
            DelegateTest.WithReturnWithPara WithReturnWithPara = (out int x, out int y) =>
            {
                x = 1;
                y = 2;
                Console.WriteLine("有参数,有返回值的方法");
                return x + y;
            };

            //【4】调用委托
            //使用委托变量调用
            noReturnNoPara();

            //使用invoke调用
            //【Invoke】执行方法,如果委托定义没有参数,则invoke也没有参数,委托没有返回值,则invoke也没有返回值
            noReturnNoPara.Invoke();

            int result= withReturnNoPara.Invoke();//调用有返回值,无参数的委托

            int x1, y1;
            int result2 = WithReturnWithPara.Invoke(out x1,out y1);//调用有返回值,有参数的委托

            //使用BeginInvoke
            //【BeginInvoke】开启一个线程去执行委托,NetCore不支持,NetFamework支持  NetCore有更好的多线程功能来支持实现类似功能
            noReturnWithPara.BeginInvoke(1,2,null,null);
            //【EndInvoke等待BeginInvoke方法执行完成后再执行EndInvoke后面的代码】
            //noReturnWithPara.EndInvoke();
            Console.ReadLine();
        }
    }

2 使用多播委托详解(委托链)

1) 关于多播委托

(1)委托都是继承自MulticastDelegate(多播委托),定义的所有的委托都是多播委托
(2)可以通过+=把多个方法添加到这个委托中,形成一个方法的执行链,执行委托的时候,按照添加方法的顺序,依次去执行方法,
(3)可以通过-=把加入到委托中的方法注销
(3)action.BeginInvoke();会开启一个新的线程 去执行委托,注册有多个方法的委托,不能使用BeginInvoke
(4)注册有多个方法的委托想要开启新线程去执行委托,可以通过action.GetInvocationList()获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke

2)案例讲解

案例:小明让小张帮忙买水

(1) 先定义一个买东西的类

    public class Zhang
    {
        public delegate void BuySomethingDelegate();

        public void BuyWater()
        {
            Console.WriteLine("买水!");
        }
        public void BuyKFC()
        {
            Console.WriteLine("买肯德基");
        }
        public void BuyHotDog()
        {
            Console.WriteLine("买热狗");
        }
    }

(2) 小张帮小明完成了买水的操作

	Zhang z = new Zhang();
    BuySomethingDelegate bsd = new BuySomethingDelegate(z.BuyWater);
    bsd.Invoke();

(3) 小明突然想吃东西,又让小张顺路带个热狗和肯德基

    Zhang z = new Zhang ();
    BuySomethingDelegate bsd = new BuySomethingDelegate(z.BuyWater);
    bsd += z.BuyHotDog;
    bsd += z.BuyKFC;
    bsd.Invoke(); 

(4)还没付钱,小明怕吃不下又让小明把热狗退了

	Zhang z= new Zhang ();
	BuySomethingDelegate bsd = new BuySomethingDelegate(z.BuyWater);
	bsd += z.BuyHotDog;
	bsd += z.BuyKFC;
	bsd -= z.BuyHotDog;
	bsd.Invoke();

3)扩展

(1)使用多播委托的时候可能会遇到一个问题,就是委托链的第一个方法报错了,导致后面的注册的方法都无法调用
解决办法:使用GetInvocationList 按照调用顺序返回此多播委托的调用列表

   //获取委托链上的调用列表,返回的是委托数组
   Delegate[] delegateArr= bsd.GetInvocationList();
   foreach (BuySomethingDelegate item in delegateArr)
   {
         try
         {                    
             item.Invoke();
         }
         catch (Exception)
         {
              Console.WriteLine($"{item.Method.Name}方法报错了!");
          }
    }

(2)当我们的委托链上,注册的都是具有返回值的方法的时候,那么如何取出里面返回值,
如果调用委托,那么只会返回最后一个注册的方法的返回值。

   #如果BuySomethingDelegate 是有返回值的委托,
   #那么只需要在上述案例代码中,加上int result=item.Invoke()即可
   Delegate[] delegateArr= bsd.GetInvocationList();
   foreach (BuySomethingDelegate item in delegateArr)
   {
         try
         {                    
            int result= item.Invoke();
         }
         catch (Exception)
         {
              Console.WriteLine($"{item.Method.Name}方法报错了!");
          }
    }

3 【?.Invoke】小知识点

【?.Invoke】在使用时的意思就是:判断调用的委托是否为空,为空则不会执行,不为空才会执行
如:

    public class InvokeTest
    {
        public delegate void Del();

        public void Show()
        {
            Console.WriteLine("show");
        }
        public void Test()
        {
            Del del = Show;
            #这里为了演示在这里注销了方法,平常在编写代码的过程中,可能在其他类中做了此类操作
            del -= Show;
            #由于疏忽可能不知道,如果在使用委托的时候用del.Invoke()就会报错
            #而使用?.则会帮你做下判断
            del?.Invoke();
        }
    }

三、为什么使用委托

1 原因

1 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。
2 委托可以实现代码的重用,逻辑解耦,在方便代码维护的同时,也可提升程序的可扩展性

2 实例说明

1) 需求:实现不同地域的人不同打招呼的方式

代码如下(示例):
公共代码:定义了不同地方区域的枚举以及人拥有的基本信息父类

    public enum AreaType
    {
        BeiJing,
        ShangHai,
        Guangzhou,
    }

    public abstract class PeopleBase
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public AreaType Type { get; set; }
    }

方案1:使用Switch

    public class People: PeopleBase
    {
        public void SayHi()
        {
            switch (Type)
            {
                case AreaType.BeiJing: Console.WriteLine("吃了吗?您嘞!"); break;
                case AreaType.ShangHai: Console.WriteLine("侬好!");  break;
                case AreaType.Guangzhou: Console.WriteLine("雷猴啊"); break;
                default: Console.WriteLine("不在指定范围内");break;
            }
        }
    }

调用SayHi方法,实现打招呼

	People people = new People() { Id = 1, Name = "北京人张某", Type = AreaType.BeiJing };
    people.SayHi();

方案2:为每个不同地区的人分别定义一个方法

    public class People1 : PeopleBase
    {
        public void BeiJingSayHi()
        {
            Console.WriteLine("吃了吗?您嘞!");
        }
        public void ShangHaiSayHi()
        {
            Console.WriteLine("侬好!");
        }
        public void GuangzhouSayHi()
        {
            Console.WriteLine("雷猴啊");
        }
    }

分别调用不同地区人员打招呼的方法,实现打招呼

    People1 people = new People1() { Id = 1, Name = "北京人张某", Type = AreaType.BeiJing };
    people.BeiJingSayHi();

方案3:定义委托

    public class People2 : PeopleBase
    {
        public delegate void SayHiDelegate();
        public void BeiJingSayHi()
        {
            Console.WriteLine("吃了吗?您嘞!");
        }
        public void ShangHaiSayHi()
        {
            Console.WriteLine("侬好!");
        }
        public void GuangzhouSayHi()
        {
            Console.WriteLine("雷猴啊");
        }

        public void SayHi(SayHiDelegate sayHiDelegate)
        {
            sayHiDelegate.Invoke();//执行传入委托中方法
        }
    }

调用委托,将打招呼的方法传进去,实现打招呼

    People2 people = new People2() { Id = 1, Name = "北京人张某", Type = AreaType.BeiJing };
    people.SayHi(people.BeiJingSayHi);

2) 第一次需求变更,新增加一个地区人打招呼的方式

  • 方案1:区域枚举需要变动,SayHi方法需要新增代码
  • 方案2:新增一个独立的方法
  • 方案3:新增一个独立的方法

3) 第二次需求变更,需要每次打招呼之前都先招手

  • 方案1:SayHi方法需要新增一行代码
  • 方案2:每个方法都需要新增招手的代码
  • 方案3:只需要在SayHi方法中新增一行代码

4) 方案对比

从以上三个方案对比下来,发现方案3,无论是代码的重用,还是代码的稳定性以及扩展性方面都是方案1和方案2 无法拥有的。

5) 小结

由以上三个方案可知,我们为什么需要使用委托,以上案例还是业务比较单例的小案例,在一些业务比较复杂的场景下就更需要运行委托来实现代码的重用,业务的解耦,保持代码的稳定性以及扩展性。

四、泛型委托

1 自定义泛型委托

    //【1】定义了具有两个泛型参数类型的委托
    public delegate void CustomDelegate<T, V>(T t, V v);

    public class TestCustomDelegate
    {
        //【2】申明委托变量,这时需指明数据类型
        CustomDelegate<string, string> customDelegate;
        //【3】编写与委托对应的方法
        public void TestMethod(string a,string b)
        {
            Console.WriteLine($"{a}拼接{b}");
        }

        public void Result()
        {
            //【4】实例化
            customDelegate = TestMethod;
            customDelegate = new CustomDelegate<string, string>(TestMethod);
            customDelegate = delegate (string a1, string b1) { Console.WriteLine("delegate匿名方法也可以"); };
            customDelegate = (string a2, string b2) => { Console.WriteLine("使用lambda匿名方法"); };
            //【5】执行委托
            customDelegate("test1","test2");
            customDelegate.Invoke("test1", "test2");
        }
    }

由上可知,泛型委托和正常委托没什么两样,不过是有了泛型参数类型,泛型委托极大的提高了代码的扩展性。

由于单独定义委托和事件,比较繁琐,而且比较冗余,因此C#2.0提供了Action 和Func两个泛型委托,不用单独申明,拿来就可以用。

2 Action

(1)Action 表示无参,无返回值的委托

(2)Action 表示有参,无返回值的泛型委托,最多可入参16个

(3)使用Action 就可以囊括所有无返回值委托,可以说Action事对无返回值委托的进一步包装

#正常申明无返回,无参数的委托是public delegate void SayHi();
#但是Action本身就代表无返回,无参数,因此将void 和 ()一省略,只剩下Action action
#Action 为我们编程提供了方便,哪里需要就在哪里直接定义使用,不需要单独去定义
    public class ActionTest
    {
        public Action action;
        public void SendMsg()
        {
            Console.WriteLine("消息完成发送");
        }
        public void Test()
        {
           //实例化方式有四种,如下
            action = SendMsg;
            action = new Action(SendMsg);
            action = delegate(){ Console.WriteLine("delegate 匿名方法"); };
            action = () => { Console.WriteLine("lambda 匿名方法"); };
            //调用方式
			action();
            action.Invoke();
        }
    }

3 Func

(1)Func 表示有返回值的委托(必须有返回值)

(2)Func可以无参数,也可以有参数,最多16个参数,最后一个表示返回值且只有一个

(3)使用方法同delegate,Func不过是对所有的有返回值的数据进行了一个包装

    public class FuncTest
    {
        public Func<int,int> func;
        public int DoubleNumber(int number)
        {
            Console.WriteLine("计算完成");
            return number * 2;
        }
        public void Test()
        {
            //实例化有以下4种方式
            func = DoubleNumber;
            func = new Func<int, int>(DoubleNumber);
            func = delegate (int a) 
            {
                Console.WriteLine("delegate 匿名方法");
                return a * 2 ;
            };
            func = (int b) => 
            {
                Console.WriteLine("lambda 匿名方法");
                return b * 2;
            };

            //调用
            int result = func(10);
            int result2 = func.Invoke(10);
        }
    }

总结

以上就是今天要讲的内容,本文介绍了委托是什么,如何使用委托,以及多播委托和泛型委托,相信通过以上内容可以帮助大家对委托有进一步的理解,也希望大家对不足之处批评指正。


参考:委托详解

你可能感兴趣的:(C#,c#,.net,开发语言)