Logo
Ana Sayfa
ÖZEL KODLAMA DERSLERİ
Yazılım Özel Ders
Tüm birebir programlara genel bakış
Python Yazılım Kursu
Sıfırdan ileri seviyeye birebir Python
Java Yazılım Kursu
OOP odaklı birebir Java eğitimi
AP Computer Science Principles
AP CSP sınav hazırlığı
GRUP DERSLERİ
Python & Django Masterclass
SINIRLI KONTENJAN
Java & Spring Boot Masterclass
SINIRLI KONTENJAN
C# .NET Masterclass
SINIRLI KONTENJAN
VİDEO DERSLER
Sıfırdan Temel Python Kursu
Kendi Hızında Öğren
ÜCRETSİZ
Seviye Testi
Ücretsiz — Python, Java, algoritma seviye testleri
Kariyerini Keşfet
Sertifika Doğrula
Belge numarası ve soyad ile doğrulama

C# LINQ ile Koleksiyon İşleme: Where, Select, OrderBy Mantığı

Yazar: Berk Keskin 28.08.2026 ~10 dk okuma 1 Okunma
csharp-linq-koleksiyon-isleme-mantigi

LINQ (Language Integrated Query), C# içine gömülü, koleksiyonlar üzerinde filtreleme, dönüştürme ve sıralama işlemlerini tek satırlık zincirleme ifadelerle yapmayı sağlayan bir sorgulama yaklaşımıdır. Bir liste veya dizi üzerinde Where, Select, OrderBy gibi metotları art arda çağırarak, klasik for döngüleri yazmadan aynı sonuca ulaşılır. Bu yazıda LINQ'in method syntax mantığını, temel metotlarını, sorgu okuma adımlarını ve deferred execution kavramını C# özelinde ele alacağız.

LINQ Nedir ve Method Syntax Mantığı Nasıl Çalışır?

LINQ, temelde koleksiyon işleme problemine deklaratif programlama penceresinden bakar. Klasik yaklaşımda geliştirici bir döngü kurar, bir sayaç değişkeni tanımlar, koşulu kontrol eder ve sonucu manuel olarak yeni bir listeye ekler; yani "nasıl yapılacağını" adım adım yazar. LINQ ise geliştiriciye "ne istediğini" söyleme imkânı verir: "Bu listeden şu koşulu sağlayanları al, şu şekilde dönüştür, şuna göre sırala" demek yeterlidir, adımların nasıl yürütüleceği runtime'a bırakılır.

Bu mantığın C#'taki en yaygın ifade biçimi method syntax'tır. Method syntax, LINQ metotlarının bir koleksiyon üzerinde nokta (.) operatörüyle zincirleme çağrılmasıdır. Her metot bir IEnumerable<T> döndürdüğü için, bir metodun çıktısı doğrudan bir sonrakinin girdisi olur ve sorgu tek bir akışa dönüşür:

List<int> sayilar = new List<int> { 4, 8, 15, 16, 23, 42 };

var sonuc = sayilar
    .Where(n => n > 10)
    .Select(n => n * 2)
    .OrderBy(n => n)
    .ToList();

// sonuc: 30, 32, 46, 84

Bu örnekte üç ayrı işlem (filtreleme, dönüştürme, sıralama) tek bir zincir hâlinde okunur. Bunun alternatifi olan sorgu sözdizimi (query syntax) SQL'e benzer bir yapı sunsa da, sektörde method syntax çok daha yaygın kullanılır çünkü zincirleme metot çağrıları IntelliSense ile daha kolay keşfedilir ve karmaşık ifadelerde daha esnektir. Konuya yeni başlayanlar bu mantığı pratikte oturtmak isterse, C# bilgi seviyeni ölçen test ile mevcut seviyeni görüp eksik noktaları tespit edebilirsin. LINQ'i anlamanın en kritik adımı, her metodun yeni bir koleksiyon değil, bir "sorgu tanımı" ürettiğini kavramaktır; bu konuya ilerleyen bölümlerde deferred execution başlığı altında ayrıca değinilecek.

Where, Select, OrderBy ve OrderByDescending Metotları

