委托概述Delegat
将方法调用者 和目标方法 动态关联起来,委托是一个类 ,所以它和类是同级 的,可以通过委托来掉用方法 ,不要误以为委托和方法同级的 ,方法只是类的成员。委托定义了方法的类型(定义委托和与之对应的方法必须具有相同的参数个数 ,并且类型相同,返回值类型相同 ),使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用if-else(Switch)语句,同时使得程序具有更好的可扩展性。
- 在现实生活中,委托只是个命令,做事情是别人,而在程序世界里委托只是存储了各个方法的地址,而他自己也是什么也不做的。
- 声明委托是声明一种类型
- 委托用来传递方法
- 如果方法带括号叫执行方法,不带括号叫传递方法
- 委托传递方法,实现封装和隔离,可以理解成占位符,相当于代数中的x
- 委托存的是函数的引用,地址,存的是存放函数指针的数组或列表
- 方法地址传给委托变量
- 定义了委托就是定义了可以代表的方法类型
普通变量存放的是不同的数据,但委托变量里存放的是不同的行为
一句话:委托就是定义了函数的形状
跟类定义一个级别

- 委托是行为的载体
一个类的实例叫对象
但一个委托的实例还是叫委托,或者叫委托实例,委托叫委托类型
在C#窗体应用程序Winform中,"委托"的名字比较规范,统一使用"EventHandler",它的具体格式是"void EventHandler(object sender, EventArgs e);"。
EventHandler is a delegate for a function that takes an Object (the sender) and eventargs

