Freigeben über


Varianz bei Delegaten (C#)

Mit .NET Framework 3.5 wurde die Unterstützung von Varianz eingeführt, um Methodensignaturen und Delegattypen in allen Delegaten in C# vergleichen zu können. Dies bedeutet, dass Sie Stellvertretungen nicht nur Methoden mit übereinstimmenden Signaturen zuweisen können, sondern auch Methoden, die mehr abgeleitete Typen (Kovarianz) zurückgeben oder Parameter akzeptieren, die weniger abgeleitete Typen (Kontravarianz) als die vom Delegattyp angegeben haben. Dies umfasst sowohl generische als auch nicht generische Delegaten.

Betrachten Sie beispielsweise den folgenden Code mit zwei Klassen und zwei Delegaten: generisch und nicht generisch.

public class First { }  
public class Second : First { }  
public delegate First SampleDelegate(Second a);  
public delegate R SampleGenericDelegate<A, R>(A a);  

Wenn Sie Delegaten der Typen SampleDelegate oder SampleGenericDelegate<A, R> erstellen, können Sie diesen Delegaten eine der folgenden Methoden zuweisen.

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

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.  
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 Using Variance in Delegates (C#) (Verwenden von Varianz bei Delegaten (C#)) und Using Variance for Func and Action Generic Delegates (C#) (Verwenden von Varianz für die generischen Delegaten Func und Action (C#)).

Varianz in generischen Typparametern

In .NET Framework 4 oder höher können Sie die implizierte Konvertierung zwischen Delegaten aktivieren. Das bedeutet, dass generische Delegaten, die über verschiedene von generischen Typparametern angegebene Typen verfügen, sich gegenseitig zugewiesen werden können, wenn die Typen voneinander geerbt werden. Dies ist für die Varianz erforderlich.

Um die implizite Konvertierung zu aktivieren, müssen Sie generische Parameter in einem Delegat explizit als kovariant oder kontravariant deklarieren, indem Sie die Schlüsselwörter in oder out verwenden.

Das folgende Codebeispiel zeigt, wie Sie einen Delegaten erstellen können, der über einen kovarianten generischen Typparameter verfügt.

// 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 nur Abweichungsunterstützung verwenden, um Methodensignaturen mit Delegatentypen abzugleichen und die inout Schlüsselwörter nicht zu verwenden, stellen Sie möglicherweise fest, dass Sie manchmal Stellvertretungen mit identischen Lambda-Ausdrücken oder -Methoden instanziieren können, sie können jedoch keine Stellvertretung einem anderen zuweisen.

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

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

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

Weitere Informationen und Beispiele finden Sie unter Verwendung der Varianz für generische Func- und Action-Delegaten (C#).

Angeben varianter Typparameter in generischen Delegaten

Wenn ein generischer Delegat kovariant oder kontravariant generische Typparameter aufweist, kann er als generischer Variant-Delegat bezeichnet werden.

Sie können einen generischen Typparameter in einem generischen Delegaten mithilfe des out Schlüsselworts kovariant deklarieren. Der kovariante Typ kann nur als Methodenrückgabetyp und nicht als Typ von Methodenargumenten verwendet werden. Das folgende Codebeispiel zeigt, wie ein kovarianter generischer Delegat deklariert wird.

public delegate R DCovariant<out R>();  

Sie können einen generischen Typparameter mithilfe des Schlüsselworts in in einem generischen Delegaten als kontravariant deklarieren. Der kontravariante Typ kann nur als Typ von Methodenargumenten und nicht als Methodenrückgabetyp verwendet werden. Im folgenden Codebeispiel wird veranschaulicht, wie ein kontravarianter generischer Delegat deklariert wird.

public delegate void DContravariant<in A>(A a);  

Von Bedeutung

ref, inund out Parameter in C# können nicht als Variante markiert werden.

Es ist auch möglich, Varianz und Kovarianz im gleichen Delegaten, aber für verschiedene Typparameter, zu unterstützen. Dies wird im folgenden Beispiel gezeigt.

public delegate R DVariant<in A, out R>(A a);  

Instanziieren und Aufrufen von varianten generischen Delegaten

Sie können variantente Delegaten genauso instanziieren und aufrufen, wie Sie invariante Delegaten instanziieren und aufrufen. Im folgenden Beispiel wird der Delegat durch einen Lambda-Ausdruck instanziiert.

DVariant<String, String> dvariant = (String str) => str + " ";  
dvariant("test");  

Kombinieren von varianten generischen Delegaten

Kombinieren Sie keine Variantendelegaten. Die Combine Methode unterstützt keine Variantendelegatkonvertierung und erwartet, dass Delegaten exakt denselben Typ haben. Dies kann zu einer Laufzeitausnahme führen, wenn Sie Delegaten entweder mithilfe der Combine-Methode oder mithilfe des +-Operators kombinieren, wie im folgenden Codebeispiel gezeigt.

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 Referenztypen

Die Varianz für generische Typparameter wird nur für Referenztypen unterstützt. Beispielsweise DVariant<int> kann nicht implizit in DVariant<Object> oder DVariant<long>, da ganze Zahl ein Werttyp ist, konvertiert werden.

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

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

Siehe auch