LINQ'in en sık kullanılan dört metodu, bir koleksiyon üzerinde yapılabilecek temel işlemlerin neredeyse tamamını karşılar. Where bir koşula göre eleman filtreler, Select her elemanı başka bir forma dönüştürür, OrderBy ve OrderByDescending ise sıralama yapar. Bu dördü genellikle birlikte, zincirleme biçimde kullanılır.

Metot İşlevi Örnek
Where Koşulu sağlayan elemanları filtreler ogrenciler.Where(o => o.Not >= 50)
Select Her elemanı yeni bir değere veya nesneye dönüştürür ogrenciler.Select(o => o.AdSoyad)
OrderBy Belirtilen anahtara göre küçükten büyüğe sıralar ogrenciler.OrderBy(o => o.Not)
OrderByDescending Belirtilen anahtara göre büyükten küçüğe sıralar ogrenciler.OrderByDescending(o => o.Not)

Bu dört metodun birlikte kullanıldığı somut bir örnek, mantığı daha net gösterir. Aşağıdaki kod, bir öğrenci listesinden notu 50'nin üzerinde olanları alır, sadece isimlerini çeker ve nota göre büyükten küçüğe sıralar:

var basarililar = ogrenciler
    .Where(o => o.Not > 50)
    .OrderByDescending(o => o.Not)
    .Select(o => o.AdSoyad)
    .ToList();

Dikkat edilmesi gereken nokta, Select'in sırasının değişebilmesidir; burada sıralama işlemi Not alanına göre yapıldığı için Select ile isimlere indirgemeden önce çalıştırılmıştır. Eğer Select önce çağrılıp koleksiyon string listesine dönüştürülseydi, Not bilgisi kaybolacağı için OrderByDescending artık nota göre sıralama yapamazdı. Bu küçük detay, LINQ zincirlerinde metot sırasının rastgele değil, veri akışına bağlı olarak bilinçli seçilmesi gerektiğini gösterir.

Bir LINQ Sorgusu Adım Adım Nasıl Okunur?

Zincirleme bir LINQ sorgusunu ilk kez gören biri için karışık görünebilir, ancak sorgu her zaman soldan sağa, yukarıdan aşağıya doğru tek bir veri akışı olarak okunur. Aşağıdaki adımlar, bir önceki bölümdeki basarililar sorgusunun nasıl çözümlendiğini gösterir:

  1. Kaynak koleksiyon belirlenir: ogrenciler listesi, sorgunun başlangıç noktasıdır ve zincirin ilk halkasına girdi sağlar.
  2. Where koşulu uygulanır: listedeki her eleman tek tek kontrol edilir, sadece Not > 50 şartını sağlayanlar bir sonraki adıma geçer.
  3. OrderByDescending devreye girer: filtrelenmiş elemanlar Not alanına göre büyükten küçüğe yeniden dizilir.
  4. Select dönüşümü yapılır: sıralanmış öğrenci nesnelerinin her biri, sadece AdSoyad alanına indirgenir.
  5. ToList() çağrılır: buraya kadar tanımlanmış olan sorgu bu noktada gerçekten çalıştırılır ve sonuç somut bir liste hâline gelir.

Bu beş adımı zihinde canlandırmak, özellikle uzun zincirlerde hata ayıklamayı kolaylaştırır. Bir sorgu beklenenden farklı sonuç verdiğinde, zinciri tek tek bu adımlara bölüp her aşamada ara çıktıyı (örneğin geçici bir değişkene atayarak) incelemek, hangi metodun beklenmedik davrandığını hızlıca ortaya çıkarır. Sorgu ne kadar uzarsa uzasın, her metot bir öncekinin çıktısını devralıp işleyerek zinciri bir sonrakine devreder; bu yüzden LINQ'i okurken "veri şu an ne hâlde, bu metot ona ne yapıyor" sorusunu her adımda tekrar sormak en sağlam yöntemdir.

Deferred Execution: Sorgu Ne Zaman Gerçekten Çalışır?

LINQ ile ilgili en çok kafa karıştıran konulardan biri, bir sorgunun tanımlandığı an ile çalıştığı anın aynı olmamasıdır. Where, Select, OrderBy gibi metotları zincirlediğinizde, aslında hiçbir eleman henüz filtrelenmiş, dönüştürülmüş ya da sıralanmış değildir. Bu davranışa deferred execution (ertelenmiş çalıştırma) denir; sorgu yalnızca bir tarif (recipe) olarak hafızada durur, gerçek işlem sonucu ihtiyaç duyulduğu ana kadar ertelenir.