当点击按钮的时候弹出一个对话框。我们怎样实现的呢?你肯定会说,我们在设计窗口双击按钮,就会自动为我们生成类似如下的方法:
csharp
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("我被点击了!!!");
}
其实,这里用到的就是事件和委托,这里的button1_Click就是符合EventHandler委托规矩的一个具体的方法,即返回值为void,参数分别是一个object和EventArgs。
我们可以在Form1.Designer.cs中看到如下代码:
this.button1.Click += new System.EventHandler(this.button1_Click);
委托类型定义了委托实例可以调用哪些方法,说白了,委托类型定义了方法的返回类型和参数
1、委托有很好的封装性
2、委托的实例化与它的执行是在不同的对象中完成的
例子1(初步理解)
csharp
//小张类,订阅
public class MrZhang
{
public static void BuyTicket()
{
Console.WriteLine("NND,每次都让我去买票,鸡人呀!");
}
}
//小明类,发布
class MrMing
{
//声明一个委托,其实就是个"命令",是最高指示
public delegate void BugTicketEventHandler();
public static void Main(string[] args)
{
//这里就是具体阐述这个命令是干什么的,本例是MrZhang.BuyTicket"小张买车票"
BugTicketEventHandler myDelegate = new BugTicketEventHandler(MrZhang.BuyTicket);
//这时候这个委托就会被附上了具体的方法
myDelegate();
Console.ReadKey();
}
}
BugTicketEventHandler myDelegate = new BugTicketEventHandler(MrZhang.BuyTicket);
这是委托的声明方法, BugTicketEventHandler(委托的方法);
委托的方法必须要加上,因为委托的构造函数是不为空的。
注:委托的参数和返回类型,都要和你要具体委托的方法要一致,例:
csharp
public delegate void BugTicketEventHandler();
public static void BuyTicket()
{
Console.WriteLine("NND,每次都让我去买票,鸡人呀!");
}
//小张类
public class MrZhang
{
public static void BuyTicket()
{
Console.WriteLine("NND,每次都让我去买票,鸡人呀!");
}
public static void BuyMovieTicket()
{
Console.WriteLine("我去,自己泡妞,还要让我带电影票!");
}
}
csharp
//小明类
class MrMing
{
//声明一个委托,其实就是个"命令"
public delegate void BugTicketEventHandler();
public static void Main(string[] args)
{
//这里就是具体阐述这个命令是干什么的,本例是MrZhang.BuyTicket"小张买车票"
BugTicketEventHandler myDelegate = new BugTicketEventHandler(MrZhang.BuyTicket);
myDelegate += MrZhang.BuyMovieTicket;
//这时候委托被附上了具体的方法,多播委托又来了一个电影票
myDelegate();
Console.ReadKey();
}
}
其实,我们只是在程序中加了 myDelegate += MrZhang.BuyMovieTicket;
这时这个委托就相当于要做2件事情,先是买车票,再是买电影票
csharp
public delegate int Math(int param1,int param2);//定义委托类型,没方法体
Public int Add(int param1,int param2)//定义同签名函数
{
return param1+param2;
}
Math math;//声明委托
math=new Math(Add);//创建委托对象,与指定进行关联
math(3,4);//调用委托函数
也可以使用内置的委托类型
Func<int,int,int> math = Add;
math<3,4>
无返回值的委托
Action委托具有Action<T>、Action<T1,T2>、Action<T1,T2,T3>......Action<T1,......T16>
多达16个的重载,其中传入参数均采用泛型中的类型参数T
涵盖了几乎所有可能存在的无返回值的委托类型
有返回值
Func则具有Func<TResult>、Func<T,Tresult>、Func<T1,T2,T3......,Tresult>
17种类型重载,T1......T16为出入参数,Tresult为返回类型。
//lambda
Func<int,int,int> math =(param1 =param2) =>
{
return param1+param2;
}
例子2
在 C# 中,委托 (delegate) 可以被理解为函数的"容器"或"代理"。它允许你将方法作为参数传递给其他方法,就像传递普通变量一样。
通俗解释
想象一下,你有一个快递员,他可以送各种各样的包裹。委托就像一个"送货单",上面写着"把这个包裹送到指定地址"。
- 委托定义: 相当于创建一张空白的"送货单",规定了包裹的类型(参数类型)和送达方式(返回类型)。
- 委托实例: 相当于在"送货单"上填写具体的包裹和地址(方法)。
- 委托调用: 相当于快递员按照"送货单"上的指示,把包裹送到目的地。
csharp
// 1. 定义委托(送货单)
public delegate int Calculate(int x, int y);
public class Example
{
// 2. 定义方法(包裹)
public static int Add(int a, int b)
{
return a + b;
}
public static int Subtract(int a, int b)
{
return a - b;
}
public static void Main(string[] args)
{
// 3. 创建委托实例(填写送货单)
Calculate addDelegate = new Calculate(Add);
Calculate subtractDelegate = new Calculate(Subtract);
// 4. 调用委托(快递员送货)
int sum = addDelegate(5, 3); // 相当于调用 Add(5, 3)
int difference = subtractDelegate(5, 3); // 相当于调用 Subtract(5, 3)
System.Console.WriteLine($"Sum: {sum}, Difference: {difference}");
}
}
在某些简单的情况下,直接调用 Add 方法似乎更直接。但是,委托的真正价值在于它的灵活性和抽象性,它允许你以一种更通用和动态的方式处理方法。以下是一些解释为什么不直接执行 Add 方法的理由:
1. 延迟执行和动态选择:
- 委托允许你将方法的执行推迟到稍后的某个时间点。
- 你可以根据运行时的条件,动态地选择要执行的方法。例如,你可以根据用户的选择,决定是执行加法还是减法。
- 这在事件处理和回调函数中非常有用。
2. 将方法作为参数传递:
- 委托允许你将方法作为参数传递给其他方法。
- 这使得你可以编写更通用和可重用的代码。
- 例如,你可以编写一个通用的排序方法,该方法接受一个委托作为参数,用于指定排序的规则。
3. 解耦和提高灵活性:
- 委托可以将方法的调用者和方法的实现者解耦。
- 这意味着调用者不需要知道具体的实现细节,只需要知道如何调用委托即可。
- 这提高了代码的灵活性和可维护性。
- 委托,可以理解为,把一个方法,赋值给一个委托变量,然后通过这个委托变量,去执行这个方法。
4. 事件驱动编程:
- 在事件驱动编程中,委托是至关重要的。
- 当事件发生时,委托用于调用相应的事件处理程序。
- 例如,当用户点击按钮时,委托用于调用按钮的点击事件处理程序。
声明委托
委托属于一个定义,是和类、接口类似的,通常放在外部。委托和类平级
csharp
//定义一个无返回值的,带一个int参数的委托
public delegate void myDelegate(int num);
委托也是将方法作为参数传递,也是发布订阅模式。
csharp
public myDelegate m_delegate;
m_delegate += MyFun;
public void MyFun(int num) //方法体签名要一样
{
Debug.Log("my func: " + num);
}
但是它有一个弊端,delegate可以使用"="将所有已经订阅的取消,挤掉了(也可以用+/-对订阅合并和删除,这是后话,不讲),只保留=后新的订阅,这给了犯罪分子可乘之机。
csharp
m_delegate = MyFun1; //MyFun订阅被取消,只有MyFun1在订阅中,刚才的MyFun被挤掉了
public void MyFun1(int num)
{
Debug.Log("my func1: " + num);
}
所以,event应运而生
event是一种特殊的委托,它只能+=,-=,不能直接用=
csharp
public event myDelegate m_event;
m_event += MyFun;
m_event = MyFun; //错误,
event是一种特殊的委托,它只能+=,-=,不能直接用=
csharp
public event myDelegate m_event;
m_event += MyFun;
m_event = MyFun; //错误,
在使用一个类的时侯,分两个阶段。首先定义一个类,告诉编译器这个类由什么方法和字段组成。然后实例化类的对象。
同理
使用委托时,也需要经过这两个步骤。
先定义要是用的委托,告诉这种类型的委托表示哪种类型的方法。然后必须创建该委托的一个或多个实例。
csharp
delegate void IntMethodInvoker(int x); 无返回值
delegate string Getstring(); 返回值类型为string
delegate double TwoLongsOp(long first,long second);返回值类型为double
事件基于委托
csharp
private delegate string GetAString();
public static void Main()
{
int x = 40;
//实例化类型为getastring的委托,并将其初始化
GetAString firstStringMethod = new GetAString(x.ToString);
引用整型变量x的tostring方法
Console.WriteLine("string is {firstStringMethod()}");
}
firststringmethod.invoke();可以代替
firststringmethod();
event在定义类中**(发布者)是可以直接=的,但是在其他类中 (订阅者)**就只能+= -=了,也就是说发布者发布一个事件后,订阅者针对他只能进行自身的订阅和取消。

