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

.NET Framework 3.5 和 Visual Studio 2008 引入了变体支持,用于在 C# 和 Visual Basic 的所有委托中匹配方法签名和委托类型。 这意味着,您不仅可以为委托指派具有匹配签名的方法,而且可以指派这样的方法:它们返回与委托类型指定的派生类型相比,派生程度更大的类型(协变),或者接受相比之下,派生程度更小的类型的参数(逆变)。 这包括泛型委托和非泛型委托。

例如,请考虑下面的代码,其中涉及两个类和两个委托,泛型和非泛型各一个。

Public Class First
End Class

Public Class Second
    Inherits First
End Class

Public Delegate Function SampleDelegate(ByVal a As Second) As First
Public Delegate Function SampleGenericDelegate(Of A, R)(ByVal a As A) As R
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

创建 SampleDelegate 或 SampleGenericDelegate<A, R>(在 Visual Basic 中为 SampleDelegate(Of A, R))类型的委托时,可以为这些委托指派以下任一方法。

' Matching signature.
Public Shared Function ASecondRFirst(
    ByVal second As Second) As First
    Return New First()
End Function

' The return type is more derived.
Public Shared Function ASecondRSecond(
    ByVal second As Second) As Second
    Return New Second()
End Function

' The argument type is less derived.
Public Shared Function AFirstRFirst(
    ByVal first As First) As First
    Return New First()
End Function

' The return type is more derived 
' and the argument type is less derived.
Public Shared Function AFirstRSecond(
    ByVal first As First) As Second
    Return New Second()