Bir LINQ sorgusu şu durumlardan biri gerçekleştiğinde fiilen çalışır:

  • Sonuç üzerinde foreach ile döngü kurulduğunda
  • ToList(), ToArray() veya ToDictionary() gibi bir metotla sonuç somutlaştırıldığında (materialize edildiğinde)
  • Count(), First(), Sum() gibi tek bir değer döndüren bir metot çağrıldığında

Aşağıdaki örnek bu farkı net biçimde gösterir:

var sayilar = new List<int> { 1, 2, 3, 4, 5 };

// Sorgu burada sadece TANIMLANIR, henüz çalışmaz
var sorgu = sayilar.Where(n => n > 2);

sayilar.Add(10); // Kaynağa yeni eleman eklendi

// Sorgu burada GERÇEKTEN çalışır ve güncel veriyi görür
foreach (var n in sorgu)
    Console.WriteLine(n); // 3, 4, 5, 10 yazdırır

Dikkat edilmesi gereken nokta şudur: sorgu değişkeni tanımlandığı anda listeye henüz 10 eklenmemişti, ama foreach çalıştığında liste güncel haliyle taranmıştır. Eğer aynı sorguyu .ToList() ile hemen somutlaştırsaydınız, sonuç o anki veriyle donmuş (kalıcı) hale gelir ve sonradan listeye eklenen elemanlar sonucu etkilemezdi. Bu fark, özellikle veritabanı sorgularında (Entity Framework gibi katmanlarda) kritik önem taşır; çünkü ertelenmiş bir sorgu gereksiz yere birden fazla kez veritabanına gidebilir. Bu nedenle bir LINQ sonucunu birden fazla kez kullanacaksanız, genellikle bir kez ToList() ile somutlaştırıp öyle kullanmak, hem performans hem öngörülebilirlik açısından daha güvenli bir alışkanlıktır.

Klasik Döngü ile LINQ'lü Kod Yan Yana

LINQ'in getirdiği deklaratif yaklaşımı somut görmenin en iyi yolu, aynı işi yapan iki kod parçasını yan yana koymaktır. Örneğin bir öğrenci listesinden notu 70'in üzerinde olanları alıp isme göre alfabetik sıralamak istediğinizi varsayalım. Klasik foreach döngüsüyle bu işlem şöyle görünür:

var basarililar = new List<Ogrenci>();
foreach (var ogrenci in ogrenciler)
{
    if (ogrenci.Not > 70)
        basarililar.Add(ogrenci);
}
basarililar.Sort((a, b) => string.Compare(a.Isim, b.Isim));

Aynı sonucu LINQ ile şu şekilde elde edersiniz:

var basarililar = ogrenciler
    .Where(o => o.Not > 70)
    .OrderBy(o => o.Isim)
    .ToList();

İki kod parçası da aynı çıktıyı üretir, ancak ikincisi ne yapılacağını tarif eder, birincisi ise bunun nasıl yapılacağını adım adım anlatır. Klasik döngüde geçici bir liste oluşturmanız, koşulu manuel yazmanız ve ayrı bir sıralama adımı çağırmanız gerekirken, LINQ sürümünde tüm akış tek bir zincir içinde okunur. Bu kısalık her zaman "daha iyi" anlamına gelmez; döngü içine hata ayıklama (debug) noktaları koymak, ara adımları loglamak veya karmaşık iş kuralları eklemek istediğinizde klasik döngü daha esnek kalabilir. Ancak basit filtreleme-dönüştürme-sıralama senaryolarında LINQ, kodun niyetini çok daha hızlı okunur kılar. Bu iki yaklaşımı birlikte kavramak, sadece sözdizimi ezberlemek değil, aynı problemi farklı soyutlama seviyelerinde çözebilme becerisidir. Böyle bir mantıksal esneklik kazanmak isteyenler için canlı yazılım eğitimi programı kapsamında döngü mantığından koleksiyon işleme kalıplarına kademeli bir öğrenme yolu izlenir; amaç, öğrencinin önce problemi çözme mantığını oturtup ardından dilin sunduğu kısayolları anlamlı biçimde kullanmasıdır.

