30 Temmuz 2020 Perşembe

in ve out ile Generic Inteface Kullanımı

S.A. Arkadaşlar,
    Bugün generic interface kullanımını ilgilendiren bir yazıyı ele alacağız. Bu yazının çıkış noktası ise önceki yazımızda ele aldığım sonarlint’in verdiği uyarıları çözmek ile ilgiliydi. Burada karşılaştığım bu uyarıyı araştırırken güzel bilgiler öğrendim 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 da isteyebiliriz. Basit anlamda 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 veya 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 T tipi almaktadır. Burada T hem dönüş tipi olarak kullanılmıştır hem de parametre olarak de kullanılmıştır. Buraya kadar bir sorun yoktur, 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 şu anlatılıyor. Generic nesneyi parametre olarak geçerseniz “in” kullanarak burada sadece parametre olarak geçildiğini belirtmemiz isteniyor.

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 ki “in” keyword’ünü kullanıp 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öylenmektedir.

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’ünü kullandık. Bu şekilde kullanımda 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şıyoruz.

      Burada dikkat edilmesi gereken bir başka konu ise farklı tiplerin birlikte kullanılabileceğidir. 2 ayrı tip kullanımının birini dönüş, diğerini parametre olarak kullanabiliriz. Burada her hangi bir problem yoktur. Şö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 istedim)

Ş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 ü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.

     Teknik borcu düşük kodlar yazmak dileğiyle.

Kaynak

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

Hiç yorum yok:

Yorum Gönder