End Function
// Matching signature.
public static First ASecondRFirst(Second first)
{ 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.
Dim dNonGeneric As SampleDelegate = AddressOf 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.
Dim dNonGenericConversion As SampleDelegate = AddressOf AFirstRSecond

' Assigning a method with a matching signature to a generic delegate.
' No conversion is necessary.
Dim dGeneric As SampleGenericDelegate(Of Second, First) = AddressOf ASecondRFirst
' Assigning a method with a more derived return type 
' and less derived argument type to a generic delegate.
' The implicit conversion is used.
Dim dGenericConversion As SampleGenericDelegate(Of Second, First) = AddressOf AFirstRSecond
// 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;

有关更多示例,请参见在委托中使用变体(C# 和 Visual Basic)对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

泛型类型参数中的变体

在 .NET Framework 4 中,您可以在委托之间进行隐式转换,这样,如果泛型类型参数指定的不同类型按照变体的要求彼此相互继承,则可以将具有这些类型的泛型委托指派给彼此。

若要启用隐式转换,必须使用 in 或 out 关键字,将委托中的泛型参数显式声明为协变或逆变。

下面的代码示例演示如何创建具有协变泛型类型参数的委托。

' Type T is declared covariant by using the out keyword.
Public Delegate Function SampleGenericDelegate(Of Out T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "
    ' You can assign delegates to each other,
    ' because the type T is declared covariant.
    Dim dObject As SampleGenericDelegate(Of Object) = dString
End Sub
// 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 表达式或方法来实例化多个委托,但无法将一个委托指派给另一个委托。

在下面的代码示例中,虽然 String 继承 Object,但无法将 SampleGenericDelegate<String> 显式转换为 SampleGenericDelegate<Object>(在 Visual Basic 中为将 SampleGenericDelegate(Of String) 转换为 SampleGenericDelegate(Of Object))。 您可以使用 out 关键字来标记泛型参数 T,从而解决此问题。

Public Delegate Function SampleGenericDelegate(Of T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "

    ' 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.
    Dim dObject As SampleGenericDelegate(Of Object) = Function() " "

    ' The following statement generates a compiler error
    ' because the generic type T is not marked as covariant.
    ' Dim dObject As SampleGenericDelegate(Of Object) = dString


End Sub
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 <Object> dObject = dString;



}

.NET Framework 中具有变体类型参数的泛型委托

.NET Framework 4 中为若干现有泛型委托中的泛型类型参数引入了变体支持:

有关更多信息和示例,请参见对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

在泛型委托中声明变体类型参数

如果泛型委托具有协变或逆变泛型类型参数,则可以将该委托称为“变体泛型委托”。

您可以使用 out 关键字,在泛型委托中将泛型类型参数声明为协变。 协变类型只能用作方法返回类型,不能用作方法参数的类型。 下面的代码示例演示如何声明协变泛型委托。

Public Delegate Function DCovariant(Of Out R)() As R
public delegate R DCovariant<out R>();

您可以使用 in 关键字,在泛型委托中将泛型类型参数声明为逆变。 逆变类型只能用作方法参数的类型,不能用作方法返回类型。 下面的代码示例演示如何声明逆变泛型委托。

Public Delegate Sub DContravariant(Of In A)(ByVal a As A)
public delegate void DContravariant<in A>(A a);
重要说明重要事项

不能将 Visual Basic 中的 ByRef 参数和 C# 中的 ref 与 out 参数标记为变体。

此外还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数。 下例演示了这种情况。

Public Delegate Function DVariant(Of In A, Out R)(ByVal a As A) As R
public delegate R DVariant<in A, out R>(A a);

实例化和调用变体泛型委托

您可以实例化和调用变体委托,就像实例化和调用固定委托一样。 在下例中,委托由 lambda 表达式进行实例化。

Dim dvariant As DVariant(Of String, String) = Function(str) str + " "
dvariant("test")
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");

组合变体泛型委托

不应该组合变体委托。 Combine 方法不支持变体委托转换,并且希望委托的类型完全相同。 这可能会在使用 Combine 方法(在 C# 和 Visual Basic 中)或使用 + 运算符(在 C# 中)组合委托时导致运行时异常,如下面的代码示例所示。

Dim actObj As Action(Of Object) = Sub(x) Console.WriteLine("object: {0}", x)
Dim actStr As Action(Of String) = Sub(x) Console.WriteLine("string: {0}", x)

' The following statement throws an exception at run time.
' Dim actCombine = [Delegate].Combine(actStr, actObj)
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<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);

涉及值类型和引用类型的泛型类型参数变体问题

只有引用类型才支持泛型类型参数的变体。 例如,无法将 DVariant<int>(在 Visual Basic 中为 DVariant(Of Int))隐式转换为 DVariant<Object> 或 DVaraint<long>(在 Visual Basic 中为 DVariant(Of Object) 或 DVaraint(Of Long)),因为整数为值类型。

下例说明了值类型不支持泛型类型参数中的变体。

' The type T is covariant.
Public Delegate Function DVariant(Of Out T)() As T
' The type T is invariant.
Public Delegate Function DInvariant(Of T)() As T
Sub Test()
    Dim i As Integer = 0
    Dim dInt As DInvariant(Of Integer) = Function() i
    Dim dVaraintInt As DVariant(Of Integer) = Function() 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.
    ' Dim dObject As DInvariant(Of Object) = dInt
    ' Dim dLong As DInvariant(Of Long) = dInt
    ' Dim dVaraintObject As DInvariant(Of Object) = dInt
    ' Dim dVaraintLong As DInvariant(Of Long) = dInt
End Sub
// 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<Object> dObject = dInt;
    // DInvariant<long> dLong = dInt;
    // DVariant<Object> dVariantObject = dVariantInt;
    // DVariant<long> dVariantLong = dVariantInt;            
}

Visual Basic 中的宽松委托转换

Visual Basic 2008 中引入了宽松委托转换,它为匹配方法签名和委托类型提供了更大的灵活性。 例如,它允许忽略参数规范,允许在将方法指派给委托时忽略函数返回值。 有关更多信息,请参见宽松委托转换 (Visual Basic)

请参见

任务

如何:合并委托(多路广播委托)(C# 编程指南)

参考

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

概念

.NET Framework 中的泛型