C# Covariance ve Contravariance

Erkan Güzelküçük
3 min readMar 31, 2023

C# dilinde Covariance ve Contravariance, genellikle birbirinin yerine kullanılabilen tipler arasındaki ilişkiyi ifade eden terimlerdir. Bu konu özellikle Generic türlerinde önemlidir. Bu makalede, Covariance ve Contravariance kavramlarını detaylı bir şekilde ele alacağız ve C# dilinde nasıl kullanıldığını örneklerle açıklayacağız.

Photo by Chris Lawton on Unsplash

Covariance

Covariance, temel olarak bir türün alt türüne dönüşebildiği durumları ifade eder. Örneğin, hayvanlar hiyerarşisinde bir köpek, hayvanın alt türü olarak kabul edilebilir. Benzer şekilde, C# dilinde de bir türün alt türüne dönüşebileceği durumlar vardır. Bu durum, genellikle bir türün bir başka türün alt türü olması durumunda gerçekleşir.

C# dilinde Covariance, genellikle geri dönüş türleriyle ilgili olarak kullanılır. Örneğin, bir metot, bir türün alt türü olan bir türü döndürebilir. Bu durumda, geri dönüş türü, alt türü içerebilir.

Aşağıdaki örnek, Covariance kavramını basit bir şekilde gösterir.

public class Animal
{
public virtual string GetName()
{
return "Animal";
}
}

public class Mammal : Animal
{
public override string GetName()
{
return "Mammal";
}
}

public class Dog : Mammal
{
public override string GetName()
{
return "Dog";
}
}

class Program
{
static Mammal GetMammal()
{
return new Dog();
}

static void Main(string[] args)
{
// Covariance kullanarak bir alt sınıfın örneğini döndürme
Animal animal = GetMammal();
Console.WriteLine(animal.GetName()); // "Dog" yazdırır
}
}

Yukarıdaki örnekte, “GetMammal” metodunun geri dönüş türü “Mammal” türüdür, ancak bu metot “Dog” türünde bir örnek döndürür. Bunun nedeni, “Dog” türünün “Mammal” türünün bir alt türü olmasıdır.

Contravariance

Contravariance, Covariance’in tam tersi olarak düşünülebilir. Yani, bir nesne türünden daha az özel bir türden başka bir nesne türüne dönüştürülebilir. Daha teknik olarak, bir fonksiyonun parametre türü için contravariance kullanılırken, fonksiyonu çağıran yerlerde daha genel bir tür kullanılabilir.

Contravariance kullanımı özellikle delegelerle çalışırken yararlıdır. Delegeler, olaylara yanıt olarak işlevler çağırmak için kullanılır. Aşağıdaki örnekte, contravariance kullanarak bir delegenin nasıl kullanılabileceği gösterilir:

delegate void AnimalHandler(Animal animal);
delegate void TigerHandler(Tiger tiger);

class Program
{
static void Main(string[] args)
{
TigerHandler tigerHandler = HandleTiger;
AnimalHandler animalHandler = tigerHandler;

animalHandler(new Animal()); // Çıktı: Animal işle
animalHandler(new Tiger()); // Çıktı: Tiger işle
}

static void HandleAnimal(Animal animal)
{
Console.WriteLine("Animal işle");
}

static void HandleTiger(Tiger tiger)
{
Console.WriteLine("Tiger işle");
}
}

class Animal { }
class Tiger : Animal { }

Bu örnekte, AnimalHandler delegesi bir hayvan nesnesini işleyebilirken, TigerHandler delegesi yalnızca bir kaplan nesnesini işleyebilir. Ancak, TigerHandler delegesi, AnimalHandler’a atanarak, bir hayvan nesnesini de işleyebilir. Böylece, contravariance kullanılarak TigerHandler tipi, AnimalHandler tipine dönüştürülebilir.

Bu örnek, contravariance’in delegelerde kullanımını gösterirken, aslında bu kavramın neden önemli olduğunu da açıklayabilir. Contravariance, bir türün daha genel bir türe dönüştürülmesine olanak tanıdığından, kodunuzun daha esnek hale gelmesine yardımcı olur ve aynı işlevi yürüten farklı türlerin kullanılabilmesini sağlar. Bu, özellikle geniş kapsamlı yazılım projeleri için önemlidir, çünkü bu tür projelerde genellikle çok sayıda farklı nesne türü kullanılır ve her biri birbirinden farklı şekilde kullanılır.

Covariance ve Contravariance, uygulama geliştirirken sıklıkla karşılaşılan ve potansiyel hatalara neden olabilen konulardan biridir. Bu hatalar, genellikle uyumsuz türlerden kaynaklanır.

Birinci örnekte, Covariant bir tür olan IEnumerable'dan, değiştirilemeyen bir öğeyi silme girişiminde bulunursak, bir hata alabiliriz. Çünkü IEnumerable sadece öğelerin okunmasına izin verir, ancak değiştirilemez.

IEnumerable<string> strings = new List<string>() { "foo", "bar", "baz" };
strings.RemoveAt(1); // HATA: 'IEnumerable<string>' bir kaldırma işlevi içermez

Benzer şekilde, Contravariant bir tür olan IComparer'da, yanlış bir sıralama yöntemi kullanırsak, hatalara neden olabiliriz.

public class ShapeComparer : IComparer<Shape>
{
public int Compare(Shape x, Shape y)
{
if (x.Area < y.Area) return -1;
if (x.Area > y.Area) return 1;
return 0;
}
}

Yukarıdaki örnekte, sıralama işlemi, Shape nesnelerinin alanlarına göre gerçekleştirilir. Ancak, bir Circle ve bir Rectangle'ı karşılaştırırken, bu iki nesnenin alanlarının karşılaştırılması yanıltıcı olabilir. Bu durum, uygulama çalıştırıldığında beklenmeyen sonuçlara neden olabilir.

Bu hataların önüne geçmek için, Covariance ve Contravariance’yi kullanmadan önce dikkatli bir şekilde düşünmek ve uyumsuz türlerin neden olabileceği potansiyel hataları anlamak önemlidir. Ayrıca, out ve in anahtar kelimelerini doğru şekilde kullanarak Covariant ve Contravariant türleri güvenli bir şekilde kullanabilirsiniz.

Sonuç olarak

Covariance ve Contravariance, C# dili içindeki tür uyumluluğunu geliştirmek için kullanılan önemli özelliklerdir. Covariance, alt sınıfların üst sınıflara atandığı yerlerde kullanılırken, Contravariance üst sınıfların alt sınıflara atandığı yerlerde kullanılır. Bu özellikler sayesinde, programlama dili içinde daha güvenli ve esnek kod yazabiliriz. Ancak, Covariance ve Contravariance, tür uyumluluğunu garanti edemeyen bazı durumlarda hata üretebilirler, bu nedenle bu özelliklerin dikkatli bir şekilde kullanılması önemlidir.

--

--

Erkan Güzelküçük

Software Architecture and Research Applications Development Team Leader at Koç University