.NET Framework 3.5 引入了变体支持,用于在 C# 中匹配所有委托的方法签名和委托类型。 这表明不仅可以将具有匹配签名的方法分配给委托,还可以将返回派生程度较大的派生类型的方法分配给委托(协变),或者如果方法所接受参数的派生类型所具有的派生程度小于委托类型指定的程度(逆变),也可将其分配给委托。 这包括泛型委托和非泛型委托。
例如,思考以下代码,该代码具有两个类和两个委托:泛型和非泛型。
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);
创建 SampleDelegate 或 SampleGenericDelegate 类型的委托时,可以将以下任一方法分配给这些委托。
// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }
// The return type is more derived.
public static Second ASecondRSecond(Second second)
{ return new Second(); }
// The argument type is less derived.
public static First AFirstRFirst(First first)
{ return new First(); }
// The return type is more derived
// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }
以下代码示例说明了方法签名与委托类型之间的隐式转换。
// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;
// Assigning a method with a matching signature to a generic delegate.
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;
在 .NET Framework 4 或更高版本中,可以启用委托之间的隐式转换,以便在具有泛型类型参数所指定的不同类型按变体的要求继承自对方时,可以将这些类型的泛型委托分配给对方。
要启用隐式转换,必须使用 in 或 out 关键字将委托中的泛型参数显式声明为协变或逆变。
以下代码示例演示了如何创建一个具有协变泛型类型参数的委托。
// Type T is declared covariant by using the out keyword.
public delegate T SampleGenericDelegate <out T>();
public static void Test()
{
SampleGenericDelegate <String> dString = () => " ";
// You can assign delegates to each other,
// because the type T is declared covariant.
SampleGenericDelegate <Object> dObject = dString;
}
如果仅使用变体支持来匹配方法签名和委托类型,且不使用 in 和 out 关键字,则可能会发现有时可以使用相同的 lambda 表达式或方法实例化委托,但不能将一个委托分配给另一个委托。
在以下代码示例中,SampleGenericDelegate 不能显式转换为 SampleGenericDelegate,尽管 String 继承 Object。 可以使用 out 关键字标记 泛型参数 T 解决此问题。
public delegate T SampleGenericDelegate<T>();
public static void Test()
{
SampleGenericDelegate<String> dString = () => " ";
// You can assign the dObject delegate
// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";
// The following statement generates a compiler error
// because the generic type T is not marked as covariant.
// SampleGenericDelegate
}
.NET Framework 4 在几个现有泛型委托中引入了泛型类型参数的变体支持:
System 命名空间的 Action 委托,例如 Action 和 Action
System 命名空间的 Func 委托,例如 Func 和 Func
Predicate 委托
Comparison 委托
Converter 委托
如果泛型委托具有协变或逆变泛型类型参数,则该委托可被称为“变体泛型委托”。
可以使用 out 关键字声明泛型委托中的泛型类型参数协变。 协变类型只能用作方法返回类型,而不能用作方法参数的类型。 以下代码示例演示了如何声明协变泛型委托。
public delegate R DCovariant<out R>();
可以使用 in 关键字声明泛型委托中的泛型类型参数逆变。 逆变类型只能用作方法参数的类型,而不能用作方法返回类型。 以下代码示例演示了如何声明逆变泛型委托。
public delegate void DContravariant<in A>(A a);
C# 中的 ref、in 和 out 参数不能标记为变体。
可以在同一个委托中支持变体和协变,但这只适用于不同类型的参数。 这在下面的示例中显示。
public delegate R DVariant<in A, out R>(A a);
可以像实例化和调用固定委托一样,实例化和调用变体委托。 在以下示例中,该委托通过 lambda 表达式进行实例化。
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");
请勿合并变体委托。 Combine 方法不支持变体委托转换,并且要求委托的类型完全相同。 这会导致在使用 Combine 方法或使用 + 运算符合并委托时出现运行时异常,如以下代码示例中所示。
Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time.
// Action actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);
泛型类型参数的变体仅支持引用类型。 例如,DVariant 不能隐式转换为 DVariant 或 DVariant,因为整数是值类型。
以下示例演示了泛型类型参数中的变体不支持值类型。
// The type T is covariant.
public delegate T DVariant<out T>();
// The type T is invariant.
public delegate T DInvariant<T>();
public static void Test()
{
int i = 0;
DInvariant<int> dInt = () => i;
DVariant<int> dVariantInt = () => i;
// All of the following statements generate a compiler error
// because type variance in generic parameters is not supported
// for value types, even if generic type parameters are declared variant.
// DInvariant
// DInvariant dLong = dInt;
// DVariant
// DVariant dVariantLong = dVariantInt;
}
向委托分配方法时,协变和逆变为匹配委托类型和方法签名提供了灵活性。 协变允许方法具有的派生返回类型多于委托中定义的类型。 逆变允许方法具有的派生参数类型少于委托类型中的类型。
本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。 DogsHandler 返回的数据类型属于 Dogs 类型,它派生自委托中定义的 Mammals 类型。
class Mammals {}
class Dogs : Mammals {}
class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();
public static Mammals MammalsHandler()
{
return null;
}
public static Dogs DogsHandler()
{
return null;
}
static void Test()
{
HandlerMethod handlerMammals = MammalsHandler;
// Covariance enables this assignment.
HandlerMethod handlerDogs = DogsHandler;
}
}
本示例演示如何将委托与具有参数的方法一起使用,这些参数的类型是委托签名参数类型的基类型。 通过逆变可以使用一个事件处理程序而不是多个单独的处理程序。 下面的示例使用两个委托:
定义 Button.KeyDown 事件签名的 KeyEventHandler 委托。 其签名为:
public delegate void KeyEventHandler(object sender, KeyEventArgs e)
定义 Button.MouseClick 事件签名的 MouseEventHandler 委托。 其签名为:
public delegate void MouseEventHandler(object sender, MouseEventArgs e)
该示例定义了一个具有 EventArgs 参数的事件处理程序,并使用它来处理 Button.KeyDown 和 Button.MouseClick 事件。 它可以这样做是因为 EventArgs 是 KeyEventArgs 和 MouseEventArgs 的基类型。
// Event handler that accepts a parameter of the EventArgs type.
private void MultiHandler(object sender, System.EventArgs e)
{
label1.Text = System.DateTime.Now.ToString();
}
public Form1()
{
InitializeComponent();
// You can use a method that has an EventArgs parameter,
// although the event expects the KeyEventArgs parameter.
this.button1.KeyDown += this.MultiHandler;
// You can use the same method
// for an event that expects the MouseEventArgs parameter.
this.button1.MouseClick += this.MultiHandler;
}
这些示例演示如何使用 Func 和 Action 泛型委托中的协变和逆变来启用重用方法并为代码中提供更多的灵活性。
下例阐释了泛型 Func 委托中的协变支持的益处。 FindByTitle 方法采用 String 类型的一个参数,并返回 Employee 类型的一个对象。 但是,可将此方法分配给 Func
// Simple hierarchy of classes.
public class Person { }
public class Employee : Person { }
class Program
{
static Employee FindByTitle(String title)
{
// This is a stub for a method that returns
// an employee that has the specified title.
return new Employee();
}
static void Test()
{
// Create an instance of the delegate without using variance.
Func<String, Employee> findEmployee = FindByTitle;
// The delegate expects a method to return Person,
// but you can assign it a method that returns Employee.
Func<String, Person> findPerson = FindByTitle;
// You can also assign a delegate
// that returns a more derived type
// to a delegate that returns a less derived type.
findPerson = findEmployee;
}
}
下例阐释了泛型 Action 委托中的逆变支持的益处。 AddToContacts 方法采用 Person 类型的一个参数。 但是,可将此方法分配给 Action 委托,因为 Employee 继承 Person。
public class Person { }
public class Employee : Person { }
class Program
{
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}
static void Test()
{
// Create an instance of the delegate without using variance.
Action<Person> addPersonToContacts = AddToContacts;
// The Action delegate expects
// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;
// You can also assign a delegate
// that accepts a less derived parameter to a delegate
// that accepts a more derived parameter.
addEmployeeToContacts = addPersonToContacts;
}
}