Bölüm 6 / 13
LINQ
Where, Select, OrderBy, GroupBy, Any/All, FirstOrDefault, ertelenmiş çalışma.
9 dakikalık okuma
Hat 02'nin 14. dersinde SQL'in matematiksel atası ilişkisel
cebiri görmüştün — σ satır seçer, π sütun seçer. LINQ, aynı cebiri C# koleksiyonlarına
getirir: Where bir σ'dır, Select bir π. Bu ders operatör
ailesini, elemanı seçme metotlarının ince farklarını (First mü
Single mı?) ve LINQ'nun en çok yanlış anlaşılan davranışını —
ertelenmiş çalışmayı — işliyor. Döngü yazmayı azaltan bu araç, yanlış kullanınca
Hat 02'nin 10. dersindeki gizli O(n²)'leri geri getirir; farkı bilen yazar.
01Ne öğreneceksin
- Sorgu zinciri modeli: Where/Select/OrderBy ve ilişkisel cebir akrabalığı
- Eleman seçiciler: First / FirstOrDefault / Single / SingleOrDefault karar tablosu
- Gruplama ve dönüştürme: GroupBy, ToDictionary, SelectMany
- Ertelenmiş çalışma: sorgu tarif midir sonuç mudur, çoklu numaralandırma tuzağı
- Performans refleksleri: Any vs Count, döngüde LINQ, IQueryable'a bakış
02Model: koleksiyonu sorgulamak, döngüyü tarif etmek
LINQ (Language Integrated Query), IEnumerable<T> üzerinde
çalışan bir sorgu metotları ailesidir. Fikri tek cümledir: "nasıl dolaşacağını"
değil "ne istediğini" yaz. Şu klasik döngüyü:
var sonuc = new List<string>();
foreach (var m in musteriler)
if (m.Sehir == "Ankara")
sonuc.Add(m.Ad);
sonuc.Sort();
LINQ üç halkalı bir zincire çevirir:
var sonuc = musteriler
.Where(m => m.Sehir == "Ankara") // σ — satır süz
.OrderBy(m => m.Ad) // sırala
.Select(m => m.Ad) // π — sütuna indirge
.ToList();
Parantez içindeki m => m.Sehir == "Ankara" bir lambda:
"m alır, şu ifadeyi döndürür" diyen isimsiz küçük fonksiyon. Mekanizmasını (delegate,
Func) 7. ders sökecek; bu derste iş görecek kadarı yeter: sol taraf parametre, ok,
sağ taraf dönen ifade. Zincirin her halkası yine bir IEnumerable
döndürür — Hat 02'nin 14. dersindeki "sonuç yine tablodur, o yüzden zincirlenir"
ilkesinin koleksiyon hâli. Çekirdek operatörler:
| Operatör | İşi | Cebir/SQL akrabası |
|---|---|---|
Where(kosul) | koşula uyan elemanları süzer | σ — WHERE |
Select(donusum) | her elemanı dönüştürür | π — SELECT listesi |
OrderBy / ThenBy / OrderByDescending | sıralar; ikincil anahtar ThenBy | ORDER BY |
Count / Sum / Min / Max / Average | toplar, tek değere indirger | toplama fonksiyonları |
Any(kosul) / All(kosul) | en az biri / hepsi sağlıyor mu (bool) | EXISTS |
Distinct / Union / Intersect / Except | küme işlemleri | ∪ ∩ − akrabaları |
Take(n) / Skip(n) | ilk n / n atla — sayfalama ikilisi | LIMIT/OFFSET |
Bir de sorgu sözdizimi vardır (from m in musteriler where ... select
...) — SQL'e benzeyen ikinci yazım. İkisi aynı koda derlenir; sahada metot
sözdizimi baskındır, bu hat da onu kullanır; sorgu sözdizimini okuyabilecek kadar
tanı.
03Eleman seçiciler: dört metot, dört farklı sözleşme
"Listeden bir eleman al" işleminin dört varyantı, 2. dersin ve 4. dersin sözleşme diliyle ayrışır — hangisini seçtiğin, "bulunamama"nın senin senaryonda olağan mı istisnai mi olduğunu bildirir:
| Metot | Sonuç yoksa | Birden çoksa | Ne zaman |
|---|---|---|---|
First | exception | ilkini verir | "kesin var" — yoksa bu bir hatadır |
FirstOrDefault | default (null/0) | ilkini verir | bulunamama olağan; dönüşte null kontrolü şart |
Single | exception | exception | "tam olarak bir tane olmalı" — teklik de sözleşme |
SingleOrDefault | default | exception | sıfır ya da bir; ikincisi veri hatası |
var m1 = musteriler.First(m => m.Id == id); // yoksa InvalidOperationException
var m2 = musteriler.FirstOrDefault(m => m.Id == id); // yoksa null → 2. dersin ?? / ?. dünyası
var m3 = musteriler.Single(m => m.TcNo == tc); // TC benzersiz OLMALI — iki tane
// çıkarsa sessizce ilkini almaz, PATLAR
Kıdım farkı Single'ın değerindedir: benzersizlik bir iş kuralıysa
First yazmak hatayı gizler, Single veri bozukluğunu ilk
gören olur. Kural: sözleşmeni en sıkı ifade eden metodu seç — "hangisi patlamaz"
değil, "hangisi doğru anda patlar".
04Gruplama ve düzleştirme: GroupBy, ToDictionary, SelectMany
// GroupBy: anahtara göre kümele — sonuç IGrouping listesi (Key + elemanlar)
var sehirGruplari = musteriler.GroupBy(m => m.Sehir);
foreach (var grup in sehirGruplari)
Console.WriteLine($"{grup.Key}: {grup.Count()} müşteri");
// gruptan özet tabloya: şehir → toplam ciro
Dictionary<string, decimal> ciroTablosu = siparisler
.GroupBy(s => s.Sehir)
.ToDictionary(g => g.Key, g => g.Sum(s => s.Tutar));
// SelectMany: iç listeleri DÜZLEŞTİR — "her müşterinin tüm siparişleri" tek akış olsun
var tumSatirlar = musteriler.SelectMany(m => m.Siparisler);
// Select olsaydı: IEnumerable<List<Siparis>> — liste listesi; SelectMany: IEnumerable<Siparis>
GroupBy, SQL'deki GROUP BY'ın (Hat 07'de db-groupby) bellek içi hâlidir; her grupKeytaşır ve kendisi numaralandırılabilir.ToDictionary / ToList / ToArray / ToHashSetailesi sorguyu maddeleştirir — bir sonraki bölümün anahtar kavramı.SelectileSelectManyayrımı mülakat sorusudur: eleman başına bir değer üretiyorsan Select, eleman başına bir koleksiyon üretip hepsini tek akışta istiyorsan SelectMany.
05Ertelenmiş çalışma: sorgu bir tariftir, yemek değil
LINQ'nun en kritik davranışı: Where/Select/OrderBy gibi operatörler
çağrıldıkları anda hiçbir eleman işlemez. Döndürdükleri
IEnumerable, bir tariftir; mutfak ancak biri sonucu
numaralandırınca çalışır — foreach, ToList,
Count(), First()... Buna ertelenmiş çalışma
(deferred execution) denir ve iki yüzü vardır:
var buyukler = sayilar.Where(x => x > 10); // şu an SIFIR iş yapıldı
sayilar.Add(99); // tarif hazırlanırken kaynak değişti
foreach (var x in buyukler) ... // ŞİMDİ çalıştı — 99 sonuçta VAR:
// tarif, çalıştığı andaki kaynağı görür
İyi yüzü verimliliktir: zincir, ara koleksiyonlar üretmeden eleman eleman akar;
First bulduğu anda durur (kısa devre). Tehlikeli yüzü çoklu
numaralandırmadır:
var pahali = siparisler.Where(s => YavasKontrol(s)); // tarif
if (pahali.Any()) // 1. çalıştırma
Console.WriteLine(pahali.Count()); // 2. çalıştırma — YavasKontrol HEPSİNE bir daha!
var liste = siparisler.Where(s => YavasKontrol(s)).ToList(); // BİR kez çalıştır, sonucu tut
if (liste.Count > 0) ... // artık bedava
Kural netleşsin: sonucu birden çok kez kullanacaksan ya da kaynağın
değişmemesini istiyorsan ToList() ile maddeleştir; tek geçişte
tüketeceksen tarif olarak bırak — erteleme senin lehine çalışır. Metot imzasında
IEnumerable<T> dönen her değerin "henüz çalışmamış olabilir"
taşıdığını bil; bu farkındalık, "neden iki kez sorgulandı?" ve "kaynağı değiştirince
sonuç değişti" sürprizlerinin tamamını keser.
06Performans refleksleri: LINQ zarafeti maliyeti gizler
Hat 02'nin 10. dersi "LINQ'da da geçerli olacak" diye söz vermişti — işte senetler:
Any()varkenCount() > 0yazma: Count tüm akışı sayar (O(n)), Any ilk elemanda durur (O(1)). Aynı aile:Firstkısa devre yapar,OrderBy(...).First()ise tüm listeyi sıralar — "en küçüğü bul" içinMindururken O(n log n) ödeme.- Döngü içinde LINQ, gizli iç döngüdür:
foreachgövdesindekilisteB.Contains(...)ya daWhere(...).First(), 10. dersin O(n²) tuzağının LINQ kılığıdır. Çare aynı: aramayı döngü dışında birHashSet'e ya daToDictionary/ToLookuptablosuna çevir — O(n²) → O(n). - Zincir sırası maliyettir: önce süz, sonra sırala/dönüştür —
Where(...).OrderBy(...),OrderBy(...).Where(...)'den ucuzdur; sıralayıcıya az eleman gitsin. - Ölçmeden suçlama, ölçmeden de güvenme: LINQ çoğu iş için yeterince hızlıdır; sıcak yolda (milyonluk veri, istek başına çağrı) Stopwatch'la döngüyle karşılaştır — bazen sade foreach kazanır, bu da meşru bir sonuçtur.
Bir kapı aralığı: bu derste gördüğün her şey LINQ to Objects — bellekteki
koleksiyonlar, IEnumerable. EF Core (Hat 07) aynı sözdizimini
IQueryable üzerinde kullanır: lambda'ların ifade ağacına çevrilip
SQL olarak veritabanında çalışır. Aynı satırın nerede koştuğu — bellekte mi
motorda mı — orada performansın ana sorusu olacak; şimdilik ayrımın var olduğunu
bil.
07Sahada LINQ: okunabilirlik sözleşmesi
LINQ'nun asıl kazancı satır sayısı değil, niyetin görünmesidir: üç halkalı
zincir, "Ankara'dakileri ada göre sırala, adları al" cümlesini kod incelemesinde
cümle gibi okutur. Bunu korumak için saha görgü kuralları: zinciri halka başına bir
satır kır; lambda üç-dört satırı geçiyorsa isimli metoda çıkar (zincir cümle
kalsın); beş-altı halkayı aşan sorguyu ara değişkenlerle isimlendirerek böl
("aktifMusteriler", "sehirOzeti") — hem okunur hem ayıklanabilir. Ve LINQ'yu
sorgulama için kullan: yan etkili işlem (loglama, kaydetme) zincire değil
foreach gövdesine yakışır — tarif ne zaman çalışacağı belirsiz bir
şeydir, yan etkiyi belirsizliğe bağlama.
08Mini alıştırma — Sipariş analizi + erteleme kanıtı
Yaklaşık 20 dakika · dotnet run · 15-20 satırlık örnek veri listesi kur
- Zincir turu:
Siparis(Id, Musteri, Sehir, Tutar, Tarih)listesi kur. Sırayla yaz: 500 TL üstü siparişlerin müşteri adları (tutara göre azalan), şehir başına sipariş sayısı ve toplam ciro (GroupBy+ToDictionary), en yüksek tutarlı üç sipariş (OrderByDescending+Take). - Seçici sözleşmesi: aynı Id ile
First,FirstOrDefault,Singleçağır; olmayan Id ve bilerek çiftlenmiş Id ile her birinin davranışını (değer mi, null mı, hangi exception) tablo hâlinde not et. - Erteleme kanıtı:
Wherelambda'sının içineConsole.Write(".")koy. Sorguyu kur (nokta yok — kanıt 1), listeye eleman ekle,foreach'le tüket (noktalar şimdi aktı ve yeni eleman sonuçta — kanıt 2), aynı sorguyu ikinci kez tüket (noktalar İKİNCİ kez — kanıt 3). SonraToList()ekleyip üç kanıtın da nasıl değiştiğini gör. - O(n²) avı: 100.000 elemanlı iki Id listesinin kesişimini önce
a.Where(x => b.Contains(x))ile, sonra b'yiToHashSet()yapıp ölç. Stopwatch farkını Hat 02'nin 10. dersindeki tabloyla ilişkilendir.
09Kendini sına
Where ve Select'in ilişkisel cebirdeki karşılıkları nedir? Zincirlenebilmelerini sağlayan ortak özellik hangisidir?
Where seçmedir (σ) — koşula uyan elemanları süzer, SQL'deki WHERE; Select izdüşümdür (π) — her elemanı istenen biçime indirger, SELECT listesi. Zincirlenebilmenin sırrı Hat 02'nin 14. dersindeki ilkeyle aynıdır: her operatörün sonucu yine IEnumerable'dır — "sonuç yine tablodur"un koleksiyon hâli; bu yüzden Where'in çıkışı OrderBy'ın girişi olabilir.
First, FirstOrDefault, Single, SingleOrDefault farklarını söyle; TC kimlik ile arama örneğinde hangisi doğrudur, neden?
First: yoksa exception, çoksa ilki. FirstOrDefault: yoksa default (referansta null), çoksa ilki. Single: yoksa da çoksa da exception — tam bir tane sözleşmesi. SingleOrDefault: yoksa default, çoksa exception. TC kimlik benzersiz bir iş kuralı olduğundan doğru seçim Single'dır: kayıt çiftlenmişse First sessizce ilkini alıp hatayı gizler, Single veri bozukluğunu ilk gören olur — metot seçimi, sözleşmenin koddaki ifadesidir.
Ertelenmiş çalışma nedir? Çoklu numaralandırma tuzağını ve ToList kuralını anlat.
Where/Select gibi operatörler çağrıldıkları anda iş yapmaz; tarif döndürürler ve tarif ancak numaralandırılınca (foreach, ToList, Count, First) çalışır — çalıştığı andaki kaynağı görür. Tuzak: aynı tarifi iki kez tüketmek (Any sonra Count gibi) tüm sorguyu iki kez çalıştırır; pahalı lambda'larda fatura ikiye katlanır. Kural: sonuç birden çok kez kullanılacaksa ya da kaynağın o anki görüntüsü sabitlenecekse ToList ile maddeleştir; tek geçişlik tüketimde tarif bırak — erteleme, kısa devre ve ara koleksiyonsuz akışla lehine çalışır.
Any() ile Count() > 0 arasında ve döngü içindeki Contains'te hangi performans farkları vardır?
Count tüm akışı numaralandırıp sayar — O(n); Any ilk elemanı görünce true döner — O(1), bu yüzden "var mı?" sorusunun doğru aracı Any'dir. Döngü içindeki liste.Contains ise gizli iç döngüdür: n elemanlı dış döngüde m elemanlı listede doğrusal arama O(n·m) eder — Hat 02'nin 10. dersindeki tuzağın LINQ kılığı. Çare aramayı sabit zamanlı yapıya taşımaktır: ToHashSet ya da ToDictionary/ToLookup ile O(n)'e iner.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) Operatör eşleme: "koşula uyan elemanları süzen LINQ metodu?" (Where), "her elemanı dönüştüren?" (Select), "en az bir eleman koşulu sağlıyor mu?" (Any). (2) Çıktı tahmini — erteleme klasiği: sorgu kurulduktan sonra kaynağa eleman eklenir, foreach sonucu sorulur (yeni eleman dahildir; ToList'li varyantta değildir). (3) Seçici farkı: "listede koşulu sağlayan iki eleman varken Single ne yapar?" (InvalidOperationException). (4) SQL köprüsü: Where → WHERE, GroupBy → GROUP BY, Take/Skip → sayfalama eşlemesi; ilişkisel cebir sorusuyla (σ/π) birlikte gelebilir — Hat 02'nin 14. dersiyle çapraz çalış.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"LINQ nedir, nelere dikkat edersin?" için düzen: (1) Tanım + değer — "koleksiyon üzerinde bildirimsel sorgu: nasıl dolaşacağımı değil ne istediğimi yazarım; Where bir seçme, Select bir izdüşümdür — SQL ile aynı cebir" (teori bağı kurmak ayrıştırır). (2) Davranış derinliği — "operatörler tariftir, numaralandırılınca çalışır; bu yüzden çoklu numaralandırmadan kaçınır, çok kullanılacak sonucu ToList ile sabitlerim." (3) Performans refleksi — "Any varken Count kullanmam, döngü içindeki Contains'i HashSet'e çeviririm, önce süzer sonra sıralarım; sıcak yolda ölçerim." (4) Kapanış köprüsü: "Aynı sözdizimi EF Core'da IQueryable ile SQL'e çevrilir — bellekte mi veritabanında mı çalıştığını bilmek, oradaki ilk performans sorusudur."