Aynı Mantık Java Stream API ve Python List Comprehension'da

LINQ'in temsil ettiği deklaratif koleksiyon işleme fikri C#'a özgü değildir; aynı mantık farklı dillerde farklı sözdizimleriyle karşımıza çıkar. Java dünyasında bu ihtiyacı Stream API karşılar. Az önceki öğrenci örneğini Java'da filter ve sorted metotlarıyla şöyle yazarsınız:

List<Ogrenci> basarililar = ogrenciler.stream()
    .filter(o -> o.getNot() > 70)
    .sorted(Comparator.comparing(Ogrenci::getIsim))
    .collect(Collectors.toList());

Yapısal benzerlik dikkat çekicidir: LINQ'teki Where ile Stream'deki filter, OrderBy ile sorted neredeyse birebir aynı rolü üstlenir. İkisi de zincirleme (method chaining) metot çağrılarıyla çalışır ve ikisi de ara adımları tembel (lazy) değerlendirebilir; yani Stream'de de collect çağrılana kadar filtreleme fiilen gerçekleşmez — LINQ'teki deferred execution mantığına oldukça yakın bir davranıştır. Bu paralelliği fark eden bir öğrenci, bir dilde öğrendiği koleksiyon işleme kalıbını diğerine aktarmakta zorlanmaz; nesne yönelimli programlama temelleri güçlü şekilde kurulduğunda 1-1 özel Java dersleri üzerinden bu tür kalıpları pratiğe dökmek daha hızlı ilerler.

Python tarafında ise aynı iş genellikle ayrı metot zincirleri yerine tek satırlık list comprehension yapısıyla ifade edilir:

basarililar = sorted(
    [o for o in ogrenciler if o.not_ > 70],
    key=lambda o: o.isim
)

Burada if koşulu LINQ'teki Where'in, sorted(..., key=...) ise OrderBy'ın karşılığıdır; fakat Python bunu ayrı metot çağrıları yerine tek bir ifade (expression) içine sıkıştırır ve sonucu anında somutlaştırır, yani ertelenmiş çalıştırma kavramı burada geçerli değildir. Üç dilin de vardığı nokta aynıdır: koleksiyon üzerinde filtrele, dönüştür, sırala — fark yalnızca bu niyetin hangi sözdizimsel kalıpla ifade edildiğidir. Bir dili öğrenirken bu ortak mantığı görmek, diğer dillere geçişi bir "yeniden öğrenme" değil bir "yeniden adlandırma" haline getirir.

Ne Zaman LINQ, Ne Zaman Düz Döngü Tercih Edilmeli?

LINQ ile klasik döngü arasındaki seçim bir "doğru" ve "yanlış" meselesi değildir; hangi aracın hangi bağlamda daha uygun olduğunu bilmekle ilgilidir. Bu kararı verirken bakılması gereken iki temel kriter vardır: okunabilirlik ve performansın gerçekten kritik olduğu senaryolar. Bir kod parçasını altı ay sonra tekrar okuyacak olan da, ekip arkadaşınız da anlaşılırlığı önemser; bu yüzden çoğu günlük senaryoda LINQ tercih sebebidir.

Aşağıdaki kriterler pratik bir karar rehberi olarak kullanılabilir:

  • Veri üzerinde filtreleme, dönüştürme ve sıralama gibi bildirimsel işlemler yapılıyorsa LINQ, niyeti "nasıl" yerine "ne" üzerinden anlatarak kodu kısaltır.
  • Döngü içinde birden fazla koşul dallanması, erken çıkış (break/continue) veya karmaşık yan etkiler varsa klasik döngü, akışı daha açık gösterir.
  • Çok büyük veri kümelerinde tekrar tekrar çalıştırılan, performansın milisaniyeler mertebesinde ölçüldüğü kritik döngülerde, LINQ'in getirdiği soyutlama katmanı ek maliyet oluşturabileceğinden elle yazılmış döngü daha öngörülebilir olabilir.
  • Zincirleme birden fazla işlem (filtrele, dönüştür, sırala) art arda uygulanacaksa LINQ, iç içe döngü karmaşasını önler.
  • Kodun test edilebilirliği ve okunurluğu öncelikliyse, LINQ'in kısa ve deklaratif yapısı bakım maliyetini azaltır.

