Berk Akademi
Birebir ders başvurusu Ücretsiz ön görüşme Ana Sayfa

C# LINQ Deferred Execution Nedir? Sorgu Ne Zaman Çalışır?

csharp-linq-deferred-execution-sorgu-ne-zaman-calisir
Bu yazıda neler var?
  1. LINQ sorgusu neden tanımlandığı anda çalışmaz?
  2. foreach, ToList ve ToArray değerlendirme zamanını nasıl belirler?
  3. Aynı sorgu ertelenmiş ve materialize edilmiş hâlde nasıl farklı sonuç verir?
  4. Sorgu neden birden fazla kez çalışıyor gibi görünür?
  5. Beklenmeyen LINQ sonucunu bulmak için hata ayıklama kontrol listesi
  6. Sık Sorulan Sorular

C# LINQ’de deferred execution, yani ertelenmiş çalıştırma, sorgunun yazıldığı anda değil; sonuçlara erişilmeye başlandığında değerlendirilmesi demektir. Bu nedenle Where ve Select ile oluşturulan bir ifade çoğu durumda sonuçların hazır bulunduğu yeni bir koleksiyon değil, ileride çalıştırılacak bir sorgu tanımıdır.

Başka bir deyişle LINQ, “sonuçları şimdi üret” yerine “veri dolaştırıldığında şu işlemleri uygula” yaklaşımını kullanabilir. Bu ayrım, kaynak listedeki değişikliklerin sonucu etkilemesine ve aynı sorgunun farklı zamanlarda farklı sonuçlar vermesine neden olur. Yazar: eğitmenimiz Berk Keskin.

LINQ sorgusu neden tanımlandığı anda çalışmaz?

IEnumerable üzerinde kullanılan Where, veriler arasından belirli koşula uyanları seçen bir filtreleme kuralı oluşturur:

var ciftSayilar = sayilar.Where(x => x % 2 == 0);

Bu satırın temel sonucu, çift sayıların hemen yeni bir listeye kopyalanması değildir. İfade, kaynak veriyi dolaşırken her eleman için “bu sayı çift mi?” sorusunun sorulacağını tanımlar.

Select ise dolaşılan her elemanın nasıl dönüştürüleceğini belirtir:

var kareler = sayilar.Select(x => x * x);

Burada da sayıların kareleri sorgu tanımlandığı anda hesaplanmak zorunda değildir. Hesaplama, sorgu enumerate edildiğinde, yani sonuçlar üzerinden ilerlenmeye başlandığında yapılır.

Bu süreci iki aşamalı düşünebilirsin:

  1. Sorguyu tanımlama: Filtreleme veya dönüştürme kuralları hazırlanır.
  2. Sorguyu enumerate etme: Kaynak veriye erişilir, elemanlar dolaşılır ve sonuçlar üretilir.

Örneğin foreach kullanmak, sorgunun sonuçları üzerinden ilerlemeye başlar. Benzer şekilde ToList, ToArray veya sonuçları tüketen başka bir işlem de değerlendirmeyi tetikleyebilir. Kritik nokta, değerlendirme zamanının sorgu değişkeninin oluşturulduğu an değil, kaynak veriye erişimin başladığı an olmasıdır.

foreach, ToList ve ToArray değerlendirme zamanını nasıl belirler?

foreach, ToList ve ToArray değerlendirme zamanını nasıl belirler?

Aşağıdaki örnekte aynı kaynak liste ve aynı Where sorgusu kullanılıyor. Kaynak liste farklı zamanlarda değiştirildiği için ertelenmiş sorgu her enumerate edildiğinde o andaki veriyi görür:

var sayilar = new List<int> { 1, 2, 3, 4 };
IEnumerable<int> ciftler = sayilar.Where(x => x % 2 == 0);

sayilar.Add(6);

Console.WriteLine("foreach:");
foreach (int sayi in ciftler)
{
    Console.WriteLine(sayi);
}

sayilar.Add(8);
List<int> liste = ciftler.ToList();

sayilar.Add(10);
int[] dizi = ciftler.ToArray();

