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 jenis IEnumerable<Base>.

  • Contravariance

    Memungkinkan Anda untuk menggunakan jenis yang lebih umum (kurang diturunkan) dari yang ditentukan semula.

    Anda dapat menetapkan instans Action<Base> ke variabel jenis Action<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 jenis List<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 TAction<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> dan Action<Base> (Action(Of Derived) dan Action(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 jenis Action<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 Type2pengembalian. 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

Lihat juga