【博主】反骨仔 【原文】http://www.cnblogs.com/liqingwen/p/6031892.html
delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型。can associate its instance with any method with a compatible signature and return type." data-guid="8805d96812f0130e52aa127b25716c15">在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。你可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法。 你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法。下面的示例演示了一个委托声明:
public delegate int PerformCalculation(int x, int y);
可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。 该方法可以是静态方法,也可以是实例方法。 这样便能通过编程方式来更改方法调用,还可以向现有类中插入新代码。
【备注】在方法重载的上下文中,方法的签名不包括返回值。 但在委托的上下文中,签名包括返回值。 换句话说,方法和委托必须具有相同的返回类型。将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。 例如,对比较两个对象的方法的引用可以作为参数传递到排序算法中。由于比较代码在一个单独的过程中,因此可通过更常见的方式编写排序算法。
委托具有以下属性:
function pointers but are type safe." data-guid="583862b4b91493630b32381e6691af95">委托类似于 C++ 函数指针,但它们是类型安全的。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不必与委托类型完全匹配。
C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递来代替单独定义的方法。 expressions as a more concise way of writing inline code blocks." data-guid="08bf2ed2f134e037d51b60a2932601c9">C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。 匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。 这些功能现在统称为匿名函数。
委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定。
//该委托可以封装 “名 MyDel,参数类型 string,返回类型 void” 的方法 public delegate void MyDel(string message);
委托对象通常通过提供委托将封装的方法的名称或使用匿名方法构造。对委托进行实例化后,委托会将对其进行的方法调用传递到该方法。调用方传递到委托的参数将传递到该方法,并且委托会将方法的返回值(如果有)返回到调用方。这被称为调用委托。实例化的委托可以按封装的方法本身进行调用。 例如:
1 //该委托可以封装 “名 MyDel,参数类型 string,返回值类型 void” 的方法 2 public delegate void MyDel(string message); 3 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 //实例化委托 9 MyDel del = Print; 10 //调用委托 11 del("Hi"); 12 13 Console.Read(); 14 } 15 16 /// <summary> 17 /// 打印文本 18 /// </summary> 19 /// <remarks>这是一个可用于 MyDel 委托的方法</remarks> 20 /// <param name="message"></param> 21 private static void Print(string message) 22 { 23 Console.WriteLine(message); 24 } 25 }
委托类型派生自 .NET Framework 中的 Delegate 类。 委托类型是封装的,它们不能派生出其他类,也不能从 Delegate 派生出自定义类。 由于实例化的委托是一个对象,因此可以作为参数传递或分配给一个属性。 这允许方法作为参数接受委托并在稍后调用委托。 这被称为异步回调,是在长进程完成时通知调用方的常用方法。 当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。 功能类似于封装接口提供的功能。
回调的另一个常见用途是定义自定义比较方法并将该委托传递到短方法。 它允许调用方的代码成为排序算法的一部分。 以下示例方法使用 Del 类型作为参数:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 MyDel del = Print; 6 CallbackMethod(100, 150, del); //将委托传递到 CallbackMethod 方法 7 8 Console.Read(); 9 } 10 11 /// <summary> 12 /// 回调方法 13 /// </summary> 14 /// <param name="m"></param> 15 /// <param name="n"></param> 16 /// <param name="del"></param> 17 private static void CallbackMethod(int m, int n, MyDel del) 18 { 19 del((m + n).ToString()); 20 } 21 22 private static void Print(string message) 23 { 24 Console.WriteLine(message); 25 } 26 }
MethodWithCallback 的作用是将字符串传递到其他方法。由于该方法可以使用任意数量的参数,此功能特别强大。
当委托构造为封装实例方法时,委托将同时引用实例和方法。 委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。 当委托构造为封装静态方法时,委托仅引用方法。 请考虑以下声明:
1 //该委托可以封装 “名 MyDel,参数类型 string,返回值类型 void” 的方法 2 public delegate void MyDel(string message); 3 4 class MyClass 5 { 6 public void Print1(string message) 7 { 8 Console.WriteLine($"{message} - {nameof(Print1)}"); 9 } 10 11 public void Print2(string message) 12 { 13 Console.WriteLine($"{message} - {nameof(Print2)}"); 14 } 15 } 16 17 class Program 18 { 19 static void Main(string[] args) 20 { 21 var myClass = new MyClass(); 22 MyDel del1 = myClass.Print1; 23 MyDel del2 = myClass.Print2; 24 MyDel del3 = Print; 25 26 var del = del1 + del2; 27 del += del3; //这里使用 += 28 del("Hi!"); 29 30 Console.Read(); 31 } 32 33 private static void Print(string message) 34 { 35 Console.WriteLine($"{message} - {nameof(Print)}"); 36 } 37 }
调用时,委托可以调用多个方法。 这被称为多播。 若要向委托的方法列表(调用列表)添加其他方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。
此时,allMethodsDelegate 的调用列表中包含三个方法,分别为 Print1、Print2 和 Print。原有的三个委托(d1、d2 和 d3)保持不变。调用 allMethodsDelegate 时,将按顺序调用所有三个方法。 如果委托使用引用参数,引用将按相反的顺序传递到所有这三个方法,并且一种方法进行的任何更改都将在另一种方法上见到。 当方法引发未在方法内捕获到的异常时,该异常将传递到委托的调用方,并且不会调用调用列表中的后续方法。 如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。 若要删除调用列表中的方法,请使用减法运算符或减法赋值运算符(“+”或“+=”)。 例如:
1 static void Main(string[] args) 2 { 3 var myClass = new MyClass(); 4 MyDel del1 = myClass.Print1; 5 MyDel del2 = myClass.Print2; 6 MyDel del3 = Print; 7 8 var del = del1 + del2; 9 del += del3; //使用 += 10 del("Hi!"); 11 12 Console.WriteLine("======分割线======"); 13 14 del -= del2; //使用 -= 15 del("Hi!"); 16 17 Console.Read(); 18 }
【参考】微软官方文档