Bagikan melalui


Kovariansi dan kontravariansi dalam generik

Kovariansi dan kontravariansi adalah istilah yang mengacu pada kemampuan untuk menggunakan jenis yang lebih turunan (lebih spesifik) atau jenis yang kurang diturunkan (kurang spesifik) daripada yang ditentukan awalnya. Parameter jenis generik mendukung kovariansi dan kontravariansi untuk memberikan fleksibilitas yang lebih besar dalam menetapkan dan menggunakan jenis generik.

Ketika Anda merujuk ke sistem tipe, kovarians, kontravariansi, dan invarians memiliki definisi sebagai berikut. Contoh mengasumsikan kelas dasar bernama Base dan kelas turunan bernama Derived.

  • Covariance

    Memungkinkan Anda menggunakan jenis yang lebih turunan dari yang semula ditentukan.

    Anda dapat menetapkan instans IEnumerable<Derived> ke variabel jenis IEnumerable<Base>.

  • Contravariance

    Memungkinkan Anda menggunakan tipe yang lebih umum (kurang spesifik) dari yang semula ditentukan.

    Anda dapat menetapkan instans Action<Base> ke variabel jenis Action<Derived>.

  • Invariance

    Berarti Anda hanya dapat menggunakan jenis yang awalnya ditentukan. Parameter jenis generik invarian bukan kovarian atau kontravarian.

    Anda tidak dapat menetapkan instans List<Base> ke variabel jenis List<Derived> atau sebaliknya.

Parameter jenis kovarian memungkinkan Anda membuat tugas 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 menangani sisanya.

Kontravariansi, di sisi lain, tampaknya berlawanan. Contoh berikut membuat delegasi jenis Action<Base> (Action(Of Base) di Visual Basic), lalu menetapkan delegasi tersebut 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 ini adalah kode jenis aman yang mengkompilasi dan menjalankan. Ekspresi lambda cocok dengan delegasi yang ditetapkan, sehingga mendefinisikan metode yang mengambil satu parameter jenis Base dan yang tidak memiliki nilai pengembalian. Delegasi yang dihasilkan dapat ditetapkan ke variabel bertipe Action<Derived> karena parameter tipe T dari delegasi Action<T> bersifat kontravarian. Kode aman tipe karena T menetapkan tipe parameter. Ketika delegasi jenis Action<Base> dipanggil seolah-olah itu adalah delegasi jenis Action<Derived>, argumennya harus berjenis Derived. Argumen ini selalu dapat diteruskan dengan aman ke metode yang mendasar, karena parameter metode berjenis Base.

Secara umum, parameter tipe kovarian dapat digunakan sebagai tipe pengembalian dari sebuah delegasi, dan parameter tipe kontravarian dapat digunakan sebagai tipe parameter. Untuk antarmuka, parameter jenis kovarian dapat digunakan sebagai jenis pengembalian metode antarmuka, dan parameter jenis kontravarian dapat digunakan sebagai jenis parameter metode antarmuka.

Kovariansi dan kontravariansi secara kolektif disebut sebagai varian. Parameter jenis generik yang tidak ditandai kovarian atau kontravarian disebut sebagai invarian. Ringkasan singkat fakta tentang variasi dalam runtime bahasa pemrograman 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 invariant untuk jenis konstruksi 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) di Visual Basic), Anda tidak dapat menggabungkan delegasi kedua dengan yang pertama meskipun hasilnya akan aman. Varians memungkinkan delegasi kedua ditetapkan ke variabel jenis Action<Derived>, tetapi delegasi hanya dapat menggabungkan jika jenisnya cocok dengan persis.

  • Mulai dari C# 9, jenis pengembalian kovarian didukung. Metode overriding dapat menyatakan tipe pengembalian yang lebih spesifik dari metode yang di-overriding, dan properti read-only yang di-overriding dapat mendeklarasikan tipe yang lebih spesifik.

Antarmuka generik dengan parameter jenis kovarian

Beberapa antarmuka generik memiliki parameter jenis kovarian, misalnya, , IEnumerable<T>, IEnumerator<T>IQueryable<T>, dan IGrouping<TKey,TElement>. Semua parameter jenis antarmuka ini adalah kovarian, oleh karena itu, parameter jenis hanya digunakan untuk jenis pengembalian dari anggota.

Contoh berikut mengilustrasikan parameter tipe kovarian. Contoh mendefinisikan dua jenis: Base memiliki metode statis bernama PrintBases yang mengambil IEnumerable<Base> (IEnumerable(Of Base) di Visual Basic) dan mencetak elemen. Derived mewarisi dari Base. Contoh membuat objek kosong List<Derived> (List(Of Derived) di Visual Basic) dan menunjukkan bahwa jenis ini dapat diteruskan ke PrintBases dan dialokasikan ke variabel jenis IEnumerable<Base> tanpa perlu konversi. 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 kontravarian; misalnya: IComparer<T>, IComparable<T>, dan IEqualityComparer<T>. Antarmuka ini hanya memiliki parameter jenis kontravarian, sehingga parameter jenis hanya digunakan sebagai jenis parameter dalam anggota antarmuka.

Contoh berikut mengilustrasikan parameter jenis kontravarian. Contoh mendefinisikan kelas abstrak (MustInherit di Visual Basic) Shape dengan Area properti . Contoh ini juga mendefinisikan ShapeAreaComparer kelas yang mengimplementasikan IComparer<Shape> (IComparer(Of Shape) di Visual Basic). Implementasi IComparer<T>.Compare metode didasarkan pada nilai Area properti, sehingga ShapeAreaComparer dapat digunakan untuk mengurutkan Shape objek berdasarkan area.

