Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Framework .NET 3.5 wprowadził obsługę wariancji podpisów metod pasujących do typów delegatów dla wszystkich delegatów 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 delegatów ogólnych, jak i delegatów nieogólnych.
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 Używanie wariancji w delegatach (C#) oraz Używanie wariancji dla delegatów ogólnych Func i Action (C#).
Wariancja w parametrach typu ogólnego
W .NET Framework 4 lub nowszym można włączyć niejawną konwersję między delegatami, dzięki czemu delegaty ogólne o różnych typach określonych przez parametry typu ogólnego mogą być przypisywane do siebie, jeśli typy są ze sobą dziedziczone zgodnie z wymaganiami zmienności.
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 słów kluczowych in i out, może się okazać, że czasami można tworzyć delegaty z identycznymi wyrażeniami lambda lub metodami, ale nie można przypisać jednego delegata do drugiego.
W poniższym przykładzie SampleGenericDelegate<String> nie można jawnie przekonwertować na SampleGenericDelegate<Object>, chociaż String dziedziczy 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:
Actiondelegatów z System przestrzeni nazw, na przykład Action<T> i Action<T1,T2>Funcdelegatów z System przestrzeni nazw, na przykład Func<TResult> i Func<T,TResult>Pełnomocnik Predicate<T>
Pełnomocnik Comparison<T>
Pełnomocnik Converter<TInput,TOutput>
Aby uzyskać więcej informacji i przykładów, zobacz Używanie wariancji dla delegatów generycznych Func i Action (C#).
Deklaracja parametru typu wariancyjnego w delegatach generycznych
Jeśli delegat ogólny ma kowariantne lub kontrawariantne parametry typu ogólnego, może być określany jako wariantowy delegat ogólny.
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 delegacie, ale dla różnych parametrów typu. Jest to pokazane w poniższym przykładzie.
public delegate R DVariant<in A, out R>(A a);
Tworzenie wystąpień i wywoływanie delegatów generycznych wariantowych
Możesz utworzyć wystąpienie i wywołać delegata wariantu tak samo, jak utworzyć wystąpienie i wywołać delegata niezmiennego. W poniższym przykładzie delegat jest tworzony przez wyrażenie lambda.
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");
Łączenie wariantowych delegatów generycznych
Nie łącz delegatów wariantowych. Metoda Combine nie obsługuje konwersji delegata z wariantem i oczekuje, że delegaty będą dokładnie tego samego typu. Może to prowadzić do wyjątku czasu wykonywania podczas łączenia delegatów zarówno przy użyciu metody Combine, jak i 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 referencyjnych
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;
}