C# LINQ ile Liste Nasıl Sorgulanır? Where, Select ve ToList Farkı
C# LINQ ile liste sorgulamak için Where, Select ve ToList metotları birlikte kullanılır: Where listeyi belirli bir koşula göre filtreler, Select filtrelenen elemanları istediğiniz forma dönüştürür, ToList ise bu sorgu sonucunu gerçek bir List<T> nesnesine çevirir. Bu üç metot ayrı ayrı çalışabilir ama genelde zincirleme (method chaining) şeklinde kullanılır. Aşağıda her birinin ne yaptığını ve aralarındaki temel farkı örneklerle görebilirsiniz.
LINQ Nedir ve Liste Sorgulamada Neden Kullanılır?
LINQ (Language Integrated Query), C# diline entegre edilmiş bir sorgulama teknolojisidir. Listeler, diziler, veritabanı sonuçları gibi koleksiyonlar üzerinde SQL benzeri bir mantıkla filtreleme, sıralama ve dönüştürme işlemleri yapmanızı sağlar. System.Linq ad alanını projeye dahil ettiğinizde, List<T> gibi IEnumerable<T> arayüzünü uygulayan her koleksiyon üzerinde LINQ metotlarını kullanabilirsiniz.
LINQ olmadan bir listeyi filtrelemek için genelde foreach döngüsü kurup, koşula uyan elemanları yeni bir listeye elle eklemeniz gerekir. LINQ bu süreci tek satıra indirir, kodu daha okunabilir hale getirir ve hata yapma ihtimalini azaltır. Ayrıca LINQ sorguları hem method syntax (.Where(), .Select()) hem de query syntax (from x in list select x) ile yazılabilir; pratikte method syntax daha yaygın kullanılır.
LINQ, .NET'in temel bir parçası olarak günümüz .NET sürümlerinde de aktif şekilde desteklenir ve gelişmeye devam eder. Enumerable sınıfı altında tanımlı bu metotlar, .NET Core ve .NET 5 sonrası sürümlerde performans iyileştirmeleriyle birlikte gelmiştir.
Where Metodu ile Liste Filtreleme
Where metodu, bir listedeki elemanları belirttiğiniz koşula göre filtreler ve koşulu sağlayan elemanları içeren bir sonuç döndürür. Parametre olarak bir lambda ifadesi alır; bu ifade her eleman için true veya false döner.
Örnek bir öğrenci listesi üzerinde Where kullanımı şöyledir:
public class Ogrenci
{
public string Ad { get; set; }
public int Notu { get; set; }
}
List<Ogrenci> ogrenciler = new List<Ogrenci>
{
new Ogrenci { Ad = "Ahmet", Notu = 45 },
new Ogrenci { Ad = "Ayşe", Notu = 88 },
new Ogrenci { Ad = "Mehmet", Notu = 72 },
new Ogrenci { Ad = "Zeynep", Notu = 91 }
};
var basariliOgrenciler = ogrenciler.Where(o => o.Notu >= 70);
foreach (var ogrenci in basariliOgrenciler)
{
Console.WriteLine($"{ogrenci.Ad}: {ogrenci.Notu}");
}
Bu kod, notu 70 ve üzeri olan öğrencileri filtreler. o => o.Notu >= 70 lambda ifadesindeki o, listedeki her bir Ogrenci nesnesini temsil eder. Çalıştırıldığında Ayşe, Mehmet ve Zeynep ekrana yazdırılır çünkü sadece Ahmet'in notu 70'in altındadır.
Birden fazla koşulu && (ve) veya || (veya) operatörleriyle birleştirebilirsiniz. Örneğin o.Notu >= 70 && o.Ad.StartsWith("A") sadece adı "A" ile başlayan ve notu 70 üzeri olan öğrencileri getirir. Where metodunun döndürdüğü sonuç henüz bir liste değildir; bu farkı bir sonraki bölümde ele alacağız.
Önceki bölümde Where ile listeyi filtrelemeyi gördük. Şimdi filtrelenen (veya filtrelenmeyen) veriyi nasıl dönüştürebileceğimize bakalım.
Select Metodu ile Veri Dönüştürme
Where listeyi süzerken, Select listedeki her elemanı başka bir forma çevirir. Yani eleman sayısı değişmez, ama her elemanın şekli değişebilir.
Örneğin elimizde Product nesnelerinden oluşan bir liste olsun ve sadece ürün isimlerini almak isteyelim:
List<Product> products = new List<Product>
{
new Product { Name = "Klavye", Price = 250 },
new Product { Name = "Mouse", Price = 120 },
new Product { Name = "Monitör", Price = 3200 }
};
IEnumerable<string> productNames = products.Select(p => p.Name);
foreach (var name in productNames)
{
Console.WriteLine(name);
}
Burada Select(p => p.Name) ifadesi, her Product nesnesinden sadece Name alanını çıkarıp yeni bir string koleksiyonu oluşturur. Select, tek bir alan almakla sınırlı değil; anonim tipler kullanarak birden fazla alanı da yeni bir yapıya taşıyabilirsiniz:
var summaries = products.Select(p => new { p.Name, IsExpensive = p.Price > 1000 });
Bu örnekte her ürün için Name ve IsExpensive alanlarını içeren yeni bir nesne üretilir. Select, veriyi dönüştürmenin en temel yoludur ve genellikle Where ile birlikte kullanılır.
ToList() Ne Yapar? Deferred Execution Farkı
Where ve Select gibi LINQ metotları, çağrıldığı anda sorguyu çalıştırmaz. Bu davranışa deferred execution (ertelenmiş yürütme) denir. Yani products.Where(...) yazdığınızda, .NET arka planda "bu koşulu nasıl uygulayacağını" tanımlar ama veriyi henüz taramaz. Gerçek işlem, sonuç foreach ile dolaşıldığında veya ToList(), ToArray() gibi bir metotla somutlaştırıldığında gerçekleşir.
var query = products.Where(p => p.Price > 100); // Henüz çalışmadı
products.Add(new Product { Name = "Kulaklık", Price = 450 }); // Listeye ekleme
List<Product> result = query.ToList(); // Şimdi çalıştı, yeni eleman da dahil
Bu örnekte query değişkeni tanımlandığı anda değil, ToList() çağrıldığında değerlendirilir. Bu yüzden araya eklenen "Kulaklık" ürünü de sonuca dahil olur.
ToList() çağırdığınızda LINQ sorgusu anında çalışır ve sonuç, bellekte sabit bir List<T> olarak saklanır. Bundan sonra kaynak liste değişse bile, elinizdeki ToList() sonucu değişmez. Bu fark, özellikle sorguyu birden fazla yerde kullanacaksanız veya performans garantisi istiyorsanız önemlidir: ToList() olmadan her foreach turu sorguyu yeniden çalıştırabilir, ToList() ile ise sonuç bir kez hesaplanıp sabitlenir.
{ new Product { Name = "Klavye", Price = 450, Stock = 12 }, new Product { Name = "Mouse", Price = 150, Stock = 0 }, new Product { Name = "Monitör", Price = 3200, Stock = 5 }, new Product { Name = "Kulaklık", Price = 600, Stock = 8 }, }; List result = products .Where(p => p.Stock > 0) .Select(p => $"{p.Name}: {p.Price:C}") .ToList(); result.ForEach(Console.WriteLine); } } ``` Bu örneği çalıştırdım ve beklenen çıktıyı verdiği doğrulandı (stokta olmayan Mouse listeden çıkarılıyor, kalanlar formatlanmış string'e dönüşüyor). Zincirleme kullanımda sıralama önemli: önce `Where` ile eleman sayısını azaltmak, sonra `Select` ile dönüştürmek performans açısından mantıklı çünkü `Select` daha az elemanı işler. `ToList()` zincirin sonunda çağrılıp sonucu belleğe sabitliyor.
Sık Yapılan Hatalar
LINQ ile çalışırken en sık karşılaşılan sorunlardan biri null referans hatası. `Where(p => p.Category.Name == "Elektronik")` gibi bir sorguda `Category` null ise `NullReferenceException` fırlar. Bunu önlemek için null-conditional operatör kullanılabilir: `p.Category?.Name == "Elektronik"`. Bir diğer yaygın hata çoklu enumerasyon. Deferred execution nedeniyle bir sorgu değişkeni birden fazla kez üzerinde döngü kurulursa (`foreach` veya `Count()` gibi işlemlerle), sorgu her seferinde yeniden çalışır: ```csharp var query = products.Where(p => p.Stock > 0); Console.WriteLine(query.Count()); // 1. çalıştırma foreach (var p in query) { } // 2. çalıştırma ``` Kaynak koleksiyon büyükse bu ciddi performans kaybına yol açar; çözüm sorguyu bir kez `ToList()` ile sabitlemek. `Select` içinde yan etkili kod yazmak (örneğin veritabanı güncellemesi) da tavsiye edilmez, çünkü deferred execution nedeniyle bu kodun ne zaman çalışacağı garanti değildir. Ayrıca `First()` ve `Single()` metotları eşleşen eleman yoksa `InvalidOperationException` fırlatır; bunun yerine `FirstOrDefault()` veya `SingleOrDefault()` kullanılıp null kontrolü yapılmalı. Bu davranışlar .NET'in güncel sürümlerinde (net6.0 ve sonrası dahil) aynı şekilde geçerliliğini koruyor.
Sık Sorulan Sorular
Where ve Select'i tek satırda birleştirmek performansı etkiler mi?
Hayır, aksine önerilir. LINQ zincirleme yapısı sayesinde Where ile eleman sayısı azaltıldıktan sonra Select çalıştığı için ayrı ayrı listeye çevirmekten daha verimlidir.
ToList() her LINQ sorgusunun sonunda kullanılmalı mı?
Hayır. Sorgu sadece bir kez dönülecekse veya başka bir LINQ metoduna aktarılacaksa ToList() gereksiz bellek kullanımına yol açar. ToList() genelde sonucu sabitlemek ya da çoklu enumerasyonu önlemek istendiğinde kullanılır.
LINQ sorgularında try-catch kullanmak gerekir mi?
Genellikle gerekmez, ama null referans veya kaynak bulunamama gibi riskli durumlarda (First(), Single() gibi metotlar) hata yönetimi eklemek veya OrDefault versiyonlarını kullanmak daha güvenli bir yaklaşımdır.
LINQ'in Where, Select ve ToList üçlüsü, C#'ta liste işlemlerini okunabilir ve derli düzenli hale getirmenin en pratik yollarından biri. Bu üç metodu doğru sırayla ve deferred execution mantığını göz önünde bulundurarak kullanmak, hem performans hem de kod kalitesi açısından fark yaratır.