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ıtlama | Anlamı |
|---|---|
where T : class | Referans tipi olmalı |
where T : struct | Değer tipi olmalı |
where T : new() | Parametresiz kurucusu olmalı |
where T : IVarlik | Belirli bir arayüzü uygulamalı |
where T : notnull | Null 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.