但是,在事件发布和订阅的过程中,定义事件的原型委托类型常常是一件重复性的工作。
所以,微软为我们推出了EventHandler应运而生
它的出生就是为了避免这种重复性工作,并建议尽量使用该类型作为事件的原型。
csharp
//这是它的定义
//@sender: 引发事件的对象
//@e: 传递的参数
public delegate void EventHandler(object sender, EventArgs e);
//使用
public event EventHandler m_event; //修改自定义委托类型为EventHandler
只要用变量x将方法名传给变量firststringmethod
效果相同= getastring firststringmethod = x.tostring;
※不能传x.tostring(),这会调用一个方法,要把方法地址赋值给委托变量



委托是什么
其实,我一直思考如何讲解委托,才能把委托说得更透彻。说实话,每个人都委托都有不同的见解,因为看问题的角度不同。个人认为,可以从以下2点来理解:
(1) 从数据结构来讲,委托是和类一样是一种用户自定义类型。
(2) 从设计模式来讲,委托(类)提供了方法(对象)的抽象。
既然委托是一种类型,那么它存储的是什么数据?
我们知道,委托是方法的抽象,它存储的就是一系列具有相同签名 和返回回类型的方法 的地址。调用委托的时候,委托包含的所有方法将被执行。
委托的定义
委托是类型,就好像类是类型一样。与类一样,委托类型必须在被用来创建变量以及类型对象之前声明。
csharp
delegate void MyDel(int x);
委托类型声明:
(1) 以deleagate关键字开头。
(2)返回类型+委托类型名+参数列表。
csharp
public delegate string ProcessString(string input);
这行代码定义了一个名为 ProcessString 的委托,它可以引用任何接受一个 string 参数并返回一个 string 的方法。
赋值委托
由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的方法地址引用。旧的引用会被垃圾回收器回收。
csharp
MyDel del;
del = myInstaObj.MyM1; //委托初始化
del = SClass.OtherM2;//委托重新赋值,旧的引用将被回收
委托加减运算
可以使用+=运算符,为委托新增方法。
同样可以使用-=运算符,为委托移除方法。
csharp
MyDel del = myObj.MyMethod;
del += SClass.OtherM2; // 增加方法
del -= myObj.MyMethod; // 移除方法
委托调用
委托调用跟方法调用类似。委托调用后,调用列表的每个方法将会被执行。
在调用委托前,应判断委托是否为空。调用空委托会抛出异常。
csharp
if(null != del)
{
del();//委托调用
}
委托的用途
委托的使用场景广泛,主要包括:
回调机制:允许方法将另一个方法作为参数,便于在适当的时候调用。
事件处理:委托是.NET事件模型的基础,用于定义在特定事件发生时应该调用哪些方法。
异步处理:在.NET中,委托被用于异步编程模型,允许方法在后台线程上执行,而不冻结用户界面。抽象和封装方法调用:委托允许方法调用更加灵活,支持高阶函数的编程风格,如LINQ查询操作。
委托的优势
使用委托的主要优势包括:
灵活性:委托允许运行时决定方法调用,增加了程序的灵活性。解耦:方法可以在不同的上下文中被重用,而调用者无需了解方法的具体实现。交互性:委托可以用于实现高级交互模式,如事件驱动或异步编程。
委托解秘
从表面上看,委托很容易使用,只需要通过delegate 关键字定义,用new 操作符构造委托实例并传入方法,最后调用即可。但实际上编译器和CLR(Common Language Runtime)在幕后做了大量工作来隐藏它的复杂性。为了高效使用委托并加深对它的的理解,我们看看编译器和CLR是如何实现委托的。
在C#中,委托是一个类(Class),我们定义一个委托,查看它的IL代码
csharp
public delegate void Feedback();
class Program
{
static void Main(string[] args)
{
Type t = typeof(Feedback);
// 使用IsClass判断是否是类,返回True
Console.WriteLine(t.IsClass);
}
}
通过IL也可以看到,编译器定义了一个MyDel 类,它继承自MulticastDelegate ,并且包含BeginInvoke (异步调用),EndInvoke 和Invok (同步调用)三个方法。这个类的可访问性是public ,这是因为定义的委托是public类型,由于委托是类,所以但凡能够定义类的地方都能定义

