Wariancja w delegatach (C#)

Program .NET Framework 3.5 wprowadził obsługę wariancji dla pasujących podpisów metod z typami delegatów we wszystkich delegatach w języku C#. Oznacza to, że można przypisać do delegatów nie tylko metody, które mają pasujące podpisy, ale także metody zwracające więcej typów pochodnych (kowariancja) lub które akceptują parametry, które mają mniej pochodne typy (kontrawariancję) niż określone przez typ delegata. Obejmuje to zarówno delegaty ogólne, jak i nieogólne.

Rozważmy na przykład następujący kod, który ma dwie klasy i dwa delegaty: ogólny i niegeneryczny.

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

Podczas tworzenia delegatów typów SampleDelegate lub SampleGenericDelegate<A, R> można przypisać dowolną z następujących metod do tych delegatów.

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

Poniższy przykład kodu ilustruje niejawną konwersję między sygnaturą metody a typem delegata.

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

Aby uzyskać więcej przykładów, zobacz Using Variance in Delegates (C#) and Using Variance for Func and Action Generic Delegates (C#) (Używanie wariancji w delegatach (C#) i Using Variance for Func and Action Generic Delegates (C#)( Używanie wariancji dla funkcji Func and Action Generic Delegates (C#).

Wariancja w parametrach typu ogólnego

W programie .NET Framework 4 lub nowszym można włączyć niejawną konwersję między delegatami, aby delegaty ogólne, które mają różne typy określone przez parametry typu ogólnego, można przypisać do siebie, jeśli typy są dziedziczone ze sobą zgodnie z wymaganiami wariancji.

Aby włączyć niejawną konwersję, należy jawnie zadeklarować parametry ogólne w delegatu jako kowariantne lub kontrawariantne przy użyciu słowa kluczowego in lub out .

Poniższy przykład kodu pokazuje, jak utworzyć delegata, który ma kowariantny parametr typu ogólnego.

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

Jeśli używasz tylko obsługi wariancji do dopasowywania podpisów metod z typami delegatów i nie używasz in słów kluczowych i out , może się okazać, że czasami można utworzyć wystąpienia delegatów z identycznymi wyrażeniami lub metodami lambda, ale nie można przypisać jednego delegata do innego.

W poniższym przykładzie SampleGenericDelegate<String> kodu nie można jawnie przekonwertować na SampleGenericDelegate<Object>, chociaż String dziedziczy element Object. Ten problem można rozwiązać, oznaczając ogólny parametr T za pomocą słowa kluczowego out .

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

Delegaty ogólne, które mają parametry typu wariantu na platformie .NET

Program .NET Framework 4 wprowadził obsługę wariancji dla parametrów typu ogólnego w kilku istniejących delegatach ogólnych:

Aby uzyskać więcej informacji i przykładów, zobacz Using Variance for Func and Action Generic Delegates (C#)( Using Variance for Func and Action Generic Delegates (C#)( Using Variance for Func and Action Generic Delegates (C#)( Używanie wariancji dla func i action generic delegates (C

Deklarowanie parametrów typu wariantu w delegatach ogólnych

Jeśli delegat ogólny ma kowariantne lub kontrawariantne parametry typu ogólnego, może być określany jako delegat ogólny wariantu.

Za pomocą słowa kluczowego out można zadeklarować kowariantny parametr typu ogólnego w delegacie ogólnym. Typ kowariantny może być używany tylko jako typ zwracany przez metodę, a nie jako typ argumentów metody. Poniższy przykład kodu pokazuje, jak zadeklarować kowariantnego delegata ogólnego.

public delegate R DCovariant<out R>();  

Można zadeklarować kontrawariant parametru typu ogólnego w delegacie ogólnym przy użyciu słowa kluczowego in . Typ kontrawariantny może być używany tylko jako typ argumentów metody, a nie jako typ zwracany przez metodę. Poniższy przykład kodu przedstawia sposób deklarowania kontrawariantnego delegata ogólnego.

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

Ważne

refParametrów , ini out w języku C# nie można oznaczyć jako wariantu.

Można również obsługiwać zarówno wariancję, jak i kowariancję w tym samym delegatu, ale dla różnych parametrów typu. Jest to pokazane w następującym przykładzie.

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

Tworzenie wystąpień i wywoływanie delegatów ogólnych wariantu

Możesz utworzyć wystąpienie i wywołać delegatów wariantu tak samo, jak wystąpienia i wywołać niezmienne delegaty. W poniższym przykładzie delegat jest tworzone przez wyrażenie lambda.

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

Łączenie delegatów ogólnych wariantu

Nie łącz delegatów wariantów. Metoda Combine nie obsługuje konwersji delegata wariantu i oczekuje delegatów dokładnie tego samego typu. Może to prowadzić do wyjątku czasu wykonywania podczas łączenia delegatów przy użyciu metody lub przy użyciu Combine+ operatora, jak pokazano w poniższym przykładzie kodu.

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

Wariancja w parametrach typu ogólnego dla typów wartości i odwołań

Wariancja dla parametrów typu ogólnego jest obsługiwana tylko dla typów referencyjnych. Na przykład DVariant<int> nie można niejawnie konwertować na DVariant<Object> lub DVariant<long>, ponieważ liczba całkowita jest typem wartości.

W poniższym przykładzie pokazano, że wariancja w parametrach typu ogólnego nie jest obsługiwana dla typów wartości.

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

Zobacz też