使用委托(C# 编程指南)

委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。 与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。 委托的类型由委托的名称定义。 下面的示例声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。

public delegate void Del(string message);

构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。 实例化委托后,委托将把对它进行的方法调用传递给方法。 调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。 这被称为调用委托。 可以将一个实例化的委托视为被包装的方法本身来调用该委托。 例如:

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

委托类型派生自 .NET Framework 中的 Delegate 类。 委托类型是密封的,不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。 由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。 这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。 这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。 以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。 此功能类似于接口所提供的封装。 有关更多信息,请参见何时使用委托而不使用接口

回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。 它允许调用方的代码成为排序算法的一部分。 下面的示例方法使用 Del 类型作为参数:

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

然后可以将上面创建的委托传递给该方法:

MethodWithCallback(1, 2, handler);

在控制台中将收到下面的输出:

The number is: 3

在将委托用作抽象概念时,MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。 MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。 此功能特别强大,因为委托的方法可以使用任意数量的参数。

将委托构造为包装实例方法时,该委托将同时引用实例和方法。 除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。 将委托构造为包装静态方法时,它只引用方法。 考虑下列声明:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

加上前面显示的静态 DelegateMethod,现在我们有三个方法可由 Del 实例进行包装。

调用委托时,它可以调用多个方法。 这称为多路广播。 若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。 例如:

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

此时,allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod。 原来的三个委托 d1、d2 和 d3 保持不变。 调用 allMethodsDelegate 时,将按顺序调用所有这三个方法。 如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。 如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。 如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。 若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。 例如:

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

由于委托类型派生自 System.Delegate,所以可在委托上调用该类定义的方法和属性。 例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:

int invocationCount = d1.GetInvocationList().GetLength(0);

在调用列表中具有多个方法的委托派生自 MulticastDelegate,这是 System.Delegate 的子类。 由于两个类都支持 GetInvocationList,所以上面的代码在两种情况下都适用。

多路广播委托广泛用于事件处理中。 事件源对象向已注册接收该事件的接收方对象发送事件通知。 为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。 事件发生时,源将调用委托。 然后,委托调用接收方的事件处理方法并传送事件数据。 给定事件的委托类型由事件源定义。 有关更多信息,请参见事件(C# 编程指南)

在编译时,对分配了两种不同类型的委托进行比较将产生编译错误。 如果委托实例静态地属于类型 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.
    System.Console.WriteLine(d == f);
}

请参见

参考

委托(C# 编程指南)

在委托中使用变体(C# 和 Visual Basic)

对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

事件(C# 编程指南)

概念

C# 编程指南

委托中的变体(C# 和 Visual Basic)