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

C# Koleksiyonları: List, Dictionary, HashSet ve Doğru Seçim

.NET koleksiyon türlerinin karşılaştırması; List, Array, Dictionary, HashSet, Queue, Stack, eşzamanlı koleksiyonlar, salt okunur ve Frozen koleksiyonlar ile performans açısından doğru seçim.

Çoğu C# geliştiricisi her şey için List<T> kullanır. Küçük veri setlerinde bu sorun yaratmaz; ama veri büyüdükçe yanlış koleksiyon seçimi, saniyelerin dakikalara dönüşmesine yol açabilir. Hangi koleksiyonun hangi işte iyi olduğunu bilmek, en ucuz performans iyileştirmelerinden biridir.

Hızlı karşılaştırma

KoleksiyonGüçlü olduğu işArama
T[] (dizi)Sabit boyutlu, yüksek performanslı erişimİndeksle çok hızlı
List<T>Sıralı, büyüyebilen listeİndeksle hızlı, değerle yavaş
Dictionary<TKey, TValue>Anahtarla aramaÇok hızlı
HashSet<T>Benzersiz öğeler, "var mı?" sorusuÇok hızlı
Queue<T>İlk giren ilk çıkar (iş kuyruğu)—
Stack<T>Son giren ilk çıkar (geri al işlemleri)—
SortedDictionaryAnahtara göre sıralı tutmaHızlı

Klasik hata: Listede arama

// 50.000 sipariş için müşteri arama: her seferinde tüm listeyi tarar
foreach (var s in siparisler)
{
    var musteri = musteriler.First(m => m.Id == s.MusteriId);
}

// Önce sözlüğe çevir: her arama neredeyse anında
var musteriSozlugu = musteriler.ToDictionary(m => m.Id);
foreach (var s in siparisler)
{
    var musteri = musteriSozlugu[s.MusteriId];
}

İlk sürümde işlem sayısı iki listenin boyutlarının çarpımı kadar büyür; ikincisinde ise yaklaşık toplamı kadar. Büyük veride fark dramatiktir. Aynı mantık veritabanı tarafında N+1 problemi olarak karşımıza çıkar.

HashSet ile "var mı?" kontrolü

var engelliEpostalar = new HashSet<string>(liste, StringComparer.OrdinalIgnoreCase);
if (engelliEpostalar.Contains(eposta)) { /* ... */ }

Karşılaştırıcı belirtmek, büyük-küçük harf ve Türkçe "İ" sorunlarını baştan önler.

Salt okunur ve Frozen koleksiyonlar

  • IReadOnlyList<T>, IReadOnlyDictionary: Metot dönüşlerinde, çağıranın koleksiyonu değiştirmemesi gerektiğini anlatır.
  • FrozenDictionary ve FrozenSet: Uygulama başlangıcında bir kez oluşturulan ve sonra sadece okunan veriler (ülke kodları, ayarlar, izin listeleri) için okuma hızında optimize edilmiştir.
  • Immutable koleksiyonlar: Her değişiklikte yeni bir kopya üretir; çok iş parçacıklı senaryolarda güvenlidir.

Eşzamanlı (concurrent) koleksiyonlar

Birden fazla iş parçacığı aynı koleksiyona yazıyorsa normal Dictionary güvenli değildir. ConcurrentDictionary, ConcurrentQueue veya kanal (Channel<T>) yapıları kullanılmalıdır. Arka plan işlerinde bu durum sık görülür. Bkz. BackgroundService ve async/await.

Başlangıç kapasitesi

Eklenecek öğe sayısı biliniyorsa kapasiteyi baştan vermek, tekrar tekrar büyüme maliyetini önler:

var liste = new List<SatirDto>(kayitSayisi);

Sık sorulan sorular

Dönüş tipi olarak List mi, IEnumerable mi kullanmalıyım?

Çağıran yalnızca dolaşacaksa IEnumerable<T> veya IReadOnlyList<T>; sayı ve indeks erişimi gerekiyorsa IReadOnlyList<T> iyi bir dengedir. Ertelenmiş yürütme davranışı için bkz. LINQ rehberi.

Küçük listelerde de dikkat etmeli miyim?

Birkaç düzine öğede fark hissedilmez. Okunabilirliği öne alın; büyüyebilecek verilerde ise doğru yapıyı baştan seçin.

Sonuç

Doğru koleksiyon, kodu değiştirmeden büyük performans kazancı sağlar. Anahtarla aramada Dictionary, benzersizlikte HashSet, sabit verilerde Frozen koleksiyonlar; çoğu senaryonun cevabıdır.