Kelas Circle mewarisi Shape dan mengambil alih Area. Contoh ini membuat SortedSet<T> dari Circle objek, menggunakan konstruktor yang menerima IComparer<Circle> (IComparer(Of Circle) dalam Visual Basic). Namun, alih-alih meneruskan IComparer<Circle>, contoh melewati objek ShapeAreaComparer, yang mengimplementasikan IComparer<Shape>. Contoh dapat memanfaatkan pembanding dari jenis yang kurang lebih turunan (Shape) ketika kode memanggil pembanding dari jenis yang lebih diturunkan (Circle), karena parameter jenis antarmuka generik yang bersifat kontravarian (IComparer<T>).

Ketika objek baru Circle ditambahkan ke SortedSet<Circle>, IComparer<Shape>.Compare metode (IComparer(Of Shape).Compare metode di Visual Basic) objek ShapeAreaComparer dipanggil setiap kali elemen baru dibandingkan dengan elemen yang ada. Jenis parameter metode (Shape) kurang berasal dari jenis yang sedang diteruskan (Circle), sehingga panggilan berjenis aman. Kontravarian 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 umum dengan parameter tipe yang bervariasi

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 Func dan (dalam kasus delegasi generik) jenis pengembalian yang kurang diturunkan.

Nota

Parameter jenis generik terakhir dari Func delegasi generik menentukan jenis nilai yang dikembalikan dalam tanda tangan delegasi. Ini kovarian (out kata kunci), sedangkan parameter jenis generik lainnya kontravarian (in kata kunci).

Kode berikut mengilustrasikan hal ini. Bagian pertama kode mendefinisikan kelas bernama Base, kelas bernama Derived yang mewarisi Base, dan kelas lain dengan static metode (Shared di Visual Basic) bernama MyMethod. Metode ini mengambil instans Base dan mengembalikan instans Derived. (Jika argumen adalah instans Derived, MyMethod mengembalikannya; jika argumen adalah instans Base, MyMethod mengembalikan instans baru Derived.) Dalam Main(), contoh membuat instans Func<Base, Derived> (Func(Of Base, Derived) di 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 menunjukkan bahwa delegate dapat ditetapkan ke variabel dengan tipe Func<Base, Base> (Func(Of Base, Base) di Visual Basic), karena tipe pengembalian bersifat 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 kode menunjukkan bahwa delegasi dapat ditetapkan ke variabel jenis Func<Derived, Derived> (Func(Of Derived, Derived) di 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 menunjukkan bahwa delegasi dapat ditetapkan ke variabel dengan jenis Func<Derived, Base> (Func(Of Derived, Base) di Visual Basic), yang menggabungkan efek tipe parameter kontravarian dan tipe 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())

Variasi pada delegat non-generik

Dalam kode sebelumnya, tanda tangan MyMethod sama persis dengan tanda tangan delegasi generik yang dibangun: Func<Base, Derived> (Func(Of Base, Derived) di Visual Basic). Contoh 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 Func<T,TResult>delegasi generik .

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 Variansi dalam Delegasi (Visual Basic)). Namun, varians dalam pengikatan delegasi berfungsi dengan semua jenis delegasi, bukan hanya dengan jenis delegasi generik yang memiliki parameter jenis varian. Selain itu, variansi dalam pengikatan delegasi memungkinkan suatu metode terikat kepada delegasi apa pun yang memiliki jenis parameter lebih ketat dan jenis pengembalian yang kurang ketat, sedangkan penetapan delegasi generik hanya berfungsi jika kedua jenis delegasi dibangun dari definisi tipe generik yang sama.

Contoh berikut menunjukkan efek gabungan variasi dalam pengikatan delegasi dan variasi dalam parameter tipe generik. Contoh mendefinisikan hierarki jenis yang mencakup tiga jenis, dari yang paling tidak diturunkan (Type1) hingga yang paling turunan (Type3). Variasi dalam pengikatan delegasi biasa digunakan untuk mengikat metode dengan jenis parameter Type1 dan jenis pengembalian Type3 ke delegasi generik dengan jenis parameter Type2 dan jenis pengembalian Type2. Delegasi generik yang dihasilkan kemudian ditetapkan ke variabel lain yang jenis delegasi generiknya memiliki parameter dengan jenis Type3 dan jenis pengembalian Type1, 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

Menentukan antarmuka generik varian dan delegasi

Visual Basic dan C# memiliki kata kunci yang memungkinkan Anda menandai parameter jenis generik antarmuka dan delegasi sebagai kovarian atau kontravarian.

Parameter jenis kovarian ditandai dengan out kata kunci (Out kata kunci di Visual Basic). Anda dapat menggunakan parameter jenis kovarian sebagai nilai pengembalian metode milik antarmuka, atau sebagai jenis pengembalian delegasi. Anda tidak dapat menggunakan parameter jenis kovarian sebagai batasan jenis generik untuk metode antarmuka.

Nota

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 kontravarian ditandai dengan in kata kunci (In kata kunci di Visual Basic). Anda dapat menggunakan parameter jenis kontravarian sebagai jenis parameter metode milik 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 melanggar aturan untuk menggunakan parameter jenis kovarian dan kontravarian, atau menambahkan anotasi kovarian dan kontravarian ke parameter jenis selain antarmuka dan delegasi.

Untuk informasi dan contoh kode, lihat Varians dalam Antarmuka Generik (C#) dan Varians di Antarmuka Generik (Visual Basic).

Daftar tipe

Jenis antarmuka dan delegasi berikut memiliki parameter jenis kovarian dan/atau kontravarian.

Tipe Parameter tipe kovarian 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