C# Covariance ve Contravariance

Covariance ve Contravariance, birbirinden türetilmiş tiplerin kendi arasında tür dönüşümü yapılması, birbiriyle yer değiştirilebilmesi ve eşitlikleri gibi karşılaştığımız belirli durumlardır. (Örneğin array tipleri, delegate tipleri ve generic tip argumanlar) Covariance atanabilme uyumluluğunu korumamıza olanak tanırken contravarience tam tersi olarak işlev görmektedir.

  • covariant : Geniş türlerin (more derived) daha küçük türlere (less derived) dönüştürülmesi durumu. Matematiksel olarak <= durumu.
  • contravariant: Küçük türlerin (less derived) daha büyük türlere (more derived) dönüştürülmesi durumu.

C# içerisinde türlerin değişme (variance) özelliklerine bakıldığında kullanım alanı aşağıdaki gibidir.

  • Covariance, dizilerde (arrays) C# 1.0 versiyonundan bu yana kullnımdadır.
  • Covariance ve contravariance, delegelerde (delegates) “method group variance” olarakta bilinmekte ve C# 2.0 versiyonundan bu yana kullanımdadır.
  • Variance (değişme) özelliği interface’lerde bulunan generic tip parametreler ve delegate’lerde C# 4.0 versiyonundan bu yana kullanımdadır.


Yazıya devam etmeden önce şunu netleştirmek istiyorum;
Object sınıfı tüm (types) tülerin ana (base) sınıfıdır. Örneğin bir sınıf yazdığınızda sınıf otomatik olarak Object sınıfını implement eder bu .Net’in varsayılan davranış biçimidir.

Yazıda sıkça geçen
(more derived) fazla genişletilmiş türler, örnek olarak string sınıfı Object sınıfını ile genişletilmiş bir sınıftır.
(less derived) az genişletilmiş türler , Object sınıfı string sınıfına göre daha az genişletilmiştir çünkü implement ettiği başka tür yoktur.

Bu konuyla ilgili daha detaylı bilgiye buradaki makaleden ulaşabilirsiniz.

Atanabilme Uyumluluğu (Assignment Compatibility)

Atanabilme uyumluluğu aslında C# programcıları için yeni bir özellik değildir uzun zamandır kullandığımız bir ifadedir, yeni olan sadece ismidir.

Bir örnek üzerinde inceleyecek olursak :

            String stringObject = "String Değişken";
            // Daha fazla genişletilmiş (more derived) olan String değişkeni (stringObject) daha az 
            // genişletilmiş (less drived) olan Object türünden değişkene (anObject) atanabilmiştir.
            Object anObject = stringObject;

Array Covariance

Array veri tipleri için Covariance .Net 1.0 dan bu yana kullanılan bir özelliktir, array’ler ile çalışırken aşağıdaki gibi atama işlemini yapabilmemize olanak sağlar

object[] objArray = new String[10];

Burada string türünden bir array object türünden array’a atanmıştır. Daha fazla genişletilmiş (derived) olan array (String[]) daha az genişletilmiş olan (object[]) türünden değişkene atanabilmiştir bu duruma arrayler’de covarience denmektedir.

Array Covariance güvenli bir (type safe) özellik değildir. Aşağıdaki kodu inceleyecek olursak neden güvenli olmadığını daha iyi anlayabiliriz.

objArray[0] = 5;

Böyle bir ifade compile time’da bir hataya sebep olmayacaktır ancak runtime’da ArrayTypeMismatchException exception’ına sebep olacaktır. Çünkü objArray değişkeni aslında string Array’e referans etmektedir.

Covariance

IEnumerable<string> strings = new List<string>();
// Daha fazla genişletilmiş (more derived) olan List<string> daha az genişletilmiş 
// (less drived) olan IEnumerable<string> türünden değişkene atanabilmiştir.
// C# 4.0 dan önce burada Compiler error alınıyordu.
// Atanabilme uyumluluğu (Assignment compatibility) korunmuştur.
IEnumerable<object> objects = strings;

