委派中的變異數 (C# 和 Visual Basic)
.NET Framework 3.5 和 Visual Studio 2008 在 C# 及 Visual Basic 的委派中加入了使方法簽章與委派型別相符的變異數支援。 這表示您可以指派給委派的,不只是具有相符簽章的方法,而且還有與委派型別所指定的型別相比,傳回衍生程度較大型別 (共變數) 或接受衍生程度較小型別 (Contravariance) 的方法。 這包括泛型與非泛型委派。
例如,試想下列程式碼,其中有兩個類別以及兩個泛型與非泛型委派。
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 關鍵字,將委派中的泛型參數明確宣告為 Covariant 或 Contravariant。
在下列程式碼範例中,會示範如何建立具有 Covariant 泛型型別參數的委派。
' 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 中具有 Variant 型別參數的泛型委派
.NET Framework 4 針對數個現有泛型委派中的泛型型別參數加入了變異數支援。
來自 System 命名空間的 Action 委派,例如 Action<T> 和 Action<T1, T2>
來自 System 命名空間的 Func 委派,例如 Func<TResult> 和 Func<T, TResult>
Predicate<T> 委派
如需詳細資訊與範例,請參閱針對 Func 與 Action 委派使用變異數 (C# 和 Visual Basic)。
宣告泛型委派中的 Variant 型別參數
如果泛型委派具有 Covariant 或 Contravariant 泛型型別參數,即可稱為「Variant 泛型委派」(Variant Generic Delegate)。
您可以使用 out 關鍵字,將泛型委派中的泛型型別參數宣告為 Covariant。 Covariant 型別只能當做方法傳回型別,而不能當做方法引數的型別使用。 在下列程式碼範例中,會示範如何宣告 Covariant 泛型委派。
Public Delegate Function DCovariant(Of Out R)() As R
public delegate R DCovariant<out R>();
您可以使用 in 關鍵字,將泛型委派中的泛型型別參數宣告為 Contravariant。 Contravariant 型別只能當做方法引數的型別,而不能當做方法傳回型別使用。 在下列程式碼範例中,會示範如何宣告 Contravariant 泛型委派。
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 參數不可以標示為 Variant。
您也可以在同一個委派中同時支援變異數和共變數,但必須是針對不同的型別參數來宣告。 這在下列範例中顯示。
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);
執行個體化和叫用 Variant 泛型委派
您可以執行個體化和叫用 Variant 委派,就如同執行個體化和叫用非變異委派一樣。 在下列範例中,委派是由 Lambda 運算式執行個體化。
Dim dvariant As DVariant(Of String, String) = Function(str) str + " "
dvariant("test")
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");
結合 Variant 泛型委派
您不應該結合 Variant 委派。 Combine 方法不支援 Variant 委派轉換,預期委派是完全相同的型別。 當您使用 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)。
請參閱
工作
HOW TO:組合委派 (多點傳送委派) (C# 程式設計手冊)
參考
針對 Func 與 Action 委派使用變異數 (C# 和 Visual Basic)