C#'ta List, Dictionary ve HashSet arasındaki temel fark, verinin nasıl tutulduğu ve nasıl erişildiği ile ilgilidir: List elemanları eklenme sırasına göre saklayan ve indeksle erişilen bir koleksiyondur, Dictionary her değeri benzersiz bir anahtarla eşleştirerek hızlı arama sağlar, HashSet ise yalnızca benzersiz elemanların bulunmasını garanti eden bir küme yapısıdır. Doğru koleksiyonu seçmek, kodun okunabilirliğini değil doğrudan performansını ve doğruluğunu etkiler. Bu yazıda her üç yapının iç işleyişini, tipik kullanım senaryolarını ve aralarında seçim yaparken izlenecek pratik adımları ele alacağız.
C#'ta List, Dictionary ve HashSet Arasındaki Temel Fark Nedir?
Bu üç koleksiyon türü, System.Collections.Generic ad alanında yer almasına rağmen çözdükleri problem tamamen farklıdır. List "bu elemanları hangi sırada tutmalıyım ve sırayla nasıl işlemeliyim" sorusuna cevap verirken, Dictionary "bu değere hangi anahtarla ulaşacağım" sorusunu çözer. HashSet ise "bu listede zaten var mı, tekrar eklenmesin" ihtiyacına odaklanır. Üçü de generic koleksiyonlar olduğu için tip güvenliği sağlar, ancak iç veri yapıları ve dolayısıyla performans karakteristikleri birbirinden ayrışır.
Kısaca özetlemek gerekirse: sıra önemliyse ve elemanlara indeksle erişilecekse List tercih edilir; anahtar üzerinden hızlı arama, ekleme veya güncelleme gerekiyorsa Dictionary öne çıkar; sadece benzersizlik garantisi ve küme işlemleri (birleşim, kesişim, fark) gerekiyorsa HashSet en uygun seçimdir. İlerleyen bölümlerde her yapının iç mantığını, C#'a özgü davranışlarını ve gerçek kod örnekleriyle bu farkların pratikte ne anlama geldiğini detaylandıracağız.
List<T>: Sıralı ve İndeks Tabanlı Koleksiyon Yapısı
List, elemanları eklenme sırasına göre saklayan ve her elemana sıfır tabanlı bir indeksle doğrudan erişim sağlayan bir koleksiyondur. Bir listeye Add ile eklenen elemanlar, siz araya müdahale etmediğiniz sürece eklenme sırasını korur; bu da onu sıraya duyarlı verilerde (log kayıtları, işlem geçmişi, kuyruk benzeri iş akışları) doğal bir seçim yapar. List ayrıca aynı değerin birden fazla kez eklenmesine izin verir, çünkü benzersizlik garantisi vermez; bu, örneğin bir siparişteki ürün satırlarını veya bir sınavdaki tekrar eden puanları tutarken avantajdır.
C#'a özgü ve genellikle göz ardı edilen bir nüans, List'in iç yapısında aslında bir dizi (array) kullanmasıdır. Liste doldukça .NET, iç diziyi otomatik olarak daha büyük bir diziye kopyalayarak büyütür; bu işlem her tetiklendiğinde ekstra bellek ayırma ve kopyalama maliyeti doğurur. Eğer listeye kaç eleman ekleneceği önceden yaklaşık olarak biliniyorsa, listeyi oluştururken Capacity değerini vermek bu gereksiz yeniden boyutlandırmaları önler:
var siparisSatirlari = new List<string>(capacity: 200);
for (int i = 0; i < 150; i++)
{
siparisSatirlari.Add($"Ürün-{i}");
}
Console.WriteLine(siparisSatirlari.Count); // 150
Console.WriteLine(siparisSatirlari.Capacity); // en az 200
Bu davranış, List'i yalnızca "sıralı dizi" olarak değil, büyüme mantığı olan dinamik bir yapı olarak görmeyi gerektirir. Eleman sayısı sabit veya öngörülebilirse Capacity vermek, özellikle döngü içinde çok sayıda ekleme yapılan senaryolarda gözle görülür bir fark yaratır. Sıralamanın korunması gereken, indeksle erişimin sık kullanılacağı ve tekrar eden değerlerin sorun olmadığı her senaryoda List ilk tercih olmalıdır.
Dictionary<TKey,TValue>: Anahtar-Değer Eşleme ile Hızlı Arama