Delegate Covariance

Bu tür (variance) değişme özelliği aslında method grup variance olarakta bilinmektedir. Burada yine genişletilmiş (more derived ) türlerin daha küçük türlere (less derived) dönüştürülmesi durumu söz konusudur.

class Mammals
{}
class Dogs : Mammals
{ }
class Program
{
    // Delegate tanımımızı yaptık.
    public delegate Mammals HandlerMethod();

    public static Mammals FirstHandler()
    {
        return null;
    }
    public static Dogs SecondHandler()
    {
        return null;
    }
    static void Main()
    {
        HandlerMethod handler1 = FirstHandler;
        // Aşağıda görüldüğü üzere Mammals sınıfından türeyen Dog Sınıfı metodu ana  
        // sınıf olan Mammals sınıfından delegate te atanabilip Covariance sağlanmıştır.
        HandlerMethod handler2 = SecondHandler;
    }
}

Generic Delegate Covariance ve Contravariance

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Main()
{
    // Covariance : Dönüş türü object olan delegate,
    // geri dönüş türü string olan metoda atanabilmiştir.
    Func<object> del = GetString;

    // Contravariance:  Object türü parametre alan metod,
    // string türünde parametre alan delegate'e atanabilmiştir.
    Action<string> del2 = SetObject;

    // Fakat aşağıda dikkat edileceği üzere implicit tür dönüşümü 
    // generic delegate'ler C# 4.0'a kadar desteklenmiyordu.
    Func<string> del3 = GetString;
    Func<object> del4 = del3; // C# 4.0 dan önce burada Compiler error alınıyordu.
}

Variance for generic type Parameters

.Net 4.0 ile birlikte birbirinden farklı türlerde arguman alan interface instance’ları arasında tür dönüşümü yapabilir olduk.

Yani herhangi bir interface instance’ının içerdiği bir metod geri dönüş değeri olarak orjinal tanımlanan türden daha genişletilmiş (more derived) bir tür dödürebilir (Covariance) veya bu metod parametre olarak orjinal tanımlanan türden daha az genişletilmiş (less derived) parametre türleri (Contravariance) alabilmektedir.

Değişken (Variant) generic interface’lerde generic türde parametre tanımlaması “out” anahtar kelimesi kullanılarak yapılır.

“ref” ve “out” parametreleri değişken (variant) olarak tanımlanamaz. Sadece referans veri tipleri (referance data types) değişken (variant) olarak tanımlanabilir, değer tipleri (value types) değişken (variant) olarak tanımlanamaz.

Örneğin

IEnumerable<int>

implicit (kalıcı) olarak

IEnumerable<object>

türüne çevrilemez çünkü integer’lar değer tiplerini (value types) temsil etmektedir.

Generic tipler interface metodların sadece dönüş tipi olarak kullanılır, metodlara argüman olarak kullanılmaz.

interface ICovariant<out R>
{
    R GetSomething();
    // Aşağıdaki gibi bir kullanımda compiler error oluşacaktır.
    // void SetSometing(R sampleArg);
}

Eğer contravariant bir generic delegate’imiz olsaydı metod parametresi olarak generic tipi aşağıdaki gibi kullanabilirdik.

interface ICovariant<out R>
{
    void DoSomething(Action<R> callback);
}

Contravariant generic tip parametreler “in” anahtar kelimesi kullanılarak yapılır.
Aşadaki örnektede görüleceği üzere bir interface farklı tipte parametre alarak covariance ve contravariance özelliğini kullanabilir.

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

Bu bahsettiklerime ek olarak .NET 4.0 ile birlikte gelen variance desteği bulunan diğer generic interface’ler aşağıdaki gibidir.

Kaynak;

Wiki Covariance and contravariance (computer science)
Covariance and Contravariance (C# and Visual Basic)
Covariance and Contravariance in Delegates (C#)
Covariance and Contravariance in Generics

Generic Tipler için özellikle okumanızı tavsiye ederim :

Variance in Generic Types (C# Programming Guide)

Bir cevap yazın