Varianz in Delegaten (C# und Visual Basic)

In .NET Framework 3.5 und Visual Studio 2008 wurde die Varianzunterstützung für den Vergleich von Methodensignaturen und Delegattypen in allen Delegaten in C# und Visual Basic eingeführt. Dies bedeutet, dass Sie Delegaten nicht nur Methoden zuweisen können, die über übereinstimmende Signaturen verfügen, sondern auch Methoden, die stärker abgeleitete Typen (Kovarianz) zurückgeben oder Parameter annehmen, die weniger stark abgeleitete Typen (Kontravarianz) aufweisen, als vom Delegattyp angegeben wurde. Dies schließt sowohl generische als auch nicht generische Delegaten ein.

Betrachten Sie beispielsweise den folgenden Code, der zwei Klassen und zwei Delegaten aufweist: generisch und nicht generisch.

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);

Wenn Sie Delegaten vom Typ SampleDelegate oder vom Typ SampleGenericDelegate<A, R> (SampleDelegate(Of A, R) in Visual Basic) erstellen, können Sie diesen Delegaten eine der folgenden Methoden zuweisen.

' 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(); }

Im folgenden Codebeispiel wird die implizite Konvertierung zwischen der Methodensignatur und dem Delegattyp veranschaulicht.

' 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;

Weitere Beispiele finden Sie unter Verwenden von Varianz in Delegaten (C# und Visual Basic) und unter Verwenden von Varianz für die generischen Delegaten Func und Action (C# und Visual Basic).

Varianz in generischen Typparametern

In .NET Framework 4 können Sie die implizite Konvertierung zwischen Delegaten aktivieren, damit generische Delegaten, für die unterschiedliche Typen von generischen Typparametern angeben werden, einander zugewiesen werden können, wenn ein Typ entsprechend den Varianzanforderungen vom jeweils anderen Typ geerbt wird.

Um die implizite Konvertierung zu aktivieren, müssen Sie generische Parameter mit dem in-Schlüsselwort oder mit dem out-Schlüsselwort in einem Delegat explizit als Kovariante oder als Kontravariante deklarieren.

Im folgenden Codebeispiel wird veranschaulicht, wie Sie einen Delegaten mit einem kovarianten generischen Typparameter erstellen können.

' 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;           
}

Wenn Sie Methodensignaturen und Delegattypen nur mithilfe der Varianzunterstützung vergleichen und weder das in-Schlüsselwort noch das out-Schlüsselwort verwenden, können Sie möglicherweise in einigen Fällen Delegaten mit identischen Lambda-Ausdrücken oder Methoden instanziieren, nicht jedoch die Delegaten einander zuweisen.

Im folgenden Codebeispiel kann SampleGenericDelegate<String> nicht explizit in SampleGenericDelegate<Object> konvertiert werden (SampleGenericDelegate(Of String) in SampleGenericDelegate(Of Object) in Visual Basic), obwohl String das Object erbt. Sie können dieses Problem beheben, indem Sie dem generischen Parameter T mit dem out-Schlüsselwort markieren.

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;



}

Generische Delegaten mit varianten Typparametern in .NET Framework

In .NET Framework 4 wird die Varianzunterstützung für generische Typparameter in mehreren vorhandenen generischen Delegaten eingeführt:

Weitere Informationen und Beispiele finden Sie unter Verwenden von Varianz für die generischen Delegaten Func und Action (C# und Visual Basic).

Deklarieren von varianten Typparametern in generischen Delegaten

Wenn ein generischer Delegat ko- oder kontravariante generische Typparameter aufweist, kann er als varianter generischer Delegat bezeichnet werden.

Mit dem out-Schlüsselwort können Sie einen generischen Typparameter in einem generischen Delegaten als Kovariante deklarieren. Der kovariante Typ kann nur als Methodenrückgabetyp verwendet werden, nicht jedoch als Methodenargumenttyp. Im folgenden Codebeispiel wird gezeigt, wie ein kovarianter generischer Delegat deklariert wird.

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

Mit dem in-Schlüsselwort können Sie einen generischen Typparameter in einem generischen Delegaten als Kontravariante deklarieren. Der kontravariante Typ kann nur als Methodenargumenttyp verwendet werden, nicht jedoch als Methodenrückgabetyp verwendet werden. Im folgenden Codebeispiel wird gezeigt, wie ein kontravarianter generischer Delegat deklariert wird.

Public Delegate Sub DContravariant(Of In A)(ByVal a As A)
public delegate void DContravariant<in A>(A a);

Wichtig

ByRef-Parameter in Visual Basic sowie ref- und out-Parameter in C# können nicht als variant gekennzeichnet werden.

Varianz und Kovarianz können im gleichen Delegaten unterstützen werden, jedoch für unterschiedliche Typparameter. Dies wird im folgenden Beispiel gezeigt.

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);

Instanziieren und Aufrufen von varianten generischen Delegaten

Variante Delegaten können analog zu nicht varianten Delegaten instanziiert und aufgerufen werden. Im folgenden Beispiel wird der Delegat durch einen Lambda-Ausdruck instanziiert.

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

Kombinieren von varianten generischen Delegaten

Variante Delegaten sollten nicht kombiniert werden. Die Combine-Methode unterstützt nicht die Konvertierung varianter Delegaten, sondern erwartet Delegaten von genau dem gleichen Typ. Dies kann zu einer Laufzeitausnahme führen, wenn Sie mit der Combine-Methode (in C# und Visual Basic) oder mit dem Operator + (in C#) Delegaten kombinieren, wie im folgenden Codebeispiel gezeigt.

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);

Varianz in generischen Typparametern für Wert- und Verweistypen

Varianz in generischen Typparameter wird nur für Verweistypen unterstützt. Beispielsweise kann DVariant<int> (DVariant(Of Int) in Visual Basic) nicht implizit in DVariant<Object> oder DVaraint<long> (DVariant(Of Object) oder DVaraint(Of Long) in Visual Basic) konvertiert werden, da die ganze Zahl ein Werttyp ist.

Im folgenden Beispiel wird veranschaulicht, dass Varianz in generischen Typparametern für Werttypen nicht unterstützt wird.

' 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;            
}

Gelockerte Delegatkonvertierung in Visual Basic

Die in Visual Basic 2008 eingeführte gelockerte Delegatkonvertierung bietet eine größere Flexibilität beim Vergleich von Methodensignaturen und Delegattypen. Beispielsweise können Sie beim Zuweisen einer Methode zu einem Delegaten auf Parameterspezifikationen und Rückgabewerte verzichten. Weitere Informationen finden Sie unter Gelockerte Delegatenkonvertierung (Visual Basic).

Siehe auch

Aufgaben

Gewusst wie: Kombinieren von Delegaten (Multicastdelegaten) (C#-Programmierhandbuch)

Referenz

Verwenden von Varianz für die generischen Delegaten Func und Action (C# und Visual Basic)

Weitere Ressourcen

Generika in .NET Framework