Menu

C# Generics: Generic Sınıflar, Metotlar ve Kısıtlar

C#'ta generics nasıl çalışır: tip parametreleriyle generic sınıflar ve metotlar yazmak, tip çıkarımı, where ile kısıtlar (class, struct, new(), temel sınıflar, interface'ler), default(T), tip başına statik üyeler ve IEnumerable<out T> ile kovaryans.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Generics, bir tipi ya da metodu bir kez yazıp tip güvenliğini kaybetmeden birçok tiple kullanmanızı sağlar. Cast gerektiren ve her şeyi kabul eden bir object List'i yerine, her biri derleyici tarafından kontrol edilen List<int> ve List<Order> elde edersiniz. Açılı parantezlerdeki yer tutucu, geleneksel olarak T, bir tip parametresidir; bir çağıranın sağladığı tip ise tip argümanıdır.

Neden generics: çözdükleri sorun

Generics'ten önce (C# 1) koleksiyonlar object saklardı. Bu derlenirdi, ama hatalar yalnızca çalışma zamanında ortaya çıkardı ve her int içeri girerken box'lanırdı:

var prices = new System.Collections.ArrayList();
prices.Add(9.99m);
prices.Add("12.50");                  // compiles: anything is an object
decimal total = 0;
foreach (object p in prices)
    total += (decimal)p;              // InvalidCastException on the string

List<decimal> ile ikinci Add bir derleme hatasıdır, döngü cast gerektirmez ve decimal'ler decimal olarak saklanır.

Generic sınıflar

Tip parametrelerini sınıf adından sonra bildirin, sonra onları sınıfın içinde diğer tipler gibi kullanın:

Çıktı:

Page 2/3: Chen, Dara, more: True
Page 1/1: 90, 72, 85, more: False

Page<string> ve Page<int> tek bir tanımdan üretilmiş iki farklı tiptir. Bir sınıf birkaç tip parametresi alabilir (Dictionary<TKey, TValue>); tek bir tane varsa T, birkaç tane varsa TSomething olarak adlandırın.

Generic metotlar ve tip çıkarımı

Bir metodun, sınıfı generic olsun ya da olmasın, kendi tip parametreleri olabilir. Çağıranlar genellikle tip argümanını yazmaz, çünkü derleyici onu argümanlardan çıkarır:

Çıktı:

2 1
back front
ho ho ho
4.50 EUR, 12.00 EUR, 0.99 EUR

Çıkarım yalnızca argümanlardan çalışır, asla dönüş tipinden çalışmaz. Repeat(0, 3) işaret gerektirmez, ama T Create<T>() gibi bir metodun çıkaracağı bir şey yoktur, bu yüzden çağıranlar Create<Customer>() yazmalıdır. LINQ'in çoğu bu çıkarıma dayanan generic metotlardır: prices.Where(p => p > 1) siz yazmadan Where<decimal>'dır.

Kısıtlar: T'nin ne olmasına izin verildiği

Generic bir metodun içinde derleyici yalnızca her olası T için çalışanı yapmanıza izin verir. Kısıt olmadan bu, object'in desteklediğinden biraz fazlasıdır. CompareTo çağırmak, bir örnek oluşturmak ya da bir property okumak için derleyiciye T'nin ne olması gerektiğini where ile söyleyin:

Çıktı:

17
plum
19.99
42 Zoe

Kullanacağınız kısıtlar:

