Covariance dan Contravariance pada C#

Pada .NET 4.0 ada Interface  IComparable<T> dan IEnumerable<T> yang defenisinya sebagai berikut :

public interface IComparable<in T>
{
       int CompareTo(T other);
}

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}


Kalau di perhatikan pada parameter generiknya ada keyword in dan out. Itu adalah keyword yang menandakan bahwa type parameter dari generic type tersebut bertipe covariance(jika di tandai dengan out) dan contravariance(jika di tandai dengan in).

Sepatah dua patah kata dari MSDN Library :
In programming languages, covariance is the ability to use a more derived type than that originally specified. Contravariance is the ability to use a less derived type.
In .NET Framework 4 and Visual Studio 2010, both C# and Visual Basic support covariance and contravariance in generic interfaces and delegates and allow for implicit conversion of generic type parameters.

Dengan bahasa yang lebih sederhana :
Covariance : Kemampuan untuk menggunakan Sub type dari type yang telah ditentukan.
Contravariance : Kemampuan untuk menggunakan Base type dari type yang telah ditentukan.
Invariance : Hanya dapat menggunakan Type sesuai dengan type yang telah ditentukan.

Suatu type yang mendukung covariance atau contravariance maka type tersebut disebut variance.
Dan suatu type yang tidak mendukung covariance atau contravariance maka type tersebut disebut invariance.
Dan di .NET 4.0, generic interface dan generic delegates sudah mendukung fitur covariance dan contravariance.

Ok, sebelumnya biar tidak terlalu pusing perhatikan kode berikut ini :

class Base { }
class Sub : Base { }  
class SubFromSub : Sub { }


Dan renungkan didalam hati dengan tulus berdasarkan kode diatas, Secara simple dapat dikatakan bahwa:
Base adalah Covariance dari Sub.
Dan SubFromSub adalah Contravariance dari Sub.

Suatu type di katakan mendukung kemampuan Covariance, apabila dia mendukung kode dibawah ini :

Collection<Sub> c1 = new Collection<Sub>();
Collection<Base> c2 = c1;


Dan suatu type di katakan mendukung kemampuan Contravarian, apabila dia mendukung kode dibawah ini :

Collection<Base> c1 = new Collection<Base>();
Collection<Sub> c2 = c1;


Class
Pada dasarnya Class mendukung fitur covariance. Perhatikan kode berikut :

class Base { }
class Sub : Base { }


//Pada method Main
Sub s = new Sub();
Base b = s; // ini tidak akan menyebabkan error


Generic Class
Tetapi generic class tidak mendukung covariance. Makanya kode berikut ini akan menyebabkan error :

class Base { }
class Sub : Base { }
class Collection<T> { }

//Pada method Main
Collection<Sub> c1 = new Collection<Sub>();
Collection<Base> c2 = c1; // ini akan menyebabkan error


Array
Berbeda, dengan array. Karena array mendukung covariance, maka kode berikut akan berjalan mulus :

class Base { }
class Sub : Base { }

//Pada method Main
Sub[] s = new Sub[3];
Base[] b = s; // ini tidak akan menyebabkan error


Covarian pada Generic Interface
Nah, bila kita tambahkan suatu interface generic ke dalam kode kita seperti berikut ini :

interface IGetNewObj<T> { T GetNewObj();}
class Base { }
class Sub : Base { }
class Collection<T> : IGetNewObj<T> where T : new()
{
        public T GetNewObj() { return new T(); }
}

//Pada method Main
IGetNewObj<Sub> var1 = new Collection<Sub>();
IGetNewObj<Sub> var2 = var1; // ini tidak akan menyebabkan error
IGetNewObj<Base> var3 = var1; // ini akan menyebabkan error


Kode diatas akan menyebabkan error karena interface IGetNewObj<T> mempunyai type parameter yang invariance. Jadi generic interface tersebut hanya bisa menggunakan type parameter yang sesuai dengan type parameter yang telah ditentukan sebelumnya (IGetNewObj<Sub> hanya bisa di convert ke IGetNewObj<Sub>).

