C#委托详解

一、委托是什么?

1 官方关于委托的概述

1 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。

2 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 委托可以链接在一起,一次性调用多个方法

3 你可以通过委托实例调用方法。

4 委托用于将方法作为参数传递给其他方法,可用于定义回调方法

5 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。

6 委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类

2 通俗解释

1 委托相当于是对具有相同返回类型和参数列表这一类方法进行了封装

2 由于委托本质是也是一个派生自Delegate类的类,其本质也是类,因此类可以申明在哪里,委托就可以申明在哪里

3 委托是c/c++ 中函数指针的升级版!

二、如何使用委托

使用委托详解

委托是一种特殊的类型,它代表引用方法的对象。委托的定义与方法的签名类似,包括返回类型和参数列表。下面是一个简单的委托定义示例:

申明委托

代码如下(示例):

cs 复制代码
public delegate int BinaryOperation(int x, int y);
这个委托BinaryOperation定义了一个接受两个整数参数并返回一个整数的方法签名。
委托的声明与实例化

委托的声明类似于变量声明,我们需要指定委托类型和变量名。委托的实例化则是将具体的方法赋值给委托变量。以下是一个委托的声明和实例化的示例:

cs 复制代码
// 声明委托变量  
BinaryOperation add;  
  
// 实例化委托,将Add方法赋值给add变量  
add = new BinaryOperation(Add);  
  
// 调用委托,实际调用的是Add方法  
int result = add(2, 3);  
  
// Add方法的定义  
int Add(int a, int b)  
{  
    return a + b;  
}

在上面的代码中,我们首先声明了一个BinaryOperation类型的委托变量add,然后通过new关键字将Add方法实例化并赋值给add变量。最后,我们通过调用add变量来执行Add方法。

其实是将委托变量指向了ADD方法的地址

多播委托

(1)委托都是继承自MulticastDelegate(多播委托),定义的所有的委托都是多播委托

(2)可以通过+=把多个方法添加到这个委托中,形成一个方法的执行链,执行委托的时候,按照添加方法的顺序,依次去执行方法,

(3)可以通过-=把加入到委托中的方法注销

(3)action.BeginInvoke();会开启一个新的线程 去执行委托,注册有多个方法的委托,不能使用BeginInvoke

(4)注册有多个方法的委托想要开启新线程去执行委托,可以通过action.GetInvocationList()获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke

C#中的委托支持多播特性,即一个委托变量可以引用多个方法。当调用多播委托时,会按照委托链中的顺序依次调用所有引用的方法。以下是一个多播委托的示例:

cs 复制代码
// 声明并实例化多播委托  
BinaryOperation addAndMultiply = new BinaryOperation(Add);  
addAndMultiply += new BinaryOperation(Multiply);  //+=运算符可以添加其他方法到多播委托中
  
// 调用多播委托,先执行Add方法,再执行Multiply方法  
int combinedResult = addAndMultiply(2, 3); // 调用Add方法,返回5  
combinedResult = addAndMultiply(combinedResult, 2); // 调用Multiply方法,返回10  
  
// Multiply方法的定义  
int Multiply(int a, int b)  
{  
    return a * b;  
}

在这个例子中,我们首先创建了一个多播委托addAndMultiply,并初始化为引用Add方法。然后,通过+=运算符将Multiply方法也添加到多播委托链中。当我们调用addAndMultiply时,会首先执行Add方法,然后再执行Multiply方法。需要注意的是,多播委托的返回值通常是最后一个方法调用的返回值,除非特别设计委托链中的方法以某种方式组合结果。

使用多播委托的时候可能会遇到一个问题,就是委托链的第一个方法报错了,导致后面的注册的方法都无法调用

解决办法:使用GetInvocationList 按照调用顺序返回此多播委托的调用列表

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

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

如:

cs 复制代码
    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 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。

2 委托可以实现代码的重用,逻辑解耦,在方便代码维护的同时,也可提升程序的可扩展性

实例说明

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

代码如下(示例):

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

cs 复制代码
    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

cs 复制代码
    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方法,实现打招呼

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

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

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

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

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

方案3:定义委托

cs 复制代码
    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();//执行传入委托中方法
        }
    }

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

cs 复制代码
    People2 people = new People2() { Id = 1, Name = "北京人张某", Type = AreaType.BeiJing };
    people.SayHi(people.BeiJingSayHi);
第一次需求变更,新增加一个地区人打招呼的方式
  • 方案1:区域枚举需要变动,SayHi方法需要新增代码
  • 方案2:新增一个独立的方法
  • 方案3:新增一个独立的方法
第二次需求变更,需要每次打招呼之前都先招手
  • 方案1:SayHi方法需要新增一行代码
  • 方案2:每个方法都需要新增招手的代码
  • 方案3:只需要在SayHi方法中新增一行代码
方案对比

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

小结

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

四、泛型委托

自定义泛型委托

cs 复制代码
    //【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两个泛型委托,不用单独申明,拿来就可以用。

Action

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

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

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

cs 复制代码
#正常申明无返回,无参数的委托是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();
        }
    }

Func

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

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

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

cs 复制代码
    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);
        }
    }
相关推荐
brent4232 小时前
DAY50复习日
开发语言·python
木头程序员2 小时前
前端(包含HTML/JavaScript/DOM/BOM/jQuery)基础-暴力复习篇
开发语言·前端·javascript·ecmascript·es6·jquery·html5
Data_agent2 小时前
Cocbuy 模式淘宝 / 1688 代购系统(欧美市场)搭建指南
开发语言·python
lsx2024062 小时前
《Foundation 下拉菜单》
开发语言
期待のcode2 小时前
认识Java虚拟机
java·开发语言·jvm
raining_peidx2 小时前
xxljob源码
java·开发语言
肥猪猪爸2 小时前
双重检查锁(DCL)与 volatile 的关键作用
java·开发语言·单例模式
yaoxin5211233 小时前
289. Java Stream API - 从字符串的字符创建 Stream
java·开发语言
癫狂的兔子3 小时前
【Python】【Flask】抽奖功能
开发语言·python·flask