Dictionary, her değeri benzersiz bir anahtarla eşleştirerek saklayan ve bu anahtar üzerinden değere doğrudan ulaşmayı sağlayan bir koleksiyondur. İç yapısında hash tabanlı bir mekanizma kullanır: anahtarın hash kodu hesaplanır, bu kod bir kovaya (bucket) yönlendirilir ve arama, listedeki gibi baştan sona tarama yapmak yerine doğrudan ilgili kovaya gidilerek gerçekleştirilir. Bu, kavramsal olarak List'teki sırayla tarama mantığından temelden farklıdır ve anahtar bilindiğinde aramayı sabit zamana yakın hale getirir.
Bu özellik, anahtarla hızlı erişim gereken her senaryoda Dictionary'yi doğal seçim yapar: öğrenci numarasıyla öğrenci kaydı bulma, ürün koduyla stok sorgulama veya kullanıcı adıyla oturum bilgisi çekme gibi durumlarda değerler bir listede aranıp taranmaz, doğrudan anahtar üzerinden çekilir. Aynı kavram hemen her modern dilde bir karşılığa sahiptir; Java'da HashMap, Python'da sözlük (dict) benzer bir anahtar-değer mantığı sunar, ancak her dilin kendi koleksiyon sınıfının davranışını ve nüanslarını ayrı ayrı öğrenmek gerekir. Farklı dillerde bu yapıların nasıl işlediğini karşılaştırmalı görmek isteyenler için Java'da koleksiyon yapıları eğitimi C#'taki Dictionary mantığını pekiştirmek açısından da faydalı bir bakış açısı sunar.
Dictionary kullanırken dikkat edilmesi gereken bir nokta, anahtarların benzersiz olması gerektiğidir; aynı anahtarla ikinci bir ekleme yapılmaya çalışıldığında çalışma zamanında hata alınır. Bu yüzden Dictionary, "her kayda benzersiz bir kimlikle ulaşacağım" ihtiyacı olduğunda; sıralamanın değil, hızlı erişimin öncelikli olduğu durumlarda tercih edilmelidir.
HashSet<T>: Benzersizlik Garantisi ve Küme İşlemleri
HashSet<T>, bir koleksiyonda her elemanın yalnızca bir kez bulunmasını garanti eden yapıdır. Aynı değeri tekrar eklemeye çalıştığınızda çalışma zamanı hata vermez; Add metodu geriye false döndürür ve eleman koleksiyona ikinci kez girmez. Bu davranış, List<T>'nin aksine, veriyi topluca tutmak değil tekilleştirmek gerektiğinde HashSet'i doğal seçim hâline getirir. Örneğin bir sistemde ziyaret edilen sayfa ID'lerini veya kayıtlı e-posta adreslerini yinelemeden tutmak istediğinizde HashSet, ek bir kontrol kodu yazmanıza gerek bırakmadan bu işi kendisi üstlenir.
HashSet<T>'yi asıl güçlü kılan diğer özellik ise matematiksel küme mantığını doğrudan metot olarak sunmasıdır. UnionWith iki kümeyi birleştirir, IntersectWith yalnızca iki kümede de ortak bulunan elemanları bırakır, ExceptWith ise birinci kümeden ikinci kümede geçen elemanları çıkarır. Bu üç metot, örneğin iki farklı listedeki ortak kullanıcıları bulmak ya da bir bütünden hariç tutulacak kayıtları ayıklamak gibi senaryolarda döngü yazmadan, okunabilir ve az hatalı kod üretmenizi sağlar.
C#'a özgü ve sık gözden kaçan bir detay, HashSet<T>'nin eşitlik kontrolünü nasıl yaptığıdır. Varsayılan olarak elemanların Equals ve GetHashCode metotları kullanılır; ancak kendi sınıflarınızda bu metotları override etmeden HashSet<MySınıf> kullanırsanız, referans eşitliği devreye girer ve içerik olarak aynı görünen iki nesne farklı kabul edilebilir. Bu sorunu çözmek için IEqualityComparer<T> arayüzü uygulanır; Equals ve GetHashCode metotlarını kendi mantığınıza göre tanımlayıp bu comparer'ı HashSet'in constructor'ına verdiğinizde, örneğin bir öğrenci nesnesini yalnızca öğrenci numarasına göre benzersiz saymak gibi özel kurallar tanımlayabilirsiniz. Bu, HashSet'i sadece basit tiplerle değil, karmaşık nesnelerle de güvenle kullanmanın anahtarıdır.
Performans, Bellek ve Sıralama Açısından Karşılaştırma

