System.Delegate 和 delegate 关键字

以前

本文介绍支持委托的 .NET 中的类,以及这些类如何映射到 delegate 关键字。

定义委托类型

让我们从“委托”关键字开始,因为这是你在处理委托时将主要使用的。 当您使用 delegate 关键字时,编译器生成的代码将映射到调用 DelegateMulticastDelegate 类成员的方法调用。

使用类似于定义方法签名的语法定义委托类型。 只需将 delegate 关键字添加到定义中。

让我们继续使用 List.Sort() 方法作为示例。 第一步是为比较委托创建类型:

// From the .NET Core library

// Define the delegate type:
public delegate int Comparison<in T>(T left, T right);

编译器生成一个类,派生自 System.Delegate 该类,该类与所使用的签名匹配(在本例中,一个返回整数的方法,并且具有两个参数)。 该委托的类型为 Comparison. Comparison委托类型是泛型类型。 有关详细信息,请参阅 泛型类和方法

请注意,语法可能显示为声明变量,但它实际上是声明 类型。 可以在类内、直接在命名空间内,甚至全局命名空间中定义委托类型。

注释

不建议直接在全局命名空间中声明委托类型(或其他类型)。

编译器还会为此新类型生成添加和删除处理程序,以便此类的客户端可以从实例的调用列表中添加和删除方法。 编译器将强制添加或删除的方法的签名与声明方法时使用的签名匹配。

声明委托的实例

定义委托后,可以创建该类型的实例。 与 C# 中的所有变量一样,不能直接在命名空间或全局命名空间中声明委托实例。

// inside a class definition:

// Declare an instance of that type:
public Comparison<T> comparator;

变量的类型是 Comparison<T>前面定义的委托类型。 变量的名称为 comparator.

上面的代码片段声明了类中的成员变量。 还可以声明局部变量的委托变量或方法的参数。

调用委托

调用委托即可调用该委托调用列表中的方法。 在 Sort() 方法中,代码将调用比较方法来确定放置对象的顺序:

int result = comparator(left, right);

在上面的行中,代码 调用 关联到委托的方法。 将变量视为方法名称,并使用普通方法调用语法调用变量。

该代码行基于不安全的假设:无法保证目标已经添加到委托对象。 如果未附加任何目标,则上面的行将导致抛出 NullReferenceException。 用于解决此问题的习惯比简单的 null 检查更为复杂,稍后将在本 系列中介绍。

分配、添加和删除调用目标

这就是定义委托类型的方式,以及如何声明和调用委托实例。

开发人员若想使用List.Sort()方法,需要定义一个方法,使其签名与委托类型相匹配,然后将该方法分配给排序方法使用的委托。 此分配将方法添加到该委托对象的调用列表中。

假设你想要按字符串长度对字符串列表进行排序。 比较函数可能如下所示:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

该方法声明为私有方法。 这没有什么不对。 你可能不希望此方法成为公共接口的一部分。 当附加到委托上时,它仍可用作比较的手段。 调用代码将此方法附加到委托对象的目标列表,并且可以通过该委托访问它。

通过将该方法传递给方法 List.Sort() 来创建该关系:

phrases.Sort(CompareLength);

请注意,方法名称是使用的,没有括号。 使用该方法作为参数告知编译器将方法引用转换为可用作委托调用目标的引用,并将该方法附加为调用目标。

你还可以通过声明一个Comparison<string>类型的变量并进行赋值来明确表达:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

在用作委托目标的方法是小型方法的用法中,通常使用 lambda 表达式 语法来执行赋值:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

后面的部分中,有关将 lambda 表达式用于委托目标的内容会有更详细的介绍。

Sort() 示例通常将一个目标方法附加到委托上。 但是,委托对象确实支持将多个目标方法附加到委托对象的调用列表。

委托类和 MulticastDelegate 类

上述语言支持提供了在处理委托时通常需要的各种功能和支持。 这些功能基于 .NET Core 框架中的两个类构建: DelegateMulticastDelegate

System.Delegate 类及其单个直接子类 System.MulticastDelegate提供框架支持,用于创建委托、将方法注册为委托目标,以及调用注册为委托目标的所有方法。

有趣的是,System.DelegateSystem.MulticastDelegate 这些类本身都不是委托类型。 它们确实为所有特定的委托类型提供了基础。 同一语言设计过程要求你无法声明派生自 DelegateMulticastDelegate的类。 C# 语言规则禁止它。

相反,当您使用 C# 语言关键字声明委托类型时,C# 编译器会创建一个从 MulticastDelegate 派生的类的实例。

此设计源于 C# 和 .NET 的第一个版本。 设计团队的一个目标是确保语言在使用委托时强制实施类型安全。 这意味着确保使用正确的类型和参数数调用委托。 在编译时正确指定了任意返回类型。 委托是 .NET 1.0 版本的一部分,引入泛型之前就在使用。

强制实施此类型安全的最佳方式是编译器创建表示正在使用的方法签名的具体委托类。

即使不能直接创建派生类,也可以使用在这些类上定义的方法。 让我们探讨一下在处理委托时将使用的最常见方法。

第一个也是最重要的事实是,你处理的每个委托都派生自 MulticastDelegate。 多播委托意味着在通过委托调用时可以调用多个方法目标。 原始设计考虑区分只能附加和调用一个目标方法的委托,以及可以附加和调用多个目标方法的委托。 事实证明,这种区别在实践中没有最初想象的那么有用。 这两个不同的类已经创建,自其初始公开发布以来一直在框架中。

您在使用委托时最常用的方法是Invoke()BeginInvoke() / EndInvoke()Invoke() 将调用已附加到特定委托实例的所有方法。 正如您上面所看到的,您通常通过委托变量的方法调用语法来调用委托。 正如 本系列后面的内容所示,有一些模式可以直接使用这些方法。

现在,你已经了解了语言语法和支持委托的类,接下来让我们来看看如何使用、创建和调用强类型委托。

下一步