既然委托继承自MulticastDelegate ,理所当然它也继承了该类的字段、属性和方法,在这些成员中,有三个字段需要注意:

每个委托都有一个构造函数,它需要两个参数:一个是对象引用,另一个是引用了回调方法的整数。构造委托时引用的对象会被保存在 _target 字段中,方法地址会保存在 _MethodPtr 中,并且将 _invocationList 字段设置为null(这个字段讲解委托链的时候再具体说明) 。
所以,每个委托对象实际都是一个包装器,其中包装了一个方法和调用该方法时要操作的对象。例如,执行上述例子中的两行代码之后:
csharp
Feedback fbStatic = new Feedback(Program.FeedbackToConsole);
//包装静态方法
Feedback fbInstance = new Feedback(new Program().FeedbackToFile);
//包装实例方法
fbStatic 和fbInstance 变量将引用两个独立的、初始化好的Feedback委托对象,如下图所示:

在 C# 委托中,target、methodPtr 和 invocationList 是与委托内部工作原理相关的关键概念。它们在委托的执行和多播行为中起着重要作用。
以下是这些概念的详细解释:
1. target(目标)
target属性表示委托所引用的方法的实例。- 如果委托引用的是一个实例方法,则
target属性会指向该方法的实例。 - 如果委托引用的是一个静态方法,则
target属性为null。 target属性允许委托在运行时确定它所引用的方法属于哪个对象。
2. methodPtr(方法指针)
methodPtr属性表示委托所引用的方法的地址。- 它是一个内部指针,指向方法在内存中的位置。
methodPtr属性允许委托直接调用它所引用的方法,而无需通过方法名称。
3. invocationList(调用列表)
invocationList属性是一个Delegate数组,它存储了多播委托所引用的所有方法。- 当一个委托引用多个方法时(多播委托),这些方法会被添加到
invocationList中。 - 当调用多播委托时,它会依次调用
invocationList中的所有方法。 - 如果委托只引用一个方法,则
invocationList为null,并且委托直接使用target和methodPtr来调用该方法。
它们之间的关系
- 对于单播委托(只引用一个方法),
target和methodPtr用于直接调用该方法。 - 对于多播委托(引用多个方法),
invocationList用于存储所有引用的方法,并依次调用它们。 invocationList内部的每个委托,又都包含target,和methodPtr。
委托链
委托链就是委托的集合,可以利用它调用集合中的委托所代表的全部方法。
修改例子中的Main 函数,添加委托fbChain ,将其赋值为fbStatic ,并且调用Combine函数
csharp
static void Main(string[] args)
{
Feedback fbStatic = new Feedback(Program.FeedbackToConsole);
Feedback fbInstance = new Feedback(new Program().FeedbackToFile);
Feedback fbChain = fbStatic;
//等价于fbChain += fbInstance;
fbChain = (Feedback)Delegate.Combine(fbChain, fbInstance);
fbChain.Invoke(); //依次执行
}
Combine 函数会返回一个新构造的委托对象并赋值给fbChain ,此时 _invocationList 字段被初始化为一个委托类型的数组,数组的第一个元素被初始化为fbStatic 引用的委托,第二个元素则引用了fbInstance 引用的委托。调用fbChain 的时候会分别调用 _invocationList 中所引用的委托。它的状态如下图所示