Pratikte deneyimli geliştiriciler genelde önce LINQ ile yazar, yalnızca profilleme sonucu gerçek bir darboğaz tespit edildiğinde o spesifik bölümü döngüye çevirir. Bu yaklaşım "önce doğru ve anlaşılır, gerekirse optimize et" prensibiyle örtüşür. Koleksiyon işleme mantığını farklı senaryolarla pekiştirmek isteyenler, C# bilgi seviyesini ölçen test üzerinden Where, Select ve OrderBy gibi kavramlara ne kadar hâkim olduklarını görebilir. Sonuç olarak seçim, kodun kimin tarafından ve hangi koşullarda çalıştırılacağına bağlı bir mühendislik kararıdır; körü körüne "LINQ her zaman daha iyidir" ya da "döngü her zaman daha hızlıdır" gibi genellemeler yanıltıcı olur.

Sık Sorulan Sorular

LINQ sadece diziler ve listeler için mi kullanılır?

Hayır. LINQ, IEnumerable arayüzünü uygulayan her koleksiyon üzerinde çalışabilir; diziler ve listelerin yanı sıra sözlükler, kümeler ve hatta veritabanı sorgu sonuçları gibi farklı veri kaynaklarında da kullanılabilir. Önemli olan koleksiyonun bu arayüzü desteklemesidir.

Deferred execution performansı nasıl etkiler?

Deferred execution, sorgunun tanımlandığı anda değil gerçekten enumerate edildiği (foreach ile dolaşıldığı, ToList gibi bir metotla listelendiği) anda çalışmasını sağlar. Bu, gereksiz hesaplamaların önüne geçebilir ama aynı sorgu birden fazla kez enumerate edilirse her seferinde yeniden çalışacağından, sonucun bir kez hesaplanıp saklanması gerektiği durumlarda dikkatli davranılmalıdır.

LINQ ile foreach döngüsü aynı sonucu mu verir?

Doğru yazıldığında evet, aynı mantıksal sonuca ulaşılır; ikisi de aynı filtreleme, dönüştürme veya sıralama işlemini gerçekleştirir. Fark sonuçta değil, kodun ifade biçiminde ve okunabilirliğindedir; LINQ niyeti daha kısa ve bildirimsel şekilde anlatırken foreach adım adım kontrol imkânı sunar.

Java Stream API ile LINQ birebir aynı mı çalışır?

İkisi de filtreleme, dönüştürme ve toplama gibi benzer deklaratif işlemleri destekler ve kavramsal olarak yakın bir mantık izler. Ancak sözdizimi, tembel değerlendirme detayları ve dil içindeki entegrasyon biçimleri farklıdır; bu yüzden "aynı mantık, farklı sözdizimi" demek daha doğru bir ifadedir.

Python list comprehension LINQ'e göre daha mı basit?

List comprehension genellikle tek satırlık filtreleme ve dönüştürme işlemleri için daha kısa bir sözdizimi sunar, bu yüzden basit görünebilir. Ancak LINQ'in method syntax yapısı, zincirleme birden fazla işlemi (filtrele, sırala, grupla) daha okunabilir şekilde ifade etmede avantaj sağlayabilir; "basitlik" burada alışkanlığa ve senaryoya bağlı bir tercih meselesidir.

LINQ, Stream API ve list comprehension aslında aynı deklaratif programlama felsefesinin farklı dillerdeki yansımalarıdır; birini öğrenmek diğerlerini kavramayı kolaylaştırır. Koleksiyon işleme mantığını farklı dillerde pekiştirmek ve pratik yapmak isteyenler canlı yazılım eğitimi sürecinde bu kavramları uygulamalı örneklerle derinleştirebilir.

Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

İlgili Eğitimler

Berk Keskin — Yazılım Geliştirici ve Eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; bugün öğrencinin seviyesine ve hedefine göre şekillenen sürdürülebilir öğrenme sistemleri tasarlıyor. 300'den fazla kişiye ezber değil, düşünerek kod yazmayı öğretti — Berk Akademi'de izlemeye değil üretmeye dayalı öğrenme kültürünü o kuruyor.

WhatsApp Hemen Ara