Kovariansi dan kontravariansi dalam generik
Kovariansi dan kontravariansi adalah istilah yang merujuk pada kemampuan untuk menggunakan jenis yang lebih turunan (lebih spesifik) atau jenis yang kurang turunan (kurang spesifik) dari yang semula ditentukan. Parameter jenis generik mendukung kovarians dan kontravarian untuk memberikan fleksibilitas yang lebih besar dalam menetapkan dan menggunakan jenis generik.
Ketika Anda mengacu pada sistem jenis, kovarians, kontravarian, dan invarian memiliki definisi berikut. Contohnya mengasumsikan kelas dasar bernama Base
dan kelas turunan bernama Derived
.
Covariance
Memungkinkan Anda untuk menggunakan jenis yang lebih turunan dari yang ditentukan semula.
Anda dapat menetapkan instans
IEnumerable<Derived>
ke variabel jenisIEnumerable<Base>
.Contravariance
Memungkinkan Anda untuk menggunakan jenis yang lebih umum (kurang diturunkan) dari yang ditentukan semula.
Anda dapat menetapkan instans
Action<Base>
ke variabel jenisAction<Derived>
.Invariance
Berarti Anda hanya dapat menggunakan jenis yang ditentukan semula. Parameter jenis generik invarian bukanlah kovarian atau kontravarian.
Anda tidak dapat menetapkan instans
List<Base>
ke variabel jenisList<Derived>
atau sebaliknya.
Parameter jenis kovarian memungkinkan Anda membuat penetapan yang terlihat seperti Polimorfisme biasa, seperti yang ditunjukkan dalam kode berikut.
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
Kelas List<T> mengimplementasikan IEnumerable<T> antarmuka, jadi List<Derived>
(List(Of Derived)
dalam Visual Basic) mengimplementasikan IEnumerable<Derived>
. Parameter jenis kovarian melakukan sisanya.
Kontravarian, di sisi lain, tampaknya berlawanan dengan intuisi. Contoh berikut membuat delegasi jenis Action<Base>
(Action(Of Base)
dalam Visual Basic), lalu menetapkan yang mendelegasikan ke variabel jenis 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())
Ini tampaknya mundur, tetapi kode jenis aman yang mengkompilasi dan berjalan. Ekspresi lambda cocok dengan delegasi yang ditetapkan, sehingga ia menentukan metode yang mengambil satu parameter jenis Base
dan yang tidak memiliki nilai pengembalian. Delegasi yang dihasilkan dapat ditetapkan ke variabel jenis Action<Derived>
karena parameter T
Action<T> jenis delegasi bersifat kontravarian. Kode berjenis aman karena T
menentukan jenis parameter. Ketika delegasi jenis Action<Base>
dipanggil sebagaimana jika itu merupakan delegasi jenis Action<Derived>
, maka argumennya harus berjenis Derived
. Argumen ini selalu dapat diteruskan dengan aman ke metode yang mendasar, karena parameter metode berjenis Base
.
Secara umum, parameter jenis kovarian dapat digunakan sebagai jenis pengembalian delegasi, dan parameter jenis kontravarian dapat digunakan sebagai jenis parameter. Untuk antarmuka, parameter jenis kovarian dapat digunakan sebagai jenis pengembalian metode antarmuka, dan parameter jenis kontravarian dapat digunakan sebagai jenis parameter metode antarmuka.
Kovarian dan kontravarian secara kolektif disebut sebagai variansi. Parameter jenis generik yang tidak ditandai kovarian atau kontravarian disebut sebagai invarian. Ringkasan singkat fakta tentang varians dalam runtime bahasa umum:
Parameter jenis varian dibatasi untuk antarmuka generik dan jenis delegasi generik.
Antarmuka generik atau jenis delegasi generik dapat memiliki parameter jenis kovarian dan kontravarian.
Varians hanya berlaku untuk jenis referensi; jika Anda menentukan jenis nilai untuk parameter jenis varian, parameter jenis tersebut invarian untuk jenis yang dibuat yang dihasilkan.
Varians tidak berlaku untuk kombinasi delegasi. Artinya, mengingat dua delegasi jenis
Action<Derived>
danAction<Base>
(Action(Of Derived)
danAction(Of Base)
dalam Visual Basic), Anda tidak dapat menggabungkan delegasi kedua dengan yang pertama meskipun hasilnya akan berjenis aman. Varians memungkinkan delegasi kedua ditetapkan ke variabel jenisAction<Derived>
, tetapi delegasi hanya dapat menggabungkan jika jenisnya cocok dengan tepat.Mulai dari C# 9, jenis pengembalian kovarian didukung. Metode utama dapat mendeklarasikan jenis pengembalian yang lebih turunan dengan metode yang dikesampingkannya, dan properti utama, baca-saja dapat mendeklarasikan jenis yang lebih turunan.
Antarmuka generik dengan parameter jenis kovarians
Beberapa antarmuka generik memiliki parameter jenis kovarian, misalnya, IEnumerable<T>, IEnumerator<T>, IQueryable<T>, dan IGrouping<TKey,TElement>. Semua parameter jenis antarmuka ini adalah kovarians, sehingga parameter jenis hanya digunakan untuk jenis pengembalian anggota.
Contoh berikut menggambarkan parameter jenis kovarians. Contoh ini menentukan dua jenis: Base
memiliki metode statis bernama PrintBases
yang mengambil IEnumerable<Base>
(IEnumerable(Of Base)
dalam Visual Basic) dan mencetak elemen. Derived
mewarisi dari Base
. Contoh membuat kosong List<Derived>
(List(Of Derived)
dalam Visual Basic) dan menunjukkan bahwa jenis ini dapat diteruskan ke PrintBases
dan ditetapkan ke variabel jenis IEnumerable<Base>
tanpa transmisi. List<T> mengimplementasikan IEnumerable<T>, yang memiliki parameter jenis kovarian tunggal. Parameter jenis kovarian adalah alasan mengapa instans IEnumerable<Derived>
dapat digunakan alih-alih 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
Antarmuka generik dengan parameter jenis kontravarian
Beberapa antarmuka generik memiliki parameter jenis kovarian, misalnya, IComparer<T>, IComparable<T>, dan IEqualityComparer<T>. Antarmuka ini hanya memiliki parameter jenis kontravarian, sehingga parameter jenis hanya digunakan sebagai jenis parameter pada anggota antarmuka.
Contoh berikut menggambarkan parameter jenis kontravarian. Contoh menentukan kelas abstrak (MustInherit
di Visual Basic) Shape
dengan Area
properti. Contoh juga menentukan ShapeAreaComparer
kelas yang mengimplementasikan IComparer<Shape>
(IComparer(Of Shape)
dalam Visual Basic). Implementasi IComparer<T>.Compare metode didasarkan pada nilai Area
properti, sehingga ShapeAreaComparer
dapat digunakan untuk mengurutkan Shape
objek menurut area.
Kelas Circle
mewarisi Shape
dan mengambil alih Area
. Contoh tersebut membuat SortedSet<T> objekCircle
, menggunakan konstruktor yang mengambil IComparer<Circle>
(IComparer(Of Circle)
dalam Visual Basic). Namun, alih-alih meneruskan IComparer<Circle>
, contoh meneruskan ShapeAreaComparer
objek, yang mengimplementasikan IComparer<Shape>
. Contoh tersebut dapat meneruskan perbandingan dari jenis yang kurang diturunkan (Shape
) ketika kode memanggil perbandingan dari jenis yang lebih turunan (Circle
), karena parameter IComparer<T> jenis antarmuka generik kontravarian.
Ketika objek baru Circle
ditambahkan ke SortedSet<Circle>
, IComparer<Shape>.Compare
metode (IComparer(Of Shape).Compare
metode dalam Visual Basic) ShapeAreaComparer
objek dipanggil setiap kali elemen baru dibandingkan dengan elemen yang ada. Jenis parameter metode (Shape
) kurang diturunkan daripada jenis yang sedang diteruskan (Circle
), sehingga panggilan berjenis aman. Kontravariansi memungkinkan ShapeAreaComparer
untuk mengurutkan koleksi jenis tunggal apa pun, serta koleksi campuran jenis, yang berasal dari 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
Delegasi generik dengan parameter jenis varian
Delegasi Func
generik, seperti Func<T,TResult>, memiliki jenis pengembalian kovarian dan jenis parameter kontravarian. Delegasi Action
generik, seperti Action<T1,T2>, memiliki jenis parameter yang kontravarian. Ini berarti bahwa delegasi dapat ditetapkan ke variabel yang memiliki lebih banyak jenis parameter turunan dan (dalam kasus Func
delegasi generik) jenis pengembalian yang kurang diturunkan.
Catatan
Parameter jenis generik terakhir dari Func
delegasi generik menentukan jenis nilai pengembalian dalam tanda tangan delegasi. Ini merupakan kovarian (out
kata kunci), sedangkan parameter jenis generik lainnya merupakan kontravarian (in
kata kunci).
Kode berikut menggambarkan hal ini. Bagian pertama dari kode tersebut menentukan kelas bernama Base
, kelas bernama Derived
yang mewarisi Base
, dan kelas lain dengan static
metode (Shared
dalam Visual Basic) bernama MyMethod
. Metode tersebut mengambil instans Base
dan mengembalikan instans Derived
. (Jika argumen adalah instans dari Derived
, MyMethod
mengembalikannya; jika argumen adalah instans Base
, MyMethod
mengembalikan instans baru Derived
.) Dalam Main()
, contoh tersebut membuat instans Func<Base, Derived>
(Func(Of Base, Derived)
dalam Visual Basic) yang mewakili MyMethod
, dan menyimpannya dalam variabel 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
Bagian kedua dari kode tersebut menunjukkan bahwa delegasi dapat ditetapkan ke variabel jenis Func<Base, Base>
(Func(Of Base, Base)
dalam Visual Basic), karena jenis pengembalian kovarian.
// 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())
Bagian ketiga dari kode tersebut menunjukkan bahwa delegasi dapat ditetapkan ke variabel jenis Func<Derived, Derived>
(Func(Of Derived, Derived)
dalam Visual Basic), karena jenis parameter kontravarian.
// 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())
Bagian akhir kode tersebut menunjukkan bahwa delegasi dapat ditetapkan ke variabel jenis Func<Derived, Base>
(Func(Of Derived, Base)
dalam Visual Basic), menggabungkan efek jenis parameter kontravarian dan jenis pengembalian kovarian.
// 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())
Varians dalam delegasi nongenerik
Dalam kode sebelumnya, tanda tangan MyMethod
sama persis dengan tanda tangan delegasi generik yang dibangun: Func<Base, Derived>
(Func(Of Base, Derived)
dalam Visual Basic). Contoh tersebut menunjukkan bahwa delegasi generik ini dapat disimpan dalam variabel atau parameter metode yang memiliki jenis parameter yang lebih turunan dan jenis pengembalian yang kurang diturunkan, selama semua jenis delegasi dibangun dari jenis delegasi generikFunc<T,TResult>.
Ini adalah poin penting. Efek kovariansi dan kontravariansi dalam parameter jenis delegasi generik mirip dengan efek kovarians dan kontravariansi dalam pengikatan delegasi biasa (lihat Varians dalam Delegasi (C#) dan Varians dalam Delegasi (Visual Basic)). Namun, varians dalam pengikatan delegasi bekerja dengan semua jenis delegasi, tidak hanya dengan jenis delegasi generik yang memiliki parameter jenis varian. Selain itu, varians dalam pengikatan delegasi memungkinkan metode untuk terikat pada setiap delegasi yang memiliki jenis parameter yang lebih ketat dan jenis pengembalian yang kurang ketat, sedangkan penetapan delegasi generik hanya berfungsi jika kedua jenis delegasi dibangun dari definisi jenis generik yang sama.
Contoh berikut menunjukkan efek gabungan dari varians dalam mengikat delegasi dan varians dalam parameter jenis generik. Contoh menentukan hierarki jenis yang mencakup tiga jenis, dari yang paling tidak diturunkan (Type1
) hingga yang paling banyak diturunkan (Type3
). Varians dalam pengikatan delegasi biasa digunakan untuk mengikat metode dengan jenis Type1
parameter dan jenis Type3
pengembalian ke delegasi generik dengan jenis Type2
parameter dan jenis Type2
pengembalian. Delegasi generik yang dihasilkan kemudian ditetapkan ke variabel lain yang jenis delegasi generiknya memiliki parameter jenis Type3
dan jenis pengembalianType1
, menggunakan kovariansi dan kontravariansi parameter jenis generik. Penetapan kedua memerlukan jenis variabel dan jenis delegasi untuk dibangun dari definisi jenis generik yang sama, dalam hal ini, 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
Tentukan antarmuka dan delegasi generik varian
Visual Basic dan C# memiliki kata kunci yang memungkinkan Anda untuk menandai parameter jenis generik antarmuka dan delegasi sebagai kovarian atau kontravarian.
Parameter jenis kovarian ditandai dengan out
kata kunci (Out
kata kunci dalam Visual Basic). Anda dapat menggunakan parameter jenis kovarian sebagai nilai pengembalian metode yang termasuk dalam antarmuka, atau sebagai jenis pengembalian delegasi. Anda tidak dapat menggunakan parameter jenis kovarian sebagai batasan jenis generik untuk metode antarmuka.
Catatan
Jika metode antarmuka memiliki parameter yang merupakan jenis delegasi generik, parameter jenis kovarian dari jenis antarmuka dapat digunakan untuk menentukan parameter jenis kontravarian dari jenis delegasi.
Parameter jenis kovarian ditandai dengan in
kata kunci (In
kata kunci dalam Visual Basic). Anda dapat menggunakan parameter jenis kontravarian sebagai jenis parameter metode yang termasuk dalam antarmuka, atau sebagai jenis parameter delegasi. Anda dapat menggunakan parameter jenis kontravarian sebagai batasan jenis generik untuk metode antarmuka.
Hanya jenis antarmuka dan jenis delegasi yang dapat memiliki parameter jenis varian. Jenis antarmuka atau delegasi dapat memiliki parameter jenis kovarian dan kontravarian.
Visual Basic dan C# tidak memungkinkan Anda untuk melanggar aturan untuk menggunakan parameter jenis kovarian dan kontravarian, atau untuk menambahkan anotasi kovarians dan kontravarian ke parameter jenis selain antarmuka dan delegasi.
Untuk informasi dan contoh kode, lihat Varians di Antarmuka Generik (C#) dan Varians di Antarmuka Generik (Visual Basic).
Daftar perbaikan
Jenis antarmuka dan delegasi berikut memiliki parameter jenis kovarian dan/atau kontravarian.
Jenis | Parameter jenis kovarians | Parameter jenis kontravarian |
---|---|---|
Action<T> ke Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Ya | |
Comparison<T> | Ya | |
Converter<TInput,TOutput> | Ya | Ya |
Func<TResult> | Ya | |
Func<T,TResult> ke Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Ya | Ya |
IComparable<T> | Ya | |
Predicate<T> | Ya | |
IComparer<T> | Ya | |
IEnumerable<T> | Ya | |
IEnumerator<T> | Ya | |
IEqualityComparer<T> | Ya | |
IGrouping<TKey,TElement> | Ya | |
IOrderedEnumerable<TElement> | Ya | |
IOrderedQueryable<T> | Ya | |
IQueryable<T> | Ya |