既然可以向委托链中添加委托,那肯定也支持删除操作,删除是通过Remove方法实现的
csharp
//等价于 fbChain -= fbInstance;
fbChain = (Feedback)Delegate.Remove(fbChain, fbInstance);
调用fbChain 的时候,会按顺序执行调用链中的每一个委托,这能适应大部分使用场景,但也有它的局限性,比如当函数有多个返回值时,下面例子中,为委托feedback添加了两个方法,分别返回Diaomao和靓仔。
csharp
delegate string Feedback();
class Program
{
static void Main(string[] args)
{
Feedback feedback = new Feedback(Diaomao);
feedback += Liangzai;
Console.WriteLine(feedback.Invoke());
}
private static string Diaomao()
{
return "I'm Diaomao";
}
private static string Liangzai()
{
return "I'm Liangzai";
}
}
运行后打印的是哪个呢?当然是靓仔了 怎么能说自己是Diaomao呢!
这是因为当委托中有多个回调方法时,只会返回最后一个回调方法的值,其他的值全都被丢弃。
如果想获取Diaomao 这个值怎么办呢?C#为我们提供了MulticastDelegate 类,它拥有了一个实例方法GetInvocationList ,会返回调用链包含的委托数组。例如可以像下面代码那样调用委托链中的每一个委托并获取结果,这样会先打印Diaomao ,然后打印Liangzai。
csharp
Feedback feedback = new Feedback(Diaomao);
feedback += Liangzai;
foreach(var fb in feedback.GetInvocationList())
{
Console.WriteLine((fb as Feedback).Invoke());
}
1.1 理解委托 Delegate
1.1.1 将方法作为方法的参数
我们先不管这个标题如何的绕口,也不管委托究竟是个什么东西,来看下面这两个最简单的方法,它们不过是在屏幕上输出一句问候的话语:
csharp
public void GreetPeople(string name)
{
EnglishGreeting(name);
}
public void EnglishGreeting(string name)
{
Console.WriteLine("Good Morning, " + name);
}
暂且不管这两个方法有没有什么实际意义。
GreetPeople 用于向某人问好,当我们传递代表某人姓名的 name 参数,比如说"Liker"进去的时候,在这个方法中,将调用 EnglishGreeting 方法,再次传递 name 参数,EnglishGreeting 则用于向屏幕输出 "Good Morning, Liker"。
现在假设这个程序需要进行全球化,哎呀,不好了,我是中国人,我不明白"Good Morning"是什么意思,怎么办呢?好吧,我们再加个中文版的问候方法:
csharp
public void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
这时候,GreetPeople 也需要改一改了,不然如何判断到底用哪个版本的 Greeting 问候方法合适呢?在进行这个之前,我们最好再定义一个枚举作为判断的依据:
csharp
public enum Language
{
English, Chinese
}
public void GreetPeople(string name, Language lang)
{
switch (lang)
{
case Language.English:
EnglishGreeting(name);
break;
case Language.Chinese:
ChineseGreeting(name);
break;
}
}
OK,尽管这样解决了问题,但我不说大家也很容易想到,这个解决方案的可扩展性很差,如果日后我们需要再添加韩文版、日文版,就不得不反复修改枚举和GreetPeople() 方法,以适应新的需求。
在考虑新的解决方案之前,我们先看看 GreetPeople 的方法签名:
csharp
public void GreetPeople(string name, Language lang);
我们仅看string name,在这里,string 是参数类型,name 是参数变量 ,当我们赋给 name 字符串"Liker"时,它就代表"Liker"这个值;当我们赋给它"李志中"时,它又代表着"李志中"这个值。然后,我们可以在方法体内对这个 name 进行其他操作。哎,这简直是废话么,刚学程序就知道了。
假如 GreetPeople() 方法可以接受一个参数变量,这个变量可以代表另一个方法
当我们给这个变量赋值 EnglishGreeting 的时候,它代表着 EnglsihGreeting() 这个方法;
当我们给它赋值ChineseGreeting 的时候,它又代表着 ChineseGreeting() 这个方法。
我们将这个参数变量命名为 MakeGreeting ,那么不是可以如同给 name 赋值时一样,在调用 GreetPeople()方法的时候,给这个MakeGreeting 参数也赋上值么(ChineseGreeting 或者EnglsihGreeting 等)?然后,我们在方法体内,也可以像使用别的参数一样使用MakeGreeting。但是,由于 MakeGreeting 代表着一个方法 ,它的使用方式应该和它被赋的方法(比如ChineseGreeting)是一样的,比如:MakeGreeting(name);
好了,有了思路了,我们现在就来改改GreetPeople()方法,那么它应该是这个样子了:
csharp
public void GreetPeople(string name, *** MakeGreeting)
{
MakeGreeting(name);
}
注意到 ******* ,这个位置通常放置的应该是参数的类型,但到目前为止,我们仅仅是想到应该有个可以代表方法的参数,并按这个思路去改写 GreetPeople 方法,现在就出现了一个大问题:这个代表着方法的 MakeGreeting 参数应该是什么类型的?
**说明:**这里已不再需要枚举了,因为在给MakeGreeting 赋值的时候动态地决定使用哪个方法,是 ChineseGreeting 还是 EnglishGreeting,而在这个两个方法内部,已经对使用"Good Morning"还是"早上好"作了区分。
聪明的你应该已经想到了,现在是委托该出场的时候了,但讲述委托之前,我们再看看MakeGreeting 参数所能代表的 ChineseGreeting()和EnglishGreeting()方法的签名:
csharp
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
如同 name 可以接受 String 类型的"true"和"1",但不能接受bool 类型的true 和int 类型的1 一样。MakeGreeting 的参数类型定义应该能够确定 MakeGreeting 可以代表的方法种类,再进一步讲,就是 MakeGreeting 可以代表的方法的参数类型和返回类型。
于是,委托出现了:它定义了 MakeGreeting 参数所能代表的方法的种类,也就是 MakeGreeting 参数的类型。可以联想到委托类型。
本例中委托的定义:
csharp
public delegate void GreetingDelegate(string name);
与上面 EnglishGreeting() 方法的签名对比一下,除了加入了delegate 关键字以外,其余的是不是完全一样?现在,让我们再次改动GreetPeople()方法,如下所示:
csharp
public delegate void GreetingDelegate(string name);
public void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
如你所见,**委托 GreetingDelegate 出现的位置与 string 相同,string 是一个类型,那么 GreetingDelegate 应该也是一个类型,**或者叫类(Class)。但是委托的声明方式和类却完全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类 。因为 Delegate 是一个类 ,委托与类是同级关系。所以在任何可以声明类的地方都可以声明委托。更多的内容将在下面讲述,现在,请看看这个范例的完整代码:
csharp
public delegate void GreetingDelegate(string name);
//定义了委托就是定义了可以代表的方法类型
class Program
{
private static void EnglishGreeting(string name)
{
Console.WriteLine("Good Morning, " + name);
}
private static void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
//此方法接受了一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
static void Main(string[] args)
{
GreetPeople("Liker", EnglishGreeting);
GreetPeople("李志中", ChineseGreeting);
Console.ReadLine();
}
}
我们现在对委托做一个总结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If ... Else(Switch)语句,同时使得程序具有更好的可扩展性。
1.1.2 将方法绑定到委托
看到这里,是不是有那么点如梦初醒的感觉?于是,你是不是在想:在上面的例子中,我不一定要直接在 GreetPeople() 方法中给 name 参数赋值,我可以像这样使用变量:
csharp
static void Main(string[] args)
{
GreetPeople("Liker", EnglishGreeting);
GreetPeople("李志中", ChineseGreeting);
Console.ReadLine();
}
而既然委托 GreetingDelegate 和类型 string 的地位一样,都是定义了一种参数类型,那么,我是不是也可以这么使用委托?
csharp
static void Main(string[] args)
{
//public delegate void GreetingDelegate(string name);
GreetingDelegate delegate1, delegate2;
delegate1 = EnglishGreeting; // 关联委托
delegate2 = ChineseGreeting;
GreetPeople("Liker", delegate1);
GreetPeople("李志中", delegate2);
Console.ReadLine();
}
如你所料,这样是没有问题的,程序一如预料的那样输出。这里,我想说的是委托不同于 string 的一个特性:**可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。**在这个例子中,语法如下:
csharp
static void Main(string[] args)
{
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
GreetPeople("Liker", delegate1);
Console.ReadLine();
}
实际上,我们可以也可以绕过GreetPeople 方法,通过委托来直接调用EnglishGreeting 和ChineseGreeting:
csharp
public delegate void GreetingDelegate(string name);
static void Main(string[] args)
{
GreetingDelegate delegate1;
delegate1 = EnglishGreeting; // 方法签名是一样的
delegate1 += ChineseGreeting;
delegate1("Liker");
Console.ReadLine();
}
说明:这在本例中是没有问题的,但回头看下上面 GreetPeople() 的定义,在它之中可以做一些对于 EnglshihGreeting 和 ChineseGreeting 来说都需要进行的工作,为了简便我做了省略。
注意这里,第一次用的"=",是赋值的语法;第二次,用的是"+=",是绑定的语法。如果第一次就使用"+=",将出现"使用了未赋值的局部变量"的编译错误。
我们也可以使用下面的代码来这样简化这一过程:
csharp
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;
既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是**"-="**
csharp
static void Main(string[] args)
{
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting;
GreetPeople("Liker", delegate1);
Console.WriteLine();
delegate1 -= EnglishGreeting;
GreetPeople("李志中", delegate1);
Console.ReadLine();
}
让我们再次对委托作个总结:
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用"调用"这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
误区注意
委托中有多个回调方法时,只会返回最后一个回调方法的值,其他的值全都被丢弃"的说法,主要针对的是有返回值的多播委托 。而你提供的例子中,委托 GreetingDelegate 的返回类型是 void,也就是没有返回值,所以不会出现返回值被丢弃的情况。
让我们详细分析一下:
1. 多播委托和返回值:
- 当一个多播委托(即包含多个方法的委托)有返回值时,调用该委托会依次执行其包含的所有方法。
- 但是,委托的返回值只能是最后一个被执行的方法的返回值。
- 其他方法的返回值会被忽略。
- 这是因为委托只能返回一个值。
2. 你的例子:
- 在你的例子中,
GreetingDelegate委托的定义是public delegate void GreetingDelegate(string name);,它没有返回值 (void)。 - 因此,当你使用
delegate1 += ChineseGreeting;将ChineseGreeting方法添加到委托链中时,实际上是创建了一个多播委托 ,它包含了EnglishGreeting和ChineseGreeting两个方法。 - 当你调用
GreetPeople("Liker", delegate1);时,delegate1中的两个方法都会被依次执行。 - 由于这两个方法都没有返回值,所以不会出现返回值被丢弃的情况。
- 每个方法都向控制台输出了内容,因此你会看到两个问候语都被显示出来。
1.1.3 delegate

