Bagikan melalui


Membuat Varian Antarmuka Generik (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 memiliki jenis argumen yang lebih sedikit diturunkan daripada yang ditentukan oleh parameter generik. Antarmuka generik yang memiliki parameter jenis generik kovarian atau kontravarian disebut varian.

Catatan

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

Mendeklarasikan Antarmuka Generik Varian

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

Penting

Parameter ref, in, dan out dalam C# tidak boleh berupa varian. Jenis nilai juga tidak mendukung varians.

Anda dapat mendeklarasikan kovarian parameter jenis generik dengan menggunakan kata kunci out. 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 jenis generik untuk delegasi. Hal ini diilustrasikan oleh jenis R dalam contoh berikut. Untuk informasi selengkapnya, lihat Varians di Delegasi (C#) dan Menggunakan Varian untuk Delegasi Generik Func dan Tindakan (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 kata kunci in. 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 kontravariansi 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 invariant. Sebagai contoh, perhatikan 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 Antarmuka Generik Varian

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

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

Dalam antarmuka IInvariant<T>, T parameter jenis generik adalah invariant, sedangkan dalam IExtCovariant<out T>, parameter jenis adalah kovarian, meskipun kedua antarmuka 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 adalah kovarian dan antarmuka yang kontravarian jika dalam antarmuka perluasan parameter jenis generik T adalah invarian. Ini diilustrasikan dalam contoh kode berikut.

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

Namun, jika parameter jenis generik T dinyatakan sebagai kovarian dalam satu antarmuka, Anda tidak dapat mendeklarasikannya sebagai 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

Saat Anda menerapkan antarmuka generik varian, varian 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 menciptakan ambiguitas. Pengompilasi tidak menghasilkan kesalahan dalam kasus ini, tetapi tidak ditentukan implementasi antarmuka mana yang akan dipilih pada durasi. Ambiguitas ini dapat menyebabkan bug halang 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 metode pets.GetEnumerator memilih antara Cat dan Dog. Ini dapat menyebabkan masalah dalam kode Anda.

Lihat juga