Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Kowariancja i kontrawariancja to terminy odwołujące się do możliwości używania bardziej pochodnego typu (bardziej szczegółowego) lub mniejszego typu pochodnego (mniej specyficznego) niż pierwotnie określone. Parametry typu ogólnego obsługują kowariancję i kontrawariancję, aby zapewnić większą elastyczność przypisywania i używania typów ogólnych.
Podczas odwoływania się do systemu typów, podane są następujące definicje dla kowariancji, kontrawariancji i wariancji. Założono, że w przykładach występuje klasa bazowa o nazwie Base
oraz klasa pochodna o nazwie Derived
.
Covariance
Umożliwia użycie bardziej pochodnego typu niż pierwotnie określony.
Możesz przypisać instancję
IEnumerable<Derived>
do zmiennej typuIEnumerable<Base>
.Contravariance
Umożliwia użycie bardziej ogólnego (mniej pochodnego) typu niż pierwotnie określony.
Możesz przypisać instancję
Action<Base>
do zmiennej typuAction<Derived>
.Invariance
Oznacza, że można użyć tylko typu pierwotnie określonego. Niezmienny parametr typu ogólnego nie jest ani kowariantny, ani kontrawariantny.
Nie można przypisać wystąpienia typu
List<Base>
do zmiennej typuList<Derived>
lub odwrotnie.
Kowariantne parametry typu umożliwiają tworzenie przypisań, które wyglądają podobnie jak zwykły polimorfizm, jak pokazano w poniższym kodzie.
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
Klasa List<T> implementuje interfejs IEnumerable<T>, więc List<Derived>
(List(Of Derived)
w Visual Basic) implementuje IEnumerable<Derived>
. Kowariantny parametr typu wykonuje resztę.
Kontrawariancja, z drugiej strony, wydaje się nieintuicyjna. Poniższy przykład tworzy delegata typu Action<Base>
(Action(Of Base)
w Visual Basic), a następnie przypisuje ten delegat do zmiennej typu Action<Derived>
.
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())
Wydaje się to nielogiczne, ale jest to bezpieczny typowo kod, który się kompiluje i uruchamia. Wyrażenie lambda pasuje do delegata, do którego jest przypisany, dlatego definiuje metodę, która przyjmuje jeden parametr typu Base
i nie ma zwracanej wartości. Wynikowy delegat można przypisać do zmiennej typu Action<Derived>
, ponieważ parametr T
typu delegata Action<T> jest kontrawariantny. Kod jest bezpieczny dla typu, ponieważ T
określa typ parametru. Gdy delegat typu Action<Base>
jest wywoływany tak, jakby był pełnomocnikiem typu Action<Derived>
, jego argument musi być typu Derived
. Ten argument zawsze można bezpiecznie przekazać do bazowej metody, ponieważ parametr metody jest typu Base
.
Ogólnie rzecz biorąc, kowariantny parametr typu może służyć jako zwracany typ delegata, a kontrawariantne parametry typu mogą być używane jako typy parametrów. W przypadku interfejsu kowariantne parametry typu mogą służyć jako typy zwracane metod interfejsu, a kontrawariantne parametry typu mogą być używane jako typy parametrów metod interfejsu.
Kowariancja i kontrawariancja są łącznie określane jako wariancja. Parametr typu ogólnego, który nie jest oznaczony kowariantnym lub kontrawariantem, jest określany jako niezmienny. Krótkie podsumowanie faktów dotyczących zmienności w środowisku uruchomieniowym języka wspólnego (CLR):
Parametry typu wariantu są ograniczone do interfejsu ogólnego i typów delegatów ogólnych.
Ogólny interfejs lub ogólny typ delegata może mieć zarówno kowariantne, jak i kontrawariantne parametry typu.
Wariancja ma zastosowanie tylko do typów referencyjnych; Jeśli określisz typ wartości dla parametru typu wariantu, ten parametr typu jest niezmienny dla wynikowego skonstruowanego typu.
Wariancja nie ma zastosowania do kombinacji delegatów. Oznacza to, że biorąc pod uwagę dwa delegaty typów
Action<Derived>
iAction<Base>
(Action(Of Derived)
iAction(Of Base)
w Visual Basic), nie można połączyć drugiego delegata z pierwszym, chociaż wynik byłby typowo bezpieczny. Wariancja umożliwia przypisanie drugiego delegata do zmiennej typuAction<Derived>
, ale delegaci mogą łączyć się tylko wtedy, gdy ich typy są dokładnie zgodne.Począwszy od języka C# 9, kowariantne typy zwracane są obsługiwane. Metoda zastępowania może zadeklarować bardziej pochodny typ zwracany przez metodę, która zastępuje, a zastępowanie właściwości tylko do odczytu może zadeklarować bardziej pochodny typ.
Interfejsy ogólne z kowariantnymi parametrami typu
Kilka interfejsów ogólnych ma kowariantne parametry typu, na przykład IEnumerable<T>, IEnumerator<T>, IQueryable<T>i IGrouping<TKey,TElement>. Wszystkie parametry typu tych interfejsów są kowariantne, więc parametry typu są używane tylko dla zwracanych typów elementów członkowskich.
W poniższym przykładzie przedstawiono kowariantne parametry typu. W przykładzie zdefiniowano dwa typy: Base
ma statyczną metodę o nazwie PrintBases
, która przyjmuje IEnumerable<Base>
(IEnumerable(Of Base)
w Visual Basic) i wyświetla jego elementy.
Derived
dziedziczy z Base
. W przykładzie jest tworzony pusty List<Derived>
(List(Of Derived)
w Visual Basic) i pokazano, że ten typ można przekazać do PrintBases
oraz przypisać do zmiennej typu IEnumerable<Base>
bez rzutowania. pl-PL: List<T> implementuje IEnumerable<T>, który ma jeden kowariantny parametr typu. Kowariantny parametr typu jest powodem, dla którego można użyć wystąpienia IEnumerable<Derived>
zamiast IEnumerable<Base>
.
using System;
using System.Collections.Generic;
class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}
class Derived : Base
{
public static void Main()
{
List<Derived> dlist = new List<Derived>();
Derived.PrintBases(dlist);
IEnumerable<Base> bIEnum = dlist;
}
}
Imports System.Collections.Generic
Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class
Class Derived
Inherits Base
Shared Sub Main()
Dim dlist As New List(Of Derived)()
Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class
Interfejsy ogólne z kontrawariantnymi parametrami typu
Kilka interfejsów ogólnych ma kontrawariantne parametry typu; na przykład: IComparer<T>, IComparable<T>i IEqualityComparer<T>. Te interfejsy mają tylko kontrawariantne parametry typu, więc parametry typu są używane tylko jako typy parametrów w elementach członkowskich interfejsów.
W poniższym przykładzie przedstawiono kontrawariantne parametry typu. W przykładzie zdefiniowano abstrakcyjną (MustInherit
w Visual Basic) Shape
klasę z właściwością Area
. W przykładzie zdefiniowano również klasę ShapeAreaComparer
, która implementuje IComparer<Shape>
(IComparer(Of Shape)
w Visual Basic). Implementacja IComparer<T>.Compare metody jest oparta na wartości Area
właściwości, więc ShapeAreaComparer
może służyć do sortowania Shape
obiektów według obszaru.
Klasa Circle
dziedziczy Shape
i zastępuje Area
. W przykładzie tworzony jest SortedSet<T> z Circle
obiektów przy użyciu konstruktora, który przyjmuje IComparer<Circle>
(IComparer(Of Circle)
w Visual Basic). Jednak zamiast przekazywać IComparer<Circle>
, przykład przekazuje obiekt ShapeAreaComparer
, który implementuje IComparer<Shape>
. Przykład może przekazać obiekt porównujący mniej zaawansowanego typu (Shape
), gdy kod wymaga obiektu porównującego bardziej zaawansowanego typu (Circle
), ponieważ parametr typu interfejsu generycznego IComparer<T> jest kontrawariantny.
Po dodaniu nowego Circle
obiektu do SortedSet<Circle>
, metoda IComparer<Shape>.Compare
(IComparer(Of Shape).Compare
metoda w Visual Basic) ShapeAreaComparer
obiektu jest wywoływana za każdym razem, gdy nowy element jest porównywany z istniejącym elementem. Typ parametru metody (Shape
) jest mniejszy niż typ przekazywany (Circle
), więc wywołanie jest bezpieczne. Kontrawariancja umożliwia ShapeAreaComparer
sortowanie zarówno kolekcji jednego typu, jak i mieszanej kolekcji typów, które wywodzą się z klasy Shape
.
using System;
using System.Collections.Generic;
abstract class Shape
{
public virtual double Area { get { return 0; }}
}
class Circle : Shape
{
private double r;
public Circle(double radius) { r = radius; }
public double Radius { get { return r; }}
public override double Area { get { return Math.PI * r * r; }}
}
class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>
{
int IComparer<Shape>.Compare(Shape a, Shape b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.Area.CompareTo(b.Area);
}
}
class Program
{
static void Main()
{
// You can pass ShapeAreaComparer, which implements IComparer<Shape>,
// even though the constructor for SortedSet<Circle> expects
// IComparer<Circle>, because type parameter T of IComparer<T> is
// contravariant.
SortedSet<Circle> circlesByArea =
new SortedSet<Circle>(new ShapeAreaComparer())
{ new Circle(7.2), new Circle(100), null, new Circle(.01) };
foreach (Circle c in circlesByArea)
{
Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
}
}
}
/* This code example produces the following output:
null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
*/
Imports System.Collections.Generic
MustInherit Class Shape
Public MustOverride ReadOnly Property Area As Double
End Class
Class Circle
Inherits Shape
Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class
Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)
Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _
Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class
Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}
For Each c As Circle In circlesByArea
Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class
' This code example produces the following output:
'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979
Delegaty ogólne ze zmiennymi parametrami typu
Delegaty Func
ogólne, takie jak Func<T,TResult>, mają kowariantne typy zwracane i kontrawariantne typy parametrów. Delegaty Action
ogólne, takie jak Action<T1,T2>, mają kontrawariantne typy parametrów. Oznacza to, że delegaci mogą być przypisywani do zmiennych, które mają bardziej pochodne typy parametrów i (w przypadku Func
delegatów ogólnych) mniej pochodnych typów zwracanych.
Uwaga / Notatka
Ostatni ogólny parametr Func
typu delegatów ogólnych określa typ zwracanej wartości w podpisie delegata. Jest kowariantny (out
słowo kluczowe), natomiast inne parametry typu ogólnego są kontrawariantne (in
słowo kluczowe).
Poniższy kod ilustruje to. Pierwszy fragment kodu definiuje klasę o nazwie Base
, klasę o nazwie Derived
, która dziedziczy Base
, oraz inną klasę z metodą static
(Shared
w Visual Basic) o nazwie MyMethod
. Metoda przyjmuje wystąpienie Base
i zwraca wystąpienie Derived
. (Jeśli argument jest wystąpieniem Derived
, MyMethod
zwraca go; jeśli argument jest wystąpieniem Base
, MyMethod
zwraca nowe wystąpienie Derived
.) W Main()
przykład tworzy wystąpienie Func<Base, Derived>
(Func(Of Base, Derived)
w Visual Basic), które reprezentuje MyMethod
i przechowuje je w zmiennej f1
.
public class Base {}
public class Derived : Base {}
public class Program
{
public static Derived MyMethod(Base b)
{
return b as Derived ?? new Derived();
}
static void Main()
{
Func<Base, Derived> f1 = MyMethod;
Public Class Base
End Class
Public Class Derived
Inherits Base
End Class
Public Class Program
Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function
Shared Sub Main()
Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod
Drugi fragment kodu pokazuje, że delegat może być przypisany do zmiennej typu Func<Base, Base>
(Func(Of Base, Base)
w Visual Basic), ponieważ zwracany typ jest kowariantny.
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
Trzeci fragment kodu pokazuje, że delegat może być przypisany do zmiennej typu Func<Derived, Derived>
(Func(Of Derived, Derived)
w Visual Basic), ponieważ typ parametru jest kontrawariantny.
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
Ostatni fragment kodu pokazuje, że delegata można przypisać do zmiennej typu Func<Derived, Base>
(Func(Of Derived, Base)
w Visual Basic), łącząc efekty typu kontrawariantnego parametru i typu kowariantnego zwracanej wartości.
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
Wariancja w delegatach innych niż ogólne
W poprzednim kodzie sygnatura MyMethod
dokładnie pasuje do sygnatury skonstruowanego delegata ogólnego: Func<Base, Derived>
(Func(Of Base, Derived)
w Visual Basic). W przykładzie pokazano, że ten delegat ogólny może być przechowywany w zmiennych lub parametrach metody, które mają więcej typów parametrów pochodnych i mniej pochodnych typów zwracanych, o ile wszystkie typy delegatów są konstruowane z ogólnego typu Func<T,TResult>delegata .
Jest to ważny punkt. Skutki kowariancji i kontrawariancji w parametrach typów delegatów ogólnych są podobne do efektów kowariancji i kontrawariancji w zwykłym powiązaniu delegatów (zobacz Wariancja w Delegatach (C#) i Wariancja w Delegatach (Visual Basic)). Jednak wariancja w powiązaniu delegata działa ze wszystkimi typami delegatów, a nie tylko z ogólnymi typami delegatów, które mają parametry typu wariantu. Ponadto zmienność w powiązaniu delegata umożliwia przypisanie metody do dowolnego delegata, który ma bardziej restrykcyjne typy parametrów i mniej restrykcyjny typ zwracany. Przypisanie delegatów ogólnych funkcjonuje jednak tylko wtedy, gdy oba typy delegatów zostały zbudowane na podstawie tej samej definicji typu ogólnego.
W poniższym przykładzie pokazano połączone efekty wariancji wiązań delegatów i wariancji parametrów typów ogólnych. W przykładzie zdefiniowano hierarchię typów obejmującą trzy typy, od najmniej pochodnych (Type1
) do najbardziej pochodnych (Type3
). Wariancja w zwykłym powiązaniu delegata służy do powiązania metody z typem parametru Type1
i zwracanym typem Type3
do delegata ogólnego z typem parametru Type2
i zwracanym typem Type2
. Docelowego delegata ogólnego przypisuje się następnie do innej zmiennej, której typ delegata ma parametr typu Type3
i zwraca typ Type1
, wykorzystując kowariancję i kontrawariancję parametrów typu ogólnego. Drugie przypisanie wymaga utworzenia zarówno typu zmiennej, jak i typu delegata z tej samej definicji typu ogólnego, w tym przypadku Func<T,TResult>.
using System;
public class Type1 {}
public class Type2 : Type1 {}
public class Type3 : Type2 {}
public class Program
{
public static Type3 MyMethod(Type1 t)
{
return t as Type3 ?? new Type3();
}
static void Main()
{
Func<Type2, Type2> f1 = MyMethod;
// Covariant return type and contravariant parameter type.
Func<Type3, Type1> f2 = f1;
Type1 t1 = f2(new Type3());
}
}
Public Class Type1
End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class
Public Class Program
Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function
Shared Sub Main()
Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod
' Covariant return type and contravariant parameter type.
Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class
Definiowanie wariantów interfejsów ogólnych i delegatów
Język Visual Basic i C# mają słowa kluczowe, które umożliwiają oznaczanie ogólnych parametrów typu interfejsów i delegatów jako kowariantne lub kontrawariantne.
Kowariantny parametr typu jest oznaczony out
słowem kluczowym (Out
słowo kluczowe w Visual Basic). Można użyć kowariantnego parametru typu jako wartości zwracanej metody należącej do interfejsu lub jako zwracanego typu delegata. Nie można użyć kowariantnego parametru typu jako ograniczenia typu ogólnego dla metod interfejsu.
Uwaga / Notatka
Jeśli metoda interfejsu ma parametr, który jest ogólnym typem delegata, kowariantny parametr typu interfejsu może służyć do określenia kontrawariantnego parametru typu delegata.
Kontrawariantny parametr typu jest oznaczony in
słowem kluczowym (In
słowo kluczowe w Visual Basic). Można użyć kontrawariantnego parametru typu jako typu parametru metody, która należy do interfejsu, lub jako typu parametru delegata. Dla metody interfejsu można użyć kontrawariantnego parametru typu jako ograniczenia typu ogólnego.
Tylko typy interfejsów i typy delegatów mogą mieć parametry typu wariantu. Typ interfejsu lub delegata może mieć zarówno kowariantne, jak i kontrawariantne parametry typu.
Visual Basic i C# nie zezwalają na naruszenie reguł dotyczących używania kowariantnych i kontrawariantnych parametrów typu lub dodawania adnotacji kowariancji i kontrawariancji do parametrów typu innych niż interfejsy i delegaty.
Aby uzyskać informacje i przykładowy kod, zobacz Wariancja w interfejsach ogólnych (C#) oraz Wariancja w interfejsach ogólnych (Visual Basic).
Lista typów
Następujące typy interfejsów i delegatów mają kowariantne i/lub kontrawariantne parametry typu.
Typ | Kowariantne parametry typu | Kontrawariantne parametry typu |
---|---|---|
Action<T> do Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Tak | |
Comparison<T> | Tak | |
Converter<TInput,TOutput> | Tak | Tak |
Func<TResult> | Tak | |
Func<T,TResult> do Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Tak | Tak |
IComparable<T> | Tak | |
Predicate<T> | Tak | |
IComparer<T> | Tak | |
IEnumerable<T> | Tak | |
IEnumerator<T> | Tak | |
IEqualityComparer<T> | Tak | |
IGrouping<TKey,TElement> | Tak | |
IOrderedEnumerable<TElement> | Tak | |
IOrderedQueryable<T> | Tak | |
IQueryable<T> | Tak |