KısıtAnlamıİzin verdiği
where T : classreferans tipinull atamak, as kullanmak
where T : structnullable olmayan değer tipiT?'yi Nullable<T> olarak kullanmak
where T : new()public parametresiz constructornew T() çağırmak (en son yazılmalı)
where T : EntityEntity'den türerEntity'nin üyelerini kullanmak
where T : IComparable<T>interface'i uygularCompareTo çağırmak
where T : Ubaşka bir tip parametresinden türeriki parametreyi ilişkilendirmek
where T : unmanaged (C# 7.3)içinde referans olmayan structpointer'lar, stackalloc
where T : Enum (C# 7.3)bir enum tipiEnum metotlarına geçirmek
where T : notnull (C# 8)nullable olmayan tipnullable referans işaretlemeleri

Tek bir parametredeki birkaç kısıt virgülle ayrılır; birkaç parametrenin her biri kendi where cümlesini alır: class Cache<TKey, TValue> where TKey : notnull where TValue : class.

Eksik bir kısıt, üyeyi kullandığınız noktada bir derleme hatası olarak ortaya çıkar; örneğin CS1061 ('T' does not contain a definition for 'CompareTo') ya da new() olmadan new T() çağırırken CS0304.

default(T)

Generic kod bazen bilmediği bir tipin "boş değerine" ihtiyaç duyar. default(T) sayılar için 0, bool için false, referans tipleri ve nullable tipler için null, diğer durumlarda sıfırlanmış bir struct verir:

Çıktı:

31
0
Porto
True
0001-01-01

İkinci satır bu kalıbın zayıflığını gösterir: değer tipleri için "bulunamadı" ile "0 bulundu" aynı görünür. Framework metotlarının bunun yerine TryGet şeklini (bool TryGetValue(TKey key, out TValue value)) kullanmasının nedeni budur. C# 7.1'den beri, tip bağlamdan belli olduğunda daha kısa return default; yazabilirsiniz.

Statik üyeler oluşturulan tip başınadır

Oluşturulan her tip (Counter<int>, Counter<string>) sınıfın statik alanlarının kendi kopyasını alır:

Çıktı:

2
1
False

Bu bazen işe yarar (her T için bir kez hesaplanan TypeInfo<T>.Name gibi tip başına bir önbellek), bazen de paylaşılan tek bir sayaç beklediğinizde bir sürprizdir. Paylaşılan durumu generic olmayan bir sınıfa koyun.

Generic koleksiyonlar

Generic koleksiyonlar çoğu ihtiyacı karşılar: List<T>, Dictionary<TKey, TValue>, HashSet<T>, Queue<T>, Stack<T> ve paylaştıkları interface'ler IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyList<T>. Bir metodun ihtiyaç duyduğu en dar interface'i kabul edin (yalnızca döngü kuruyorsa IEnumerable<T>) ve somut ya da salt okunur bir tip döndürün.

Kovaryans ve kontravaryans

Bir Dog bir Animal'dır, ama bir List<Dog> bir List<Animal> değildir:

List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs;
// error CS0029: Cannot implicitly convert type 'List<Dog>' to 'List<Animal>'

Buna izin verilseydi animals.Add(new Cat()) köpek listesine bir kedi koyardı. Yalnızca değer üreten interface'ler güvenlidir ve out ile kovaryant bildirilir:

IEnumerable<Animal> readOnly = dogs;          // fine: IEnumerable<out T>
IReadOnlyList<Animal> alsoFine = dogs;        // fine: IReadOnlyList<out T>

Ayna görüntüsü olan ve in ile işaretlenen kontravaryans, yalnızca değer tüketen interface'lere ve delegelere uygulanır: bir Action<Animal>, bir Action<Dog>'un beklendiği yerde kullanılabilir, çünkü her hayvanı ele alabilen her şey bir köpeği de ele alabilir. IComparer<in T> kontravaryanttır ve Func<in T, out TResult> iki işareti de kullanır. Varyans yalnızca interface'lere ve delegelere ve yalnızca referans tipi argümanlarına uygulanır: IEnumerable<int>, IEnumerable<object>'e dönüşmez.

Yaygın hatalar

  • Bir tip parametresinin olması gereken yerde object kullanmak. Derleme zamanı kontrollerinden vazgeçer ve değer tiplerini box'lar.
  • İki T değerini == ile karşılaştırmak. Bir class kısıtı olmadan derlenmez (CS0019), bir tane varsa referansları karşılaştırır. Her T için çalışan ve tipin kendi Equals'ına uyan EqualityComparer<T>.Default.Equals(a, b)'yi kullanın.
  • "Bulunamadı" olarak default(T) döndürmek. Değer tipleri için belirsizdir. bool TryX(..., out T value) kullanın.
  • Çok fazla kısıt. Her biri metodu kimin kullanabileceğini daraltır. Yalnızca gövdenin gerçekten ihtiyaç duyduğunu kısıtlayın.
  • List<Derived>'ın List<Base>'e dönüşmesini beklemek. Bunun yerine IEnumerable<Base> ya da IReadOnlyList<Base> kabul edin.

Sıkça Sorulan Sorular

C#'ta generics nedir?

Generics, bir class'ı, interface'i ya da metodu, çağıranların doldurduğu T gibi bir yer tutucu tiple bir kez yazmanızı sağlar: List<int>, List<Customer>. Derleyici her kullanımı gerçek tipe karşı kontrol eder, böylece cast olmadan tip güvenliği elde edersiniz ve değer tipleri boxing olmadan saklanır. System.Collections.Generic içindeki koleksiyonlar en tanıdık örnektir.

C#'ta generic bir metot nasıl yazılır?

Tip parametrelerini metot adından sonra yazın: static T Largest<T>(List<T> items) where T : IComparable<T> { ... }. Çağıranlar genellikle tip argümanını yazmaz, çünkü derleyici onu argümanlardan çıkarır: prices bir List<decimal> ise Largest(prices) çalışır. Çıkarılacak bir şey olmadığında onu açıkça yazın: Largest<decimal>(...).

C#'ta where T : class ne anlama gelir?

Bir kısıttır: T bir referans tipi olmalıdır, bu yüzden generic kod bir T'ye null atayabilir ve as T kullanabilir. Diğer kısıtlar where T : struct (nullable olmayan bir değer tipi), where T : new() (public parametresiz bir constructor'ı vardır, böylece kod new T() çağırabilir), where T : SomeBaseClass ve kodun T üzerinde o interface'in üyelerini çağırmasını sağlayan where T : ISomeInterface'tir.

C#'ta default(T) nedir?

T hangi tip olursa olsun onun varsayılan değeridir: sayılar için 0, bool için false, referans tipleri ve nullable tipler için null, diğer değer tipleri için sıfırlanmış bir struct. Generic kod, tipi bilmeden "değer yok"a ihtiyaç duyduğunda onu kullanır; örneğin başarısız bir aramanın dönüş değeri olarak. C# 7.1 daha kısa default literal'ini ekledi.

C#'ta neden bir List<Dog>'u bir List<Animal>'a atayamıyorum?

Çünkü bir List<Animal> her hayvanı kabul eder: atamaya izin verilseydi kod aslında bir List<Dog> olan şeye bir Cat ekleyebilirdi. Derleyici bunu reddeder (CS0029). Salt okunur interface'ler kovaryanttır, bu yüzden IEnumerable<Animal> animals = dogs; ve IReadOnlyList<Animal> animals = dogs; ikisi de derlenir, çünkü onların üzerinden hiçbir şey eklenemez.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA