Furkan KapukayaYazılım geliştirme
C# ve .NET3 dk okuma

C# Generics: Tür Güvenli ve Tekrar Kullanılabilir Kod Yazmak

C# generics kavramı, generic sınıf ve metotlar, kısıtlamalar (where), kovaryans ve kontravaryans, generic math ve generics kullanırken sık yapılan hatalar.

List<Musteri>, Dictionary<string, decimal>, Task<Siparis>... C# ile kod yazan herkes generics'i her gün kullanır. Generics, aynı mantığı farklı tiplerle tür güvenliğinden ödün vermeden çalıştırmayı sağlar. Kendi generic tiplerinizi yazmayı bilmek, tekrarı azaltmanın en güçlü yollarından biridir.

Neden generics?

Generics olmadan iki seçenek vardı: her tip için ayrı sınıf yazmak veya her şeyi object olarak tutup dönüşüm yapmak. İlki tekrar, ikincisi hem çalışma zamanı hatası hem de performans kaybı demekti.

public class Sonuc<T>
{
    public bool Basarili { get; init; }
    public T? Deger { get; init; }
    public string? Hata { get; init; }

    public static Sonuc<T> Ok(T deger) => new() { Basarili = true, Deger = deger };
    public static Sonuc<T> Fail(string hata) => new() { Basarili = false, Hata = hata };
}

Sonuc<Siparis> s = Sonuc<Siparis>.Ok(siparis);

Bu yapı hata yönetiminde anlatılan Result deseninin basit bir uygulamasıdır.

Generic metotlar

public static T EnBuyuk<T>(IEnumerable<T> liste) where T : IComparable<T>
{
    using var e = liste.GetEnumerator();
    if (!e.MoveNext()) throw new InvalidOperationException("Liste boş.");
    var max = e.Current;
    while (e.MoveNext())
        if (e.Current.CompareTo(max) > 0) max = e.Current;
    return max;
}

Derleyici çoğu zaman tipi kendisi çıkarır: EnBuyuk(fiyatlar).

Kısıtlamalar (where)

KısıtlamaAnlamı
where T : classReferans tipi olmalı
where T : structDeğer tipi olmalı
where T : new()Parametresiz kurucusu olmalı
where T : IVarlikBelirli bir arayüzü uygulamalı
where T : notnullNull olamayan tip

Kısıtlamalar, generic kod içinde o tipin yeteneklerini kullanmanızı sağlar. Örneğin where T : IVarlik ile T.Id özelliğine erişebilirsiniz.

Kovaryans ve kontravaryans

IEnumerable<string>, bir IEnumerable<object> beklenen yere verilebilir; çünkü arayüz out T ile tanımlanmıştır (kovaryans). Sadece veriyi tüketen arayüzlerde in T (kontravaryans) kullanılır. Kendi arayüzlerinizde bu anahtar kelimeleri, tipin yalnızca çıktı veya yalnızca girdi olarak kullanıldığı durumlarda eklemek esneklik sağlar.

Generic math

Modern C#, arayüzlerde statik soyut üyeler sayesinde sayısal tipler üzerinde generic hesaplama yazmayı mümkün kılar:

public static T Topla<T>(IEnumerable<T> sayilar) where T : INumber<T>
{
    T toplam = T.Zero;
    foreach (var s in sayilar) toplam += s;
    return toplam;
}

Aynı metot int, decimal ve double ile çalışır.

Sık yapılan hatalar

  • Her şeyi generic yapmak: Tek bir tiple kullanılacak kodu generic yazmak gereksiz karmaşıklıktır (YAGNI).
  • Generic repository'yi her projeye zorlamak: EF Core'un sunduğu yapının üstüne anlamsız bir katman eklemek.
  • Kısıtlama yerine çalışma zamanında tip kontrolü (if (typeof(T) == ...)) yapmak.

Sık sorulan sorular

Generics performansı etkiler mi?

Olumlu etkiler. Değer tipleri için kutulama (boxing) olmaz ve tür dönüşümü maliyeti ortadan kalkar.

Java generics ile farkı nedir?

.NET'te generic tip bilgisi çalışma zamanında korunur; bu da yansıma ve performans açısından avantaj sağlar.

Sonuç

Generics, tekrar kullanılabilir ve tür güvenli kodun temelidir. Gerçek bir tekrar ihtiyacı olduğunda doğru kısıtlamalarla kullanıldığında kodu hem kısaltır hem de daha güvenli hale getirir.