0.前言
委托是安全封装方法的类型,功能类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 C#委托倒是和C++的std::function<>类似,都是对可调用对象封装。
委托实际是特殊的类。委托必须直接或间接地派生自System.Delegate,点net中的委托类型总是派生自System.MulticastDelegate,而他又从System.Delegate派生。但是,委托类型是密封的(密封不能被继承),C#不允许声明直接或间接从这两个类派生的类。
1.写第一个委托
要使用委托需要先声明一个委托类型,委托的类型由委托的名称确定,支持0到32个参数。下面声明了一个名为Del的委托类型:
public delegate void Del(string message);
委托声明也能嵌套在类中,此时委托的类型就是(外部类名.委托名称)。
此外,还需要为委托定义一个方法。
public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}
接下来就是实例化和调用委托。委托对象通常通过提供委托将封装的方法的名称或使用匿名函数构造。 对委托进行实例化后,委托会将对其进行的方法调用传递到该方法。 调用方传递到委托的参数将传递到该方法,并且委托会将方法的返回值(如果有)返回到调用方。
// 实例化委托
Del handler = DelegateMethod;
// 调用委托
handler("Hello World");
注意,委托是引用类型,但不必用new实例化。从方法组(为方法命名的表达式)向委托类型的转换会自动创建一个新的委托对象。
由于实例化的委托是一个对象,因此可以作为参数传递或分配给一个属性。 这允许方法接受委托作为参数并在稍后回调委托。
//创建方法,接收委托对象参数,并回调
public static void CallbackMethod(string message, Del callback)
{
callback(message);
}
...
CallbackMethod("Callback", handler);
完整代码如下:
using System;
namespace TestCS_20191110_delegate
{
//声明委托类型
public delegate void Del(string message);
class Program
{
static void Main(string[] args)
{
// 实例化委托
Del handler = DelegateMethod;
// 调用委托
handler("Hello World");
//回调
CallbackMethod("Callback", handler);
System.Console.ReadKey();
}
//为委托创建一个方法
public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}
public static void CallbackMethod(string message, Del callback)
{
callback(message);
}
}
}
2.多播
调用时,委托可以调用多个方法, 这被称为多播。 若要向委托的方法列表(调用列表)添加其他方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。若要删除调用列表中的方法,请使用减法运算符或减法赋值运算符(-或 -=)。如果委托使用引用参数,引用将按相反的顺序传递到所有的方法,并且一种方法进行的任何更改都将在另一种方法上见到。 当方法引发未在方法内捕获到的异常时,该异常将传递到委托的调用方,并且不会调用调用列表中的后续方法。 如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。多播委托广泛用于事件处理中。
MyDelegate d1 = AMethod;
MyDelegate d2 = BMethod;
MyDelegate d3 = CMethod;
MyDelegate delegates = d1 + d2;
delegates += d3;
//delegates -= d3;
string arg = "arg";
string result = delegates(ref arg);
Console.WriteLine("result:" + result);
Console.WriteLine("arg:" + arg);
3.通用的委托:System.Func和System.Action
System.Func为有返回值的泛型委托,而System.Action为void返回值的泛型委托,两者支持最多16个输入参数。
static void Main(string[] args)
{
Console.WriteLine(Test<int,int>(Fun,100,200));
Console.ReadKey();
}
public static int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
{
return func(a, b);
}
private static int Fun(int a, int b)
{
return a + b;
}
static void Main(string[] args)
{
Test<string>(Action,"Hello World!");
Test<int>(Action, 1000);
Test<string>(p => { Console.WriteLine("{0}", p); }, "Hello World");//使用Lambda表达式定义委托
Console.ReadKey();
}
public static void Test<T>(Action<T> action, T p)
{
action(p);
}
private static void Action(string s)
{
Console.WriteLine(s);
}
private static void Action(int s)
{
Console.WriteLine(s);
}
虽然大部分时候使用Func或Action都能完全避免定义自己的委托,但是声明自己的委托类型可以增强代码的可读性。
还要注意的是,点net委托类型不具备结构相等性。也就是说,不能将某个委托类型的对象引用转换成不相干的委托类型,即使他们的形参列表和返回值一致。此时可以用就委托的Invoke方法,即b=a.Invoke。
4.参考
MSDN文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/
博客:https://www.cnblogs.com/zhaogaojian/p/8421974.html
书籍:C#本质论