Tetapi pada IGetNewObj<T>, T bisa kita tentukan menjadi covariance dengan menambahkan keyword out. Seperti kode berikut :

interface IGetNewObj<out T> // keyword out ditambahkan
{ T GetNewObj();}

So, Kode berikut akan berjalan dengan sangat mulus :

//Pada method Main
IGetNewObj <Sub> var1 = new Collection<Sub>();
IGetNewObj <Sub> var2 = var1; // ini tidak akan menyebabkan error
IGetNewObj <Base> var3 = var1; // ini tidak akan menyebabkan error


Dan berdasarkan MSDN library, penggunaan keyword out tersebut mempunyai batasan yaitu : T hanya bisa digunakan sebagai return type (dalam kasus ini, T sebagai return type dari method GetNewObj. Jadi, T tidak bisa digunakan sebagai method parameter)

Contravarian pada Generic Interface
Ok, selanjutnya kita akan membahas contravariance. Sesuai namanya maka contravariance merupakan kebalikan dari covariance. Perhatikan kode berikut ini :

interface IPrintObjInfo<T> { void PrintObjInfo(T Obj);}
class Base { }
class Sub : Base { }
class Collection<T> : IPrintObjInfo<T>
{
    public void PrintObjInfo(T Obj) { }
}

//Pada Method Main
IPrintObjInfo<Base> var1 = new Collection<Base>();
IPrintObjInfo<Base> var2 = var1; // ini tidak akan menyebabkan error
IPrintObjInfo<Sub> var3 = var1; // ini akan menyebabkan error


Kode diatas akan menyebabkan error, karena interface IPrintObjInfo adalah invariance. Untuk mengatasinya kita bisa menambahkan keyword in kedalam deklarasi dari interface IPrintObjInfo<T>. perhatikan kode berikut :

interface IPrintObjInfo<in T> { void PrintObjInfo(T Obj);} // Keyword in ditambahkan

Dan seperti keyword out(Covariance), maka contravariance juga mempunyai constrain yaitu :
T yang didefenisikan sebagai contravariance(dengan keyword in) hanya bisa sebagai menjadi parameter input dari suatu method dan tidak bisa menjadi output(return value). Oleh karena itu type kembalian dari method PrintObjInfo adalah bukan T(melainkan void)

Sekarang, kode berikut tidak akan menyebabkan error lagi karena kita telah menambahkan keyword in pada interface(IPrintObjInfo<in T>) :

IPrintObjInfo<Base> var1 = new Collection<Base>();
IPrintObjInfo<Base> var2 = var1; // ini tidak akan menyebabkan error
IPrintObjInfo<Sub> var3 = var1; // ini tidak akan menyebabkan error


Varian pada Generic Delegate
Dengan prinsip yang sama seperti yang kita bahas pada text diatas, covariance dan contravariance juga bisa di aplikasikan ke generic delegate. Perhatikan kode berikut :

delegate T2 PrintMessage<T1, T2>(T1 obj);
class Base { }
class Sub : Base { }  
class SubOfSub : Sub { }

//Pada Method Main()
PrintMessage<Sub, Sub> var1 = (Sub obj) => { return new Sub(); };
PrintMessage<SubOfSub, Base> var2 = var1; // ini akan menyebabkan error


Pengaplikasian covariance dan contravariance pada delegate akan menyebabkan kode diatas berjalan mulus seperti di bawah ini. Perhatikan :

delegate T2 PrintMessage<in T1, out T2>(T1 obj); // penambahan keyword in dan out
class Base { }
class Sub : Base { }  
class SubOfSub : Sub { }

//Pada Sub Main()
PrintMessage<Sub, Sub> var1 = (Sub obj) => { return new Sub(); };
PrintMessage<SubOfSub, Base> var2 = var1; // ini tidak akan menyebabkan error

Satu hal yang perlu diingat bahwa penambahan keyword in dan out pada suatu Type Parameter dari Generic Type adalah untuk membuat Generic Type tersebut menjadi variance

Ok, itulah pembahasan mengenai covariance dan contravariance. Semoga berguna.

Share this post: | | | |
Published Wednesday, January 26, 2011 8:24 PM by Agus Syahputra

Comments

No Comments