Üç yapı arasındaki en somut fark, bir elemanı ararken yapılan işin niteliğidir. List<T>'de Contains veya IndexOf çağrıldığında koleksiyon baştan sona doğrusal olarak taranır; bu nedenle arama maliyeti eleman sayısıyla birlikte artar ve kavramsal olarak O(n) karmaşıklığa karşılık gelir. Dictionary<TKey,TValue> ve HashSet<T> ise anahtarı veya elemanı doğrudan bir hash tablosu üzerinde konumlandırdığı için arama, ekleme ve silme işlemleri ortalama durumda sabit süreli kabul edilir; bu da O(1) olarak ifade edilir. Burada dikkat edilmesi gereken nokta, bunun bir garanti değil ortalama davranış olduğudur; hash çakışmalarının yoğunlaştığı kötü senaryolarda performans düşebilir, ancak günlük kullanımda bu iki yapı arama açısından List'ten belirgin şekilde önde durur.
Bellek tarafında List<T> genellikle daha az yer kaplar çünkü sadece elemanları art arda tutar; Dictionary ve HashSet ise hash tablosu için ek bir iç yapı (bucket dizisi ve hash kodları) barındırdığından aynı sayıda eleman için daha fazla bellek kullanır. Sıralama konusunda da net bir ayrım vardır: List<T> elemanları ekleme sırasını korur ve indeksle erişime izin verir; Dictionary ve HashSet ise herhangi bir sıralama garantisi vermez, foreach ile gezindiğinizde eleman sırası iç hash yapısına bağlı olarak değişebilir. Sıralı çıktı gerekiyorsa bu iki yapıdan biri seçilmişse bile ayrıca OrderBy gibi bir sıralama adımı eklenmelidir.
| Yapı | Kullanım amacı | Arama hızı | Sıralama | Benzersizlik | Tipik senaryo |
|---|---|---|---|---|---|
| List<T> | Sıralı, indeksli veri tutma | Doğrusal (O(n)) | Ekleme sırasını korur | Garanti yok | Kayıt listesi, sıralı işlem kuyruğu |
| Dictionary<TKey,TValue> | Anahtara göre hızlı erişim | Ortalama sabit (O(1)) | Garanti vermez | Anahtarlar benzersiz | ID'ye göre kayıt sorgulama |
| HashSet<T> | Tekilleştirme ve küme işlemleri | Ortalama sabit (O(1)) | Garanti vermez | Elemanlar benzersiz | Yinelenen kayıtları ayıklama, iki liste karşılaştırma |
Kod Örneğiyle Karşılaştırma: Aynı Veriyi Üç Farklı Yapıyla Modellemek
Aynı öğrenci verisini üç farklı koleksiyonla modellemek, her yapının hangi erişim deseninde avantajlı olduğunu somutlaştırır. Aşağıdaki örnekte bir Student sınıfı; sırayla tutulması gereken kayıtlar için List<Student>, numaraya göre hızlı sorgulama için Dictionary<int, Student> ve yalnızca benzersiz e-posta adreslerini takip etmek için HashSet<string> ile kullanılıyor.
public class Student
{
public int Number { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
var studentList = new List<Student>
{
new Student { Number = 101, Name = "Ali", Email = "[email protected]" },
new Student { Number = 102, Name = "Zeynep", Email = "[email protected]" }
};
var studentByNumber = new Dictionary<int, Student>();
foreach (var s in studentList)
studentByNumber[s.Number] = s;
var uniqueEmails = new HashSet<string>();
foreach (var s in studentList)
uniqueEmails.Add(s.Email);
// Sıradaki öğrenciyi göster (List)
Console.WriteLine(studentList[0].Name);
// Numaraya göre anında eriş (Dictionary)
Console.WriteLine(studentByNumber[102].Name);
// E-posta zaten kayıtlı mı? (HashSet)
Console.WriteLine(uniqueEmails.Contains("[email protected]"));
Kodda görüldüğü gibi studentList[0] ile indeks üzerinden erişim yalnızca List<T>'de anlamlıdır; Dictionary ve HashSet indeks kavramını desteklemez. studentByNumber[102] satırı ise numarayı bilerek öğrenciye tek adımda ulaşmayı gösterir ve bu, öğrenci sayısı arttıkça List içinde döngüyle arama yapmaktan belirgin şekilde daha pratik bir yaklaşımdır. uniqueEmails.Contains(...) ise bir e-postanın sistemde zaten var olup olmadığını, ayrı bir kontrol listesi tutmadan doğrudan sorar. Bu üç kullanım biçimini birlikte görmek, doğru koleksiyonu seçmenin soyut bir kural değil, erişim deseninin doğal bir sonucu olduğunu netleştirir. Bu tür yapı seçimlerini uygulamalı örneklerle pekiştirmek isteyenler için C# konularını içeren video eğitim içerikleri adım adım ilerleyen alıştırmalar sunar.
Hangi Koleksiyonu Seçmeliyim? Adım Adım Karar Süreci
Üç koleksiyon arasında seçim yaparken teorik tanımları ezbere bilmek yeterli değildir; pratikte hızlı ve tutarlı bir karar mekanizmasına ihtiyaç vardır. Aşağıdaki adımlar, bir veri modelleme sorusuyla karşılaştığınızda sırayla sorulması gereken pratik sorulardır. Sorulara verdiğiniz cevaplar sizi doğrudan doğru koleksiyona yönlendirir.
- Verinin eklenme sırası anlamlı mı? Kullanıcıya gösterilecek bir liste, işlem geçmişi ya da adım adım ilerleyen bir akış varsa sıralama korunmalıdır; bu durumda
List<T>doğru tercihtir. Sıra önemsizse diğer adımlara geçin. - Bir anahtar üzerinden hızlı erişim gerekiyor mu? Kullanıcı ID'si, ürün kodu veya isim gibi bir anahtarla değere ulaşmanız gerekiyorsa
Dictionary<TKey, TValue>kullanın. Anahtar-değer ilişkisi yoksa devam edin. - Aynı elemanın birden fazla kez bulunması bir hata mı sayılır? Tekrar eden kayıtların mantıksal olarak anlamsız veya hatalı olduğu durumlarda (etiket kümesi, ziyaret edilen düğümler, benzersiz e-posta listesi)
HashSet<T>tercih edilmelidir. - Veri üzerinde sık sık "içeriyor mu" veya "var mı" kontrolü yapılacak mı? Bu tür sorgular çok sayıda tekrarlanacaksa List yerine Dictionary veya HashSet düşünülmelidir; List'te bu kontrol eleman sayısıyla birlikte yavaşlar.
- Hem sıra hem hızlı erişim mi gerekiyor? Böyle bir senaryoda tek bir koleksiyon yerine iki yapıyı birlikte kullanmak (örneğin sıralı bir List ile birlikte arama için ayrı bir Dictionary tutmak) daha sağlıklı bir çözümdür.
Bu karar zincirini birkaç proje boyunca bilinçli şekilde uyguladığınızda, veri yapısı seçimi zamanla refleks haline gelir. Kendi seviyenizi ve bu konudaki hakimiyetinizi ölçmek isterseniz C# bilgi testi üzerinden kısa bir değerlendirme yapabilirsiniz; test, koleksiyon seçimi dahil temel C# kavramlarındaki eksiklerinizi görmenize yardımcı olur.
Pratikte Sık Yapılan Seçim Hataları ve Öneriler
Yeni yazılımcıların koleksiyon seçiminde düştüğü hatalar genellikle bilgi eksikliğinden değil, alışkanlıktan kaynaklanır. En sık görülen hata, her veri koleksiyonunu List ile çözmeye çalışmaktır. List tanıdık ve kullanımı basittir, bu yüzden anahtar bazlı arama veya benzersizlik gerektiren senaryolarda bile ilk akla gelen yapı olur. Sonuç, kod çalışsa da gereksiz döngülerle dolu, büyüdükçe yavaşlayan bir yapı olur.
İkinci yaygın hata, Dictionary'yi gereksiz yere karmaşıklaştırmaktır. Basitçe bir listeye ihtiyaç duyulan yerde anahtar-değer çifti üretmek, kodun okunabilirliğini bozar ve gereksiz bir soyutlama katmanı ekler. Anahtar üzerinden erişim ihtiyacı yoksa Dictionary kurmak, çözümden çok probleme dönüşür.
Üçüncü hata ise HashSet'i sıralama gerektiren yerlerde kullanmaktır. HashSet'in iç sıralaması eleman ekleme sırasıyla örtüşmez ve bu davranışa güvenilerek yazılan kod, farklı ortamlarda veya farklı .NET sürümlerinde beklenmedik sonuçlar üretebilir. Sıra önemliyse bu iş HashSet'in değil List'in ya da sıralanmış bir yapının görevidir.
Bu hataların ortak noktası, koleksiyonun iç davranışını değil yalnızca yüzeysel kullanımını bilmekten geçer. Doğru seçim alışkanlığı kazanıldığında iki somut fayda ortaya çıkar: kod daha az kaynak tüketir ve başka bir geliştirici tarafından okunduğunda niyet daha net anlaşılır. Bu tür alışkanlıkları tek başına kod okuyarak fark etmek zaman alabilir; bu noktada kendi kodunuz üzerinden anlık geri bildirim almak öğrenme sürecini kısaltır. 1-1 özel ders formatında çalışılan pratik oturumlar, tam olarak bu tür seçim hatalarının erken aşamada fark edilip düzeltilmesini sağlar.
Sık Sorulan Sorular
List, Dictionary ve HashSet arasında performans farkı en çok hangi işlemde ortaya çıkar?
Fark en belirgin şekilde "eleman arama" veya "içeriyor mu" kontrolünde ortaya çıkar. List'te bu işlem eleman sayısıyla orantılı olarak yavaşlarken, Dictionary ve HashSet'te anahtar veya değerin varlığı kavramsal olarak sabit sürede kontrol edilir.
Dictionary'de anahtar olarak hangi tür veriler kullanılabilir?
Anahtar, eşitlik karşılaştırması ve tutarlı bir hash kodu üretebilen herhangi bir tür olabilir; string, int gibi temel tipler doğrudan kullanılabilir, özel sınıflar ise Equals ve GetHashCode metotlarının doğru şekilde uygulanmasını gerektirir.
HashSet ile List arasında benzersizlik açısından temel fark nedir?
List aynı değeri sınırsız sayıda tekrar eklemenize izin verirken, HashSet bir değeri yalnızca bir kez barındırır; aynı değeri tekrar eklemeye çalıştığınızda işlem sessizce yok sayılır ve koleksiyon değişmez.
C#'ta List<T>'in Capacity özelliği neden önemlidir?
Capacity, List'in iç dizisinin gerçek boyutunu belirler ve eleman sayısı bu sınırı aştığında dizi daha büyük bir alana kopyalanır. Beklenen eleman sayısı önceden biliniyorsa Capacity'yi baştan ayarlamak, bu yeniden boyutlandırma maliyetini azaltır.
Sıralı veri gerektiren senaryolarda Dictionary veya HashSet kullanılabilir mi?
Kullanılabilir ancak önerilmez; her iki yapı da eleman sırasını korumayı garanti etmez. Sıralamanın kritik olduğu senaryolarda List ya da sıralamayı açıkça yöneten bir koleksiyon tercih edilmelidir.
IEqualityComparer, HashSet<T> içinde ne işe yarar?
IEqualityComparer, HashSet'in iki elemanı "aynı" sayıp saymayacağına nasıl karar vereceğini belirler. Özellikle string karşılaştırmalarında büyük/küçük harf duyarlılığı gibi özel eşitlik kurallarını devreye sokmak için kullanılır.
List, Dictionary ve HashSet arasındaki seçim, sonuçta veri hakkında sorduğunuz doğru sorulara dayanır: sıra, erişim biçimi ve benzersizlik ihtiyacı. Bu mantığı bir kez içselleştirdiğinizde, aynı karar süreci ileride öğreneceğiniz Java koleksiyonlarına geçişte de işinize yarayacaktır; bu geçişi sağlam bir zeminde yapmak isteyenler Java yazılım kursu içeriğine göz atabilir.