委托先定义一个函数模板
delegate委托声明 Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。
注意 delegate必须实例化 声明的参数和返回值没有限制 但是委托的方法必须和委托的类型一致

csharp
/// <summary>
///声明一个返回值为int的委托并带两个int参数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public delegate int MethodDelegate(int x, int y);
//这样声明 委托就是私有的静态类
// private static MethodDelegate method;
/// <summary>
/// 创建一个返回值为int的方法带两个int参数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private static int Add(int x, int y)
{
return x + y;
}
public static void Main(string[] args)
{
// 实例化委托 并传方法(方法必须跟委托的返回值类型和参数类型和个数一致)
//只要跟这个委托的参数和返回值一致的,所有的该方法都可以使用该委托
MethodDelegate method = new MethodDelegate(Add);
//然后调用委托的实例化 直接传参数(实际上调用的是add方法)
Console.WriteLine(method(10, 20));
}

多播委托 +=
多播委托 一个委托想要多个方法作为参数 使用+=,-= 他会执行按顺序执行所有的方法

csharp
func += (a, b) =>
{
return int.Parse(a) - int.Parse(b);
};
sw.AppendLine($"【func用法2】func返回结果是:{func("3", "5")}");
+=注册实现多播委托
1.1.4. Func和Action(内置委托)
Func(有返回值)
是一个.Net内置的泛型委托 。Func是有返回值的泛型委托(必须有返回值),简化了定义委托的繁琐程度,TResult就是返回值
原型函数:public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
Func
Func<T,TResult>·
Func<T1,T2,TResult>
Func<T1,T2,T3,TResult>
Func<T1,T2,T3,T4,TResult>
展示5种形式,只是参数个数不同;第一个是无参数,但是有返回值;T1-T4表示的是参数,TResult表示返回类型
Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void
csharp
private delegate string Say();
public static string SayHello()
{
return "Hello";
}
static void Main(string[] args)
{
Say say = SayHello;
Console.WriteLine(Say());
Console.ReadKey();
}
所以,在有时候,我们不知道一个接口同时要做什么操作的时候,我可以给它留一个委托。
为了更方便,.Net直接默认有了委托。我们再来试试.Net默认带的委托。
直接使用Func,不用delegate
csharp
public static string SayHello()
{
return "Hello";
}
static void Main(string[] args)
{
Func<string> say = SayHello;
Console.WriteLine(say());
Console.ReadKey();
}
csharp
public int StringAddA(string a, string b)
{
return int.Parse(a) + int.Parse(b);
}
Func<string, string, int> func = StringAddA;
是
Func<string, string, int> func = new Func<string, string, int>(StringAddA);
的简写
Func<string, string, int> func = StringAddA; // 简写
var result = func.Invoke("3", "5"); // 可以简化为func("3", "5")
sw.AppendLine($"【func用法1】func返回结果是:{result}");
Func<T,TR>(T arg)
参数类型T
此委托封装的方法的参数类型。
返回值类型TR
此委托封装的方法的返回值类型。TResult
参数 arg
类型 T
此委托封装的方法的参数。
在使用 Func<T,TResult>委托时,不必显式定义一个封装只有一个参数的方法的委托。以下示例简化了此代码,它所用的方法是实例化 Func<T, TResult>委托,而不是显式定义一个新委托并将命名方法分配给该委托。
Action(无返回值)
csharp
// Action是系统预定义的一种委托,无返回值,参数在<>中传入
public Action<int> m_action;
//比较下delegate和Action的定义(个人理解)
public delegate void myDelegate(int num);
public Action<int> m_action;
// 1,Action省略了void,因为它本身就是无返回值
// 2, Action的参数在<>中定义的,delegate就是传统定义
// 3,delegate要用关键字,然后自定义一个委托名字。而Action委托名字已定。不需要delegate关键字。
是无返回值的泛型委托,可以有参数
Action
Action<T1,T2>
Action<T1,T2,T3>
Action<T1,T2,T3,T4>
Action至少0个参数,至多16个参数,无返回值。T1-T4表示的是参数
csharp
/// <summary>
/// 声明一个带int参数并返回类型为int类型的方法
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static int Square(int input)
{
return input * input;
}
/// <summary>
/// 声明带一个int参数并无返回值的方法
/// </summary>
/// <param name="input"></param>
private static void GetSquere(int input)
{
Console.WriteLine("The square of {0} is {1}.", input, input * input);
}
public static void Main(string[] args)
{
int num = 5;
/* Func和Action委托的唯一区别在于Func要有返回值, Action没有返回值 */
//注意:委托的函数的返回值和参数个数必须跟方法一样 才能等于
//注意func的格式是<参数类型,返回参数类型>
//Func<int, int>看起来是两个参数 实际上是一个返回int的类型并带一个int参数的方法
//调用的方法跟该委托声明的方法类型一致 (都是返参为int,且带一个int参数)
Func<int, int> funcTest = Square;
Console.WriteLine("The square of {0} is {1}.", num, funcTest(num));
//直接实例化的对象 Square(num)
//无返回值
Action<int> actionTest = GetSquere;//调用的方法跟该委托声明的方法类型一致(都是无返回参数且只带一个int参数)
actionTest(num);//直接实例化的对象 传参数就会直接调用GetSquere(num)
CreateHostBuilder(args).Build().Run();
}

Predicate
只能接受一个传入参数,返回值为bool类型。
可以理解成返回bool值的Func
摘要:表示定义一组条件并确定指定对象是否符合这些条件的方法。
public delegate bool Predicate(T obj)。
参数:obj:要按照由此委托表示的方法中定义的条件进行比较的对象。
类型参数:T:要比较的对象的类型。
返回结果: 如果 obj 符合由此委托表示的方法中定义的条件,则为 true;否则为 false。

总结定义委托的三种用法
csharp
namespace func
{
//委托声明 -- 定义一个签名:
delegate double MathAction(double num);
public class Program
{
// 符合委托声明的常规方法
static double Double(double input)
{
return input * 2;
}
static void Main(string[] args)
{
// 使用一个命名方法实例化委托类型
写法一,需要写出专门委托的函数,还需要自定义委托
MathAction ma = Double;
//注意这里千万不可有Double(),否则就成了一个返回类型,是报错的,这里是制定函数的地址,给定的是函数的地址
//方法地址!!!
//方法地址!!!
//方法地址!!!
//调用委托
double result1 = ma(4.5);
//使用系统自定义委托实例化委托类型
写法二,需要写出专门委托的函数,不需要自定义委托,使用系统委托
Func<double, double> func = Double;
//调用委托
double result2 = func(4.5);
//系统委托使用lamdba进行传递参数
写法三,不需要写出专门委托的函数,还需要自定义委托
Func<double, double> result = s => s * 2;//写法还可以换成lamdba语句块,适应多个参数的写法
double result3 = result(4.5);
Func<double, double> result4 = s =>
{
return s * 2;
};
Console.WriteLine(result1);
Console.WriteLine(result3);
Console.WriteLine(result2);
Console.WriteLine(result4(4.5));
}
}
}
9 9 9 9
1.1.5.为什么要使用委托
委托是一个简单的类,用于指向具有特定签名的方法,它本质上是一个类型安全的函数指针 。委托的目的是为了方便调用另一种方法(或方法),在完成之后,以结构化的方式。
虽然可以创建大量的代码来执行此功能
1.1.6事件+=

1.2.异步委托&同步委托
同步委托 Invoke
委托的Invoke 方法用来进行同步调用。同步调用也可以叫阻塞调用 ,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。
csharp
delegate void MyDelegate();
void MyMethod() { /* ... */ }
MyDelegate del = new MyDelegate(MyMethod);
del.Invoke(); // 显式调用 Invoke
del(); // 隐式调用 Invoke
// 在这段代码中,del.Invoke() 和 del() 的效果是相同的
异步委托 BeginInvoke/EndInvoke
异步调用不阻塞线程,而是把调用塞到线程池 中,程序主线程或UI线程可以继续执行。委托的异步调用通过BeginInvoke 和EndInvoke来实现。
异步委托调用方式
BeginInvoke方法可以使用线程异步地执行委托所指向的方法。