Console.WriteLine("ToList:");
foreach (int sayi in liste)
{
    Console.WriteLine(sayi);
}

Console.WriteLine("ToArray:");
foreach (int sayi in dizi)
{
    Console.WriteLine(sayi);
}

Beklenen çıktı:

foreach:
2
4
6
ToList:
2
4
6
8
ToArray:
2
4
6
8
10

foreach başladığında enumerator kaynak listeyi dolaşır. O anda listede bulunan çift sayılar 2, 4 ve 6 olduğu için bu üç satır üretilir.

ToList çağrısı, çağrıldığı anda ciftler sorgusunu çalıştırır ve bulunan sonuçları yeni bir List<int> nesnesine aktarır. Bu çağrıdan önce 8 eklendiği için liste dört eleman içerir.

Ardından eklenen 10, daha önce oluşturulan liste değişkenine yansımaz. Ancak ToArray çağrısı sorguyu yeniden çalıştırdığı için bu kez 10 da yeni int[] dizisine aktarılır. Bu nedenle ciftler sorgu tanımıdır; liste ve dizi ise farklı anlarda materialize edilmiş, yani sonuçları somut bir koleksiyona aktarılmış değerlerdir.

Bu ayrımı pekiştirmek için C# bilgi testleri içindeki algoritmik düşünme ve programlama sorularını çözerek sorgu akışını adım adım değerlendirebilirsin.

Aynı sorgu ertelenmiş ve materialize edilmiş hâlde nasıl farklı sonuç verir?

Aynı sorgu ertelenmiş ve materialize edilmiş hâlde nasıl farklı sonuç verir?

Ertelenmiş sorgu, yalnızca sorgu ifadesini saklar; kaynak veriyi o anda kopyalamaz. ToList() ise sorguyu hemen çalıştırır ve o andaki sonucu yeni bir listeye aktarır. Bu farkı aynı kaynak liste ve aynı filtreyle görelim:

using System;
using System.Collections.Generic;
using System.Linq;

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

IEnumerable<int> ertelenmis =
    sayilar.Where(sayi => sayi % 2 == 0); // t1

List<int> materializeEdilmis =
    sayilar.Where(sayi => sayi % 2 == 0).ToList(); // t2

sayilar.Add(6); // t3

Console.WriteLine("Ertelenmiş: " +
    string.Join(", ", ertelenmis));

Console.WriteLine("Materialize edilmiş: " +
    string.Join(", ", materializeEdilmis)); // t4

Beklenen çıktı şöyledir:

Ertelenmiş: 2, 4, 6
Materialize edilmiş: 2, 4

Buradaki zaman çizelgesi farkı açıkça gösterir:

  • t0: Kaynak liste oluşturuldu.
  • t1: Ertelenmiş sorgu tanımlandı; henüz filtreleme yapılmadı.
  • t2: ToList() çağrıldı ve o andaki çift sayılar yeni listeye alındı.
  • t3: Kaynak listeye 6 eklendi.
  • t4: İki sonuç yazdırıldı.

ertelenmis değişkeni enumerate edildiğinde kaynak listeyi yeniden dolaşır. Bu nedenle 6 değerini de görür. materializeEdilmis ise t2 anında oluşturulmuş ayrı bir listedir; kaynak liste sonradan değişse bile içeriği kendiliğinden güncellenmez. Konuyu farklı örneklerle pekiştirmek için C# video eğitimlerini ve C# bilgi testini kullanabilirsin.

Sorgu neden birden fazla kez çalışıyor gibi görünür?

Bir IEnumerable sorgusu iki ayrı foreach döngüsünde kullanılırsa sorgu genellikle her döngüde yeniden enumerate edilir. Bu, yalnızca sonucun tekrar okunması anlamına gelmez; kaynak üzerinde yeniden dolaşma, Where koşullarını tekrar değerlendirme ve varsa dönüştürme adımlarını yeniden uygulama anlamına gelir.

var sorgu = sayilar.Where(sayi => sayi % 2 == 0);

foreach (var sayi in sorgu)
{
    Console.WriteLine(sayi);
}

foreach (var sayi in sorgu)
{
    Console.WriteLine(sayi);
}

Kaynak liste değişebilen bir yapıysa iki enumeration arasında sonuçlar da farklılaşabilir. Bu yüzden hata ayıklarken “sonuç neden değişti?” sorusunun yanında “sorgu kaynak üzerinde kaç kez yürütüldü?” sorusunu da sormalısın. Sorgunun kaç kez çalıştığına bakmadan kesin bir hız veya performans hükmü vermek doğru değildir; gerekirse ölçüm yapmalısın.

Şu durumlarda ToList() veya ToArray() ile materialize etmek anlamlı olabilir:

  • Aynı ara sonucu birden fazla kez kullanmak istiyorsan,
  • Kaynağın sonraki değişikliklerinden bağımsız bir anlık görüntüye ihtiyaç duyuyorsan,
  • Filtreleme adımlarının her kullanımda yeniden çalışmasını istemiyorsan.

Buna karşılık her enumeration işleminde kaynağın güncel hâlini görmek istiyorsan ertelenmiş davranışı korumalısın. Bu seçimi yaparken C# bilgi testindeki seviyeni ölçebilir; konuyu kendi kodun üzerinden çalışmak için birebir özel ders programını değerlendirebilirsin.

Beklenmeyen LINQ sonucunu bulmak için hata ayıklama kontrol listesi

LINQ sonucunu beklediğinden farklı bulduğunda önce sorgunun kendisini değiştirmeye çalışma. Sorun çoğu zaman filtrenin nasıl yazıldığından değil, sorgunun hangi anda enumerate edildiğinden kaynaklanır.

  1. Değişkenin türünü kontrol et: Değişken IEnumerable<T> olarak mı duruyor, yoksa List<T> veya diziye mi dönüştürüldü? IEnumerable<T> sorgunun ertelenmiş olabileceğini gösterir; ToList() ve ToArray() ise sonucu o anda materialize eder.
  2. Enumeration satırını işaretle: Sorgunun tanımlandığı satırı ve gerçekten çalıştığı satırı ayır. Where veya Select çağrısı çoğunlukla yalnızca sorgu planını oluşturur. Gerçek değerlendirme foreach, ToList(), ToArray() veya başka bir dolaşma sırasında gerçekleşebilir.
  3. Liste değişikliğinin zamanını belirle: Kaynağa yeni değer eklenmeden önce mi, sonra mı foreach, ToList() ya da ToArray() çağrıldı? “Liste ne zaman değişti?” sorusunun cevabı, çıktının neden farklı olduğunu genellikle açıklar.
  4. Dolaşma sayısını kontrol et: Aynı IEnumerable birden fazla kez enumerate ediliyor olabilir. Her foreach yeni bir değerlendirme başlatabilir.
  5. Kaynağı ve sonucu ayrı yazdır: Kritik adımlardan sonra hem kaynak listeyi hem de sorgu sonucunu ayrı ayrı ekrana yazdır. Böylece değişenin kaynak mı, sorgu sonucu mu olduğunu görürsün.
  6. Zaman çizelgesi oluştur: Sorgunun tanımı, materialize edilmesi, listeye yeni değer eklenmesi ve son enumeration sırasını yan yana yaz.

Mini hata ayıklama senaryosu

var sayilar = new List<int> { 1, 2, 3 };
var ciftSayilar = sayilar.Where(x => x % 2 == 0);

var sabitSonuc = ciftSayilar.ToList(); // Bu anda: [2]

sayilar.Add(4); // Liste burada değişti.

foreach (var sayi in ciftSayilar)
{
    Console.WriteLine(sayi);
}

Beklenen zaman çizelgesi şöyledir: Sorgu tanımlandı, ToList() çağrıldı ve [2] üretildi; ardından kaynak listeye 4 eklendi; son olarak ertelenmiş sorgu yeniden dolaştırıldı. Bu nedenle foreach çıktısı 2 ve 4 olur.

sabItSonuc ise yalnızca materialize edildiği andaki sonucu taşır ve [2] olarak kalır. Dolayısıyla beklenen ve gerçek çıktı arasındaki farkın nedeni sorgu tanımının değişmesi değil, aynı sorgunun farklı değerlendirme anlarında çalıştırılmasıdır.

Sık Sorulan Sorular

Where çağrısı yapıldığı anda filtreleme işlemi gerçekten başlar mı?

Genellikle hayır. Where çağrısı çoğunlukla ertelenmiş bir sorgu oluşturur. Filtreleme, sorgu foreach ile dolaşıldığında veya ToList() ve ToArray() gibi bir işlemle materialize edildiğinde başlar.

ToList ile ToArray arasında deferred execution açısından temel fark nedir?

Deferred execution açısından temel davranışları aynıdır: İkisi de sorguyu çağrıldıkları anda çalıştırır ve sonucu bellekte sabitler. Fark, sonucun sırasıyla List<T> veya dizi olarak saklanmasıdır.

Materialize edilmiş bir liste, kaynak listedeki sonraki değişiklikleri neden otomatik olarak yansıtmaz?

Materialize edilen liste, sorgunun çalıştığı andaki öğelerin kopyalanmış sonucudur. Kaynak listeye daha sonra ekleme veya silme yapılması, daha önce oluşturulmuş bu sonucu yeniden çalıştırmaz.

Aynı LINQ sorgusunu birden fazla kez enumerate etmek ne zaman sorun oluşturabilir?

Kaynak liste değişiyorsa, sorgu her dolaşmada farklı sonuç verebilir. Ayrıca sorgu dosya, veritabanı veya maliyetli bir işlem gibi dış bir kaynağa bağlıysa, her enumeration işlemi aynı çalışmayı yeniden tetikleyebilir.

Bu kontrol listesi, LINQ davranışını zaman çizelgesi üzerinden okumayı kolaylaştırır. Yazı, Berk Akademi yazarı ve eğitmenimiz Berk Keskin tarafından hazırlanmıştır.

Bu içerik aradığın cevabı verdi mi?
Yanıtın, hangi yazıları geliştirmemiz gerektiğini anlamamıza yardımcı olur.
Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

Bu konudan sonra ne okuyabilirsin?

Tüm yazılar

İ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ı; İzmir Ekonomi Üniversitesi'ni bölüm birincisi ve yüksek şeref öğrencisi olarak tamamladı. Bugün yalnızca eğitim vermekle kalmıyor, sektörde aktif olarak yazılım projeleri geliştiriyor ve gerçek dünya deneyimini birebir derslerine taşıyor. Ezberden uzak, mühendislik zihniyetini merkeze alan sürdürülebilir öğrenme sistemleri tasarlayarak sorgulayan, üreten ve problem çözebilen yeni nesil yazılımcılar yetiştiriyor.

Sektörel Deneyim & Projeler

  • Ticarify Entegrasyon Yazılım logosu CEO Ticarify Entegrasyon YazılımPazaryerleri ve e-ticaret sitelerine otomatik e-fatura kesimi, sipariş ve kargo takibi hizmetleri sunan e-Dönüşüm platformunun API mimarisini ve yazılım ekibini yönetmektedir.
  • Benim Düğünüm logosu CEO Benim DüğünümDijital etkinlik ve anı paylaşım platformu.
  • Siberdizayn logosu Yazılım Ekibi Lideri SiberdizaynYüksek anlık oyuncu trafiğine sahip oyun kontrol panelleri ve sunucu altyapıları geliştiren yazılım ekibine liderlik etmektedir.
  • MEDYOGRAFYA 360° Dijital Çözümler logosu Dijital Strateji Lideri MEDYOGRAFYA 360° Dijital ÇözümlerŞirketlerin dijital çözümlerde uzun vadede nasıl ilerlemesi gerektiği ve dijital dönüşüm süreçlerinin yönetilmesine destek olmaktadır.
  • İzmir Ekonomi Üniversitesi logosu Danışma Kurulu Üyesi İzmir Ekonomi ÜniversitesiMezun olduğu üniversitesinde, Bilgisayar Programcılığı bölümünün akademik müfredatını güncel sektör ihtiyaçlarına göre şekillendirmek adına Danışma Kurulu'nda görev almaktadır.
WhatsApp Hemen Ara