委托概述
委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 你可以通过委托实例调用方法。
委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法。 你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法。
委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定。
委托具有以下属性:
委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不必与委托类型完全匹配。 有关详细信息,请参阅使用委托中的变体。
使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。
使用委托
委托对象通常可采用两种方式进行构造,一种是提供委托将封装的方法的名称,另一种是使用 lambda 表达式。 对委托进行实例化后,委托会将对其进行的方法调用传递到该方法。 调用方传递到委托的参数将传递到该方法,并且委托会将方法的返回值(如果有)返回到调用方。 这被称为调用委托。 实例化的委托可以按封装的方法本身进行调用。 例如:
//delegate 声明一个委托
public delegate void Del(string message);
//为委托创建一个方法。
public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}
在Main方法中
//实例化委托
Del handler = DelegateMethod;
//添加数据
handler("我是一个委托");
委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 ,也不能从其派生出自定义类。 由于实例化的委托是一个对象,因此可以作为参数传递或分配给一个属性。 这允许方法接受委托作为参数并在稍后调用委托。 这被称为异步回调,是在长进程完成时通知调用方的常用方法。 当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。 功能类似于封装接口提供的功能。
回调的另一个常见用途是定义自定义比较方法并将该委托传递到短方法。 它允许调用方的代码成为排序算法的一部分。 以下示例方法使用 Del 类型作为参数:
//自定义方法调用委托(回调)
public static void MethodWithCallback(int param1, int param2, Del callback)
{
callback("两个数的和为:" + (param1 + param2).ToString());
}
然后,你可以将上面创建的委托传递到该方法:
MethodWithCallback(1, 2, handler);
输出:
两个数的和为:3
抽象方式使用委托
以抽象方式使用委托时,MethodWithCallback 不需要直接调用控制台,记住,其不必设计为具有控制台。 MethodWithCallback 的作用是简单准备字符串并将字符串传递到其他方法。 由于委托的方法可以使用任意数量的参数,此功能特别强大。
当委托构造为封装实例方法时,委托将同时引用实例和方法。 委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。 当委托构造为封装静态方法时,委托仅引用方法。 请考虑以下声明:
public class MethodClass
{
public void Method1(string message) { }
public void Method2(string message) { }
}
加上之前显示的静态 DelegateMethod,我们现在已有三个 Del 实例可以封装的方法。
调用时,委托可以调用多个方法。 这被称为多播。 若要向委托的方法列表(调用列表)添加其他方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。 例如:
//使用委托调用多个方法(多播委托)
var obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//这两种类型的作业都有效。
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
此时,allMethodsDelegate 的调用列表中包含三个方法,分别为 Method1、Method2 和 DelegateMethod。 原有的三个委托(d1、d2 和 d3)保持不变。 调用 allMethodsDelegate 时,将按顺序调用所有三个方法。 如果委托使用引用参数,引用将按相反的顺序传递到所有这三个方法,并且一种方法进行的任何更改都将在另一种方法上见到。 当方法引发未在方法内捕获到的异常时,该异常将传递到委托的调用方,并且不会调用调用列表中的后续方法。 如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。 若要删除调用列表中的方法,请使用减法运算符或减法赋值运算符( 或 -=)。
查询委托调用列表中方法的数量
由于委托类型派生自 System.Delegate,因此可以在委托上调用该类定义的方法和属性。 例如,若要查询委托调用列表中方法的数量,你可以编写:
//查询调用委托数量
//int invocationCount = d1.GetInvocationList().GetLength(0);
int invocationCount = allMethodsDelegate.GetInvocationList().GetLength(0);
Console.WriteLine(invocationCount);
调用列表中具有多个方法的委托派生自 MulticastDelegate,该类属于 System.Delegate 的子类。 由于这两个类都支持 GetInvocationList,因此在其他情况下,上述代码也将产生作用。
多播委托广泛用于事件处理中。 事件源对象将事件通知发送到已注册接收该事件的接收方对象。 若要注册一个事件,接收方需要创建用于处理该事件的方法,然后为该方法创建委托并将委托传递到事件源。 事件发生时,源调用委托。 然后,委托将对接收方调用事件处理方法,从而提供事件数据。 给定事件的委托类型由事件源确定。 有关详细信息,请参阅事件。
在编译时比较分配的两个不同类型的委托将导致编译错误。 如果委托实例是静态的 System.Delegate 类型,则允许比较,但在运行时将返回 false。
delegate void Delegate1();
delegate void Delegate2();
static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
// Compile-time error.
//Console.WriteLine(d == e);
// OK at compile-time. False if the run-time type of f
// is not the same as that of d.
Console.WriteLine(d == f);
}
带有命名方法的委托与匿名方法
委托可以与命名方法相关联。 使用命名方法实例化委托时,该方法作为参数传递。
这称为使用命名方法。 使用命名方法构造的委托可以封装静态方法或实例方法。 命名方法是在早期版本的 C# 中实例化委托的唯一方式。 但是,如果创建新方法会造成多余开销,C# 允许你实例化委托并立即指定调用委托时委托将处理的代码块。 代码块可包含 Lambda 表达式或匿名方法。
作为委托参数传递的方法必须具有与委托声明相同的签名。 委托实例可以封装静态方法或实例方法。
备注:
尽管委托可以使用 out 参数,但不建议将该委托与多播事件委托配合使用,因为你无法知道将调用哪个委托。
示例
以下是声明和使用委托的简单示例。 请注意,委托 Del 与关联的方法 MultiplyNumbers 具有相同的签名
// Declare a delegate
delegate void Del(int i, double j);
class MathClass
{
static void Main()
{
MathClass m = new MathClass();
// 使用“MultiplyNumbers”进行委托实例化
Del d = m.MultiplyNumbers;
// 调用委托对象.
Console.WriteLine("使用 'MultiplyNumbers':");
for (int i = 1; i <= 5; i++)
{
d(i, 2);
}
//在调试模式下保持控制台窗口打开
Console.WriteLine("按任意键退出");
Console.ReadKey();
}
// 调用委托对象
void MultiplyNumbers(int m, double n)
{
Console.Write(m * n + " ");
}
}
/* 输出:
Invoking the delegate using 'MultiplyNumbers':
2 4 6 8 10
*/
在下面的示例中,一个委托映射到静态方法和实例方法,并返回来自两种方法的具体信息。
// Declare a delegate
delegate void Del();
class SampleClass
{
public void InstanceMethod()
{
Console.WriteLine("A message from the instance method.");
}
static public void StaticMethod()
{
Console.WriteLine("A message from the static method.");
}
}
class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();
// Map the delegate to the instance method:
Del d = sc.InstanceMethod;
d();
// Map to the static method:
d = SampleClass.StaticMethod;
d();
}
}
/* Output:
A message from the instance method.
A message from the static method.
*/
合并委托(多播委托)
此示例演示如何创建多播委托。 委托对象的一个有用属性在于可通过使用 运算符将多个对象分配到一个委托实例。 多播委托包含已分配委托列表。 此多播委托被调用时会依次调用列表中的委托。 仅可合并类型相同的委托。
- 运算符可用于从多播委托中删除组件委托。
using System;
// Define a custom delegate that has a string parameter and returns void.
delegate void CustomDel(string s);
class TestClass
{
// Define two methods that have the same signature as CustomDel.
static void Hello(string s)
{
Console.WriteLine($" Hello, {s}!");
}
static void Goodbye(string s)
{
Console.WriteLine($" Goodbye, {s}!");
}
static void Main()
{
// Declare instances of the custom delegate.
CustomDel hiDel, byeDel, multiDel, multiMinusHiDel;
// In this example, you can omit the custom delegate if you
// want to and use Action<string> instead.
//Action<string> hiDel, byeDel, multiDel, multiMinusHiDel;
// Create the delegate object hiDel that references the
// method Hello.
hiDel = Hello;
// Create the delegate object byeDel that references the
// method Goodbye.
byeDel = Goodbye;
// The two delegates, hiDel and byeDel, are combined to
// form multiDel.
multiDel = hiDel + byeDel;
// Remove hiDel from the multicast delegate, leaving byeDel,
// which calls only the method Goodbye.
multiMinusHiDel = multiDel - hiDel;
Console.WriteLine("Invoking delegate hiDel:");
hiDel("A");
Console.WriteLine("Invoking delegate byeDel:");
byeDel("B");
Console.WriteLine("Invoking delegate multiDel:");
multiDel("C");
Console.WriteLine("Invoking delegate multiMinusHiDel:");
multiMinusHiDel("D");
}
}
/* Output:
Invoking delegate hiDel:
Hello, A!
Invoking delegate byeDel:
Goodbye, B!
Invoking delegate multiDel:
Hello, C!
Goodbye, C!
Invoking delegate multiMinusHiDel:
Goodbye, D!
*/