Dengan <out T>
, Anda bisa memperlakukan referensi antarmuka sebagai referensi ke atas dalam hierarki.
Dengan <in T>
, Anda dapat memperlakukan referensi antarmuka sebagai referensi ke bawah di hiearchy.
Izinkan saya mencoba menjelaskannya dalam istilah yang lebih bahasa Inggris.
Katakanlah Anda mengambil daftar hewan dari kebun binatang, dan Anda berniat untuk memprosesnya. Semua hewan (di kebun binatang Anda) memiliki nama, dan ID unik. Beberapa hewan adalah mamalia, beberapa reptil, beberapa amfibi, beberapa ikan, dll. Tetapi semuanya adalah hewan.
Jadi, dengan daftar hewan Anda (berisi hewan dari berbagai jenis), Anda dapat mengatakan bahwa semua hewan memiliki nama, jadi jelas akan aman untuk mendapatkan nama semua hewan.
Namun, bagaimana jika Anda hanya memiliki daftar ikan, tetapi perlu memperlakukannya seperti hewan, apakah itu berhasil? Secara intuitif, ini seharusnya berfungsi, tetapi di C # 3.0 dan sebelumnya, bagian kode ini tidak akan dikompilasi:
IEnumerable<Animal> animals = GetFishes();
Alasan untuk ini adalah bahwa kompilator tidak "tahu" apa yang Anda inginkan, atau bisa , lakukan dengan koleksi binatang setelah Anda mengambilnya. Untuk semua yang diketahui, mungkin ada jalan masukIEnumerable<T>
untuk memasukkan kembali objek ke dalam daftar, dan itu berpotensi memungkinkan Anda untuk memasukkan hewan yang bukan ikan, ke dalam koleksi yang seharusnya hanya berisi ikan.
Dengan kata lain, kompilator tidak dapat menjamin bahwa ini tidak diperbolehkan:
animals.Add(new Mammal("Zebra"));
Jadi kompilator langsung menolak untuk mengkompilasi kode Anda. Ini adalah kovarians.
Mari kita lihat pelanggaran.
Karena kebun binatang kita bisa menangani semua hewan, pasti bisa menangani ikan, jadi mari kita coba menambahkan beberapa ikan ke kebun binatang kita.
Di C # 3.0 dan sebelumnya, ini tidak dapat dikompilasi:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Di sini, kompiler dapat mengizinkan potongan kode ini, meskipun metode tersebut kembali List<Animal>
hanya karena semua ikan adalah hewan, jadi jika kita mengubah tipenya menjadi ini:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Maka itu akan berhasil, tetapi kompilator tidak dapat menentukan bahwa Anda tidak mencoba melakukan ini:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Karena daftar tersebut sebenarnya adalah daftar hewan, hal ini tidak diperbolehkan.
Jadi kontra dan kovarian adalah bagaimana Anda memperlakukan referensi objek dan apa yang boleh Anda lakukan dengannya.
Kata kunci in
dan out
dalam C # 4.0 secara khusus menandai antarmuka sebagai satu atau yang lain. Dengan in
, Anda diizinkan untuk menempatkan tipe generik (biasanya T) di input -positions, yang berarti argumen metode, dan properti hanya-tulis.
Dengan out
, Anda diizinkan untuk menempatkan tipe generik dalam output -positions, yang merupakan nilai kembalian metode, properti hanya-baca, dan parameter metode keluar.
Ini akan memungkinkan Anda melakukan apa yang ingin dilakukan dengan kode:
IEnumerable<Animal> animals = GetFishes();
List<T>
memiliki arah masuk dan keluar di T, jadi ini bukan varian bersama atau kontra-varian, tetapi antarmuka yang memungkinkan Anda menambahkan objek, seperti ini:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
akan memungkinkan Anda melakukan ini:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Berikut beberapa video yang menunjukkan konsep tersebut:
Berikut contohnya:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Tanpa tanda ini, berikut ini dapat dikompilasi:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
atau ini:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants