Bagikan melalui


Membuat Antarmuka Generik Varian (C#)

Anda dapat mendeklarasikan parameter jenis generik dalam antarmuka sebagai kovarian atau kontravarian. Kovarian memungkinkan metode antarmuka untuk memiliki jenis pengembalian yang lebih turunan daripada yang ditentukan oleh parameter jenis generik. Kontravarian memungkinkan metode antarmuka untuk memiliki jenis argumen yang kurang diturunkan dari yang ditentukan oleh parameter generik. Antarmuka generik yang memiliki parameter jenis generik kovarian atau kontravarian disebut varian.

Nota

.NET Framework 4 memperkenalkan dukungan varians untuk beberapa antarmuka generik yang ada. Untuk daftar antarmuka varian di .NET, lihat Varians di Antarmuka Generik (C#).

Mendeklarasikan Varian Antarmuka Generik

Anda dapat mendeklarasikan antarmuka generik varian dengan menggunakan in kata kunci dan out untuk parameter jenis generik.

Penting

ref, in, dan out parameter dalam C# tidak dapat menjadi varian. Jenis nilai juga tidak mendukung varians.

Anda dapat mendeklarasikan kovarian parameter jenis generik dengan menggunakan out kata kunci. Jenis kovarian harus memenuhi kondisi berikut:

  • Jenis hanya digunakan sebagai jenis pengembalian metode antarmuka dan tidak digunakan sebagai jenis argumen metode. Ini diilustrasikan dalam contoh berikut, di mana jenis R dinyatakan kovarian.

    interface ICovariant<out R>
    {
        R GetSomething();
        // The following statement generates a compiler error.
        // void SetSomething(R sampleArg);
    
    }
    

    Ada satu pengecualian untuk aturan ini. Jika Anda memiliki delegasi generik kontravarian sebagai parameter metode, Anda dapat menggunakan jenis sebagai parameter tipe untuk delegasi generik tersebut. Ini diilustrasikan oleh jenis R dalam contoh berikut. Untuk informasi selengkapnya, lihat Variansi dalam Delegat (C#) dan Menggunakan Variansi untuk Delegat Generik Func dan Action (C#).

    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • Jenis ini tidak digunakan sebagai batasan generik untuk metode antarmuka. Ini diilustrasikan dalam kode berikut.

    interface ICovariant<out R>
    {
        // The following statement generates a compiler error
        // because you can use only contravariant or invariant types
        // in generic constraints.
        // void DoSomething<T>() where T : R;
    }
    

Anda dapat mendeklarasikan parameter jenis generik kontravarian dengan menggunakan in kata kunci. Jenis kontravarian hanya dapat digunakan sebagai jenis argumen metode dan bukan sebagai jenis pengembalian metode antarmuka. Jenis kontravarian juga dapat digunakan untuk batasan generik. Kode berikut menunjukkan cara mendeklarasikan antarmuka kontravarian dan menggunakan batasan generik untuk salah satu metodenya.

interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error.
    // A GetSomething();
}

Dimungkinkan juga untuk mendukung kovariansi dan kontravarians dalam antarmuka yang sama, tetapi untuk parameter jenis yang berbeda, seperti yang ditunjukkan dalam contoh kode berikut.

interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSomethings(A sampleArg);
}

Menerapkan Antarmuka Generik Varian

Anda menerapkan antarmuka generik varian di kelas dengan menggunakan sintaks yang sama yang digunakan untuk antarmuka invarian. Contoh kode berikut menunjukkan cara mengimplementasikan antarmuka kovarian di kelas generik.

interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

Kelas yang mengimplementasikan antarmuka varian adalah tidak bervariasi. Misalnya, pertimbangkan kode berikut.

// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

Memperluas Varian Antarmuka Generik

Ketika Anda memperluas antarmuka generik varian, Anda harus menggunakan kata kunci in dan out untuk menentukan secara eksplisit apakah antarmuka turunan mendukung varians. Kompilator tidak menyimpulkan perbedaan dari antarmuka yang diekstensikan. Misalnya, pertimbangkan antarmuka berikut.

interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

Dalam antarmuka IInvariant<T>, parameter jenis generik T yang bersifat invariant, sedangkan dalam IExtCovariant<out T>, parameter jenis yang bersifat kovarian, meskipun keduanya memperluas antarmuka yang sama. Aturan yang sama diterapkan ke parameter jenis generik kontravarian.

Anda dapat membuat antarmuka yang memperluas antarmuka di mana parameter jenis generik T bersifat kovarian dan antarmuka di mana parameter tersebut bersifat kontravarian, asalkan dalam antarmuka yang memperluas parameter jenis generik T tetap bersifat invariant. Ini diilustrasikan dalam contoh kode berikut.

interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

Namun, jika parameter T jenis generik dinyatakan kovarian dalam satu antarmuka, Anda tidak dapat menyatakannya kontravarian dalam antarmuka yang diperluas, atau sebaliknya. Ini diilustrasikan dalam contoh kode berikut.

interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

Menghindari Ambiguitas

Ketika Anda menerapkan antarmuka generik varian, varians terkadang dapat menyebabkan ambiguitas. Ambiguitas seperti itu harus dihindari.

Misalnya, jika Anda secara eksplisit menerapkan antarmuka generik varian yang sama dengan parameter jenis generik yang berbeda dalam satu kelas, itu dapat membuat ambiguitas. Pengkompilasi tidak menghasilkan kesalahan dalam kasus ini, tetapi tidak ditentukan implementasi antarmuka mana yang akan dipilih pada waktu proses. Ambiguitas ini dapat menyebabkan bug halus dalam kode Anda. Perhatikan contoh kode berikut.

// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
    IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
    {
        Console.WriteLine("Cat");
        // Some code.
        return null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        // Some code.
        return null;
    }

    IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
    {
        Console.WriteLine("Dog");
        // Some code.
        return null;
    }
}
class Program
{
    public static void Test()
    {
        IEnumerable<Animal> pets = new Pets();
        pets.GetEnumerator();
    }
}

Dalam contoh ini, tidak ditentukan bagaimana pets.GetEnumerator metode memilih antara Cat dan Dog. Ini dapat menyebabkan masalah dalam kode Anda.

Lihat juga