• Malik Masis

<in T> ve <out T> ile Generic Inteface Kullanımı (Covariance - Contravariance)



https://www.slideshare.net/alinpandichi/covariance-and-contravariance-say-what-agile-talks-22


S.A. Arkadaşlar,

Bu yazımızda, generic interface kullanımını ele alacağız. Yazımızın çıkış noktası, önceki yazımızda ele aldığımız SonarLint’in verdiği uyarıları temizleyerek teknik borcumuzu azaltmaktı :) Burada karşılaştığımız bu uyarıyı araştırırken faydalı bilgiler öğrendik ve bunları bir yazıda toplamaya karar verdim.


Bildiğimiz üzere interface’leri bağımlılıkları azaltmak için kullanıyoruz, fakat bazen generic repository’de olduğu gibi interface’leri generic olarak kullanmak isteyebiliriz. Burada basit manada 2 kullanım şekli vardır. Bunlar:

  • Dönüş Tipi olarak kullanım

  • Parametre olarak kullanım


Burada ne demek istediğimizi açalım. Bildiğimiz üzere interface içerisinde metodların sadece imzası bulunur. Bunlar parametre alabilir ve dönüş tipleri olabilir. Bunu aşağıdaki örnek üzerinden inceleyelim.

public interface IGenericRepository<T>
{
      T FindById(object EntityId);//1.satır
      void Insert(T Entity);
}

Yukarıdaki interface’de görüldüğü üzere generic bir interface tanımlanmış ve parametre olarak T almaktadır. Burada T hem dönüş tipi olarak kullanılmıştır hem de parametre olarak kullanılmıştır. Buraya kadar bir sorun yok, fakat 1.satırdaki metod tanımlaması olmasaydı ne olurdu? İşte konu tam da buradan ortaya çıktı. Sadece parametre olarak veya sadece dönüş tipi olarak tanımlama yapılırsa SonarLint sizi uyaracaktır.

Generic type parameters should be co/contravariant when possible

Burada belirtilen uyarıya “contravariant” diyoruz. Yani bize, generic nesneyi parametre olarak geçerseniz “in” keyword’ü kullanarak sadece parametre olarak geçildiğini belirtmemizi istiyor.

public interface IGenericRepository<in T>
{
      //T FindById(object EntityId);
      void Insert(T Entity);
}
Invalid variance: The type parameter ‘T’ must be covariantly valid on ‘IGenericRepository<T>.FindById(object)’. ‘T’ is contravariant.

Eğer “in” keyword’ü kullanıp jenerik T döndüren metodu kullanmaya kalkarsak yukarıdaki gibi bir hata ile karşılaşırız. Burada bize tipin sadece parametre olarak geçildiği ve bu yüzden dönüş tipi olarak kullanamayacağımızı söylemektedir.

public interface IGenericRepository<out T>
{
      T FindById(object EntityId);
      //void Insert(T Entity);
}
Invalid variance: The type parameter ‘T’ must be contravariantly valid on ‘IGenericRepository<T>.Insert(T)’. ‘T’ is covariant.

Bu sefer de “out” keyword kullandık. Bu şekilde kullanım ise, tip sadece dönüş tipi olarak kullanılmaktadır. Eğer biz bunu parametre olarak kullanmak istersek yukarıdaki gibi bir hata ile karşılaşırız.

Burada dikkat edilmesi gereken bir başka konu ise, farklı tiplerin birlikte kullanılabileceğidir. 2 ayrı tip kullanımını, birini dönüş, diğerini parametre olarak kullanabiliriz. Burada herhangi bir problem yok.

Şöyle ki:

public interface IGenericRepository<out T, in Z>
{
    T FindById(object EntityId);
    void Insert(Z Entity);
}

Burada T ve Z farklı tiplerdir. O yüzden T dönüş tipi olarak kullanılırken, Z ise parametre olarak kullanılmalıdır. Aksi halde yine hata verecektir. (Buradaki örnekte böyle bir kullanıma ihtiyaç olmayabilir, ama konunun anlaşılması için aynı örnek üzerinden devam istedik.)

Şimdi konuyu biraz daha karmaşıklaştıralım. Bu 2 yapıyı Action ve Func yapılarıyla kullanmayı deneyelim. (Bir sonraki yazımızın konusu olmasını temenni ediyorum)

interface IBuildingManager<in T>
{
     Action<T> Sell();
     //Func<T> Buy();
}

Yukarıdaki kod derlenecek mi yoksa hata mı verecek? Yukarıda T nesnesini sadece parametre olarak kullanıp her hangi bir dönüş yapamayacağımızı söyledik, fakat burada kod derlenecektir. Bunun nedeni Action bir delegete’dir ve void dönüyordur. Bu yüzden de aslında burada her hangi bir dönüş olmamaktadır.

Şimdi de Func kullanımı inceleyelim. Func<T> bilindiği üzere bir değer döndürecektir. O yüzden “in” ile dönüş tipi döndürmek istersek hata ile karşılaşacağız. Bunun çalışması için “out” keyword’ünü kullanmamız gerekecektir.


Şimdi konuyu biraz farklı bir yere taşıyalım. Biri diğerinden üretilen 2 adet sınıfımız olsun. <out T> ile üst(parent) sınıf beklenen yere alt(child) sınıf geçilebilir. <in T> için ise alt sınıf beklenen yere alt sınıf parametre olarak geçilebilir.

Ne demek istediğimizi kod satırları üzerinden ifade etmeye çalışalım:

public class Car : Vehicle
{
   //Kodlar
}
public class Vehicle
{
    //Kodlar
}
public interface IGarageManager<in T>
{ 
   void Park(T item);
}
public class GarageManager<T> : IGarageManager<T>
{
    public void Park(T item)
    {
       //Kodlar
    }
}

Yukarıda Car sınıfı Vechile sınıfından üretilmiştir. Car sınıfı alt sınıf ve Vechile üst sınıf olmuştur. “in” ve “out” keyword’leriyle birbiri yerine kullanabileceğiz.

Şimdi bunları GarageManager içinde çağırdığımızda ne ile karşılaştığımıza bakalım.


IGarageManager<Vehicle> vehicleGarageManager =
 new GarageManager<Car>(); //(1)
IGarageManager<Car> carGarageManager =
 new GarageManager<Vehicle>();  //(2)

“in” 'keyword’ü kullandığımızda alt sınıf beklenen yerde üst sınıf kullanabiliriz demiştik. 1.satırdaki kodda üst sınıf beklenen(Vechile) yere alt sınıfı(Car) geçtiğimiz için bize aşağıdaki hatayı dönecektir. 2. satırda ise alt sınıf beklenen yere üst sınıf geçilebilir demiştik o yüzden burada her hangi bir sıkıntı oluşmayakcatır.


Cannot implicitly convert type ‘GarageManager<Car>’ to IGarageManager< Vehicle>’. An explicit conversion exists (are you missing a cast?)

“in” keywordü kullandığımız yerde “out” keyword’ü kullandığımızda ise bu sefer de üst sınıf bekleyen yerde alt sınıf kullanabileceğimizden 1.satırdaki kod çalışacak, fakat 2. satırdaki kod hata verecektir. (tabi ki Park metodu bu sefer parametre almayarak dönüş tipi döndürmelidir.)


Son olarak ise “out” ve “in” keyword’lerini kaldırırsak bu sefer her iki satır da cast işlemi isteyeceğinden her iki satır da hata verecektir.


Kullandığımız yapıları derinlemesine bilmek dileğiyle.

Kaynaklar:

https://ericlippert.com/2013/07/29/a-contravariance-conundrum/

https://agirlamonggeeks.com/2019/06/04/cannot-implicitly-convert-type-abc-to-iabc-contravariance-vs-covariance-part-2/#comment-108

https://stackoverflow.com/questions/10956993/out-t-vs-t-in-generics


10 views