C# SelectMany, iç içe koleksiyonları tek bir akışta birleştirmek için kullanılır. Select her elemanı başka bir değere dönüştürür; bu dönüşümün sonucu yine bir koleksiyonsa koleksiyon katmanını korur. SelectMany ise her elemanın ürettiği iç koleksiyonları bir araya getirerek daha düz bir sonuç üretir.
Bu farkı anlamanın en güvenilir yolu, yalnızca metot adına değil, giriş ve çıkış veri tiplerine bakmaktır. C# koleksiyon dönüşümleri konusunda seviyenizi kontrol etmek için C# bilgi testi üzerinden kısa bir değerlendirme yapabilirsiniz.
SelectMany Ne İşe Yarar? Önce Veri Şeklini Görün
Elinizde List<List<string>> varsa veri tek bir liste değildir; her elemanı kendi içinde başka bir string listesidir. Örneğin ders kategorilerini şu şekilde düşünebilirsiniz:
var categories = new List<List<string>>
{
new() { "Değişkenler", "Döngüler" },
new() { "Sınıflar", "Kalıtım" }
};
Bu verinin dış katmanı iki kategori listesinden oluşur. İç katmanlarda ise toplam dört ders adı bulunur. Select kullanıldığında her kategori listesi ayrı bir sonuç olarak kalır. SelectMany kullanıldığında ise bu iç listeler tek bir akışta birleşir ve sonuç şekli IEnumerable<string> olur.
Dolayısıyla temel mantık şudur: Girdiniz IEnumerable<IEnumerable<string>> gibi iç içe bir yapıysa ve hedefiniz tek tek string değerlerine ulaşmaksa, SelectMany uygun araçtır. Ancak her dış eleman için ayrı bir koleksiyon elde etmek istiyorsanız, iç katmanı koruyan Select davranışı daha doğrudur.
Select İç İçe Koleksiyonlarda Neden İç İçe Sonuç Üretir?

Select metodunun mantığı “koleksiyondaki her elemanı bir kez dönüştür” şeklindedir. Selector olarak verilen lambda ifadesinin girdisi dış koleksiyonun tek bir elemanıdır. Bu eleman bir listeyse ve lambda yine bu listeyi döndürüyorsa, Select her listeyi olduğu gibi sonuç akışına ekler.
var selected = categories.Select(category => category);
// Tip: IEnumerable<List<string>>
foreach (var category in selected)
{
Console.WriteLine(string.Join(", ", category));
}
Buradaki lambda ifadesini adım adım okuyalım:
categorydeğişkeninin tipiList<string>değerindedir.=> category, aldığı listeyi değiştirmeden geri döndürür.- Bu nedenle her sonuç elemanı bir
stringdeğil, birList<string>olur. - Sonuç tipi de
IEnumerable<List<string>>benzeri iç içe bir yapı haline gelir.
Aynı durum DTO listelerinde de görülür. Örneğin her kategorinin kendi kurslarını taşıdığı bir model düşünelim:
class Category
{
public string Name { get; set; } = "";
public List<string> Courses { get; set; } = new();
}
var categories = new List<Category>
{
new() { Name = "C#", Courses = new() { "Değişkenler", "LINQ" } },
new() { Name = "Java", Courses = new() { "Sınıflar", "Miras" } }
};
var coursesByCategory = categories.Select(category => category.Courses);
// IEnumerable<List<string>>
Burada Select dış koleksiyondaki kategori sayısını mantıksal olarak korur: her kategori için bir kurs listesi üretir. İç listelerin içeriğini otomatik olarak tek seviyeye indirmez.
| Metot | Lambda çıktısı | Sonuç şekli |
|---|---|---|
Select |
IEnumerable<string> |
IEnumerable<IEnumerable<string>> |
SelectMany |
IEnumerable<string> |
IEnumerable<string> |
Özetle Select, lambda ifadesinin döndürdüğü veri şeklini korur. Lambda bir koleksiyon döndürüyorsa sonuç da koleksiyonların koleksiyonu olur; düzleştirme davranışı kendiliğinden gerçekleşmez.
SelectMany ile İç Koleksiyonları Tek Listeye Düzleştirme

SelectMany, bir koleksiyondaki her elemanın içinden dönen IEnumerable<T> değerlerini tek bir akışta birleştirir. Bu nedenle List<List<string>> gibi iç içe bir yapı, düzleştirme sonrasında List<string> biçimine dönüştürülebilir. Lambda yazımında temel fark, Select(x => ...) ifadesinin her eleman için ayrı bir sonuç üretmesi; SelectMany(x => ...) ifadesinin ise bu sonuçları tek seviyede birleştirmesidir.
| Metot | Girdi | Selector çıktısı | Nihai veri şekli | Tipik kullanım |
|---|---|---|---|---|
Select |
List<List<string>> |
IEnumerable<string> içeren sonuçlar |
IEnumerable<IEnumerable<string>> |
Her iç koleksiyonu ayrı sonuç olarak korumak |
SelectMany |
List<List<string>> |
IEnumerable<string> |
IEnumerable<string> |
Tüm iç koleksiyonları tek akışta birleştirmek |
Aşağıdaki örnekte her dış liste elemanı bir ders kategorisini, içindeki metinler ise o kategorideki dersleri temsil eder. İkinci kategori boş, üçüncü kategori ise null olarak verilmiştir. ?? Enumerable.Empty<string>() ifadesi, null iç listeyi eleman içermeyen güvenli bir enumerable'a dönüştürür.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<List<string>> categories = new List<List<string>>
{
new List<string> { "Değişkenler", "Döngüler" },
new List<string>(),
null,
new List<string> { "Sınıflar" }
};
List<string> courses = categories
.SelectMany(category => category ?? Enumerable.Empty<string>())
.ToList();
Console.WriteLine(string.Join(", ", courses));
}
}
Beklenen konsol çıktısı: Değişkenler, Döngüler, Sınıflar. Boş liste herhangi bir ders üretmeden akışın devam etmesini sağlar; null liste ise boş enumerable'a çevrildiği için aynı şekilde atlanır. Canlı sınıflı eğitimlerde bu tür veri dönüşümlerini eğitmen eşliğinde ve uygulama yaparak öğrenmek isteyenler, canlı sınıflı yazılım eğitimleri kapsamında benzer örnekleri inceleyebilir.
Null ve Boş İç Liste Durumlarında Güvenli SelectMany Kullanımı
Boş bir iç koleksiyon ile null bir iç koleksiyon aynı durum değildir. Boş koleksiyon geçerli bir nesnedir ve içinde eleman bulunmadığı için SelectMany sonucuna katkı yapmadan doğal biçimde atlanır. Null koleksiyon ise selector'ın beklediği enumerable değerini sağlayamaz; risk, düzleştirme sırasında iç koleksiyon üzerinde işlem yapılmak istendiğinde ortaya çıkar.
Bu nedenle null gelebilecek kaynaklarda System.Linq ad alanındaki Enumerable.Empty<T>() metodu savunmacı bir yaklaşım sunar:
var allCourses = categories
.SelectMany(category =>
category.Courses ?? Enumerable.Empty<Course>())
.ToList();
Basit bir DTO senaryosunda Category sınıfı, IEnumerable<Course> Courses özelliğine sahip olabilir. Amaç, tüm kategorilerdeki dersleri tek akışta toplamaktır. category.Courses null ise onu boş enumerable'a çevirmek, hatalı bir değer üretmek yerine o kategorinin sonuçta ders katkısı yapmamasını sağlar.
Bu yaklaşım özellikle dış kaynaktan, dosyadan veya API yanıtından gelen kategori-ders verilerinde önemlidir. Verinin her kategoride mutlaka dolu olacağını varsaymak yerine, null ve boş koleksiyonları açıkça ele almak kodun davranışını daha öngörülebilir kılar.
Select mi SelectMany mi? Veri Tipini Okuyarak Karar Verin
Select ile SelectMany arasındaki doğru tercihi yapmak için önce hedef veri tipine bakın. Her kaynak elemanı tek bir değer üretiyorsa sonuç düz bir koleksiyon olur; her kaynak elemanı başka bir koleksiyon üretiyorsa Select iç içe yapı oluşturur, SelectMany ise bu katmanları birleştirir.
- Her eleman tek bir değer üretiyorsa
Selectkullanın. Örneğin her öğrencinin yalnızca adını almak,IEnumerabledeğeriniIEnumerable<string>değerine dönüştürür. - Her eleman bir koleksiyon üretiyorsa önce sonuç tipini kontrol edin.
kategoriler.Select(kategori => kategori.Dersler)ifadesi,IEnumerable<IEnumerable<string>>üretir. - Hedefiniz
IEnumerable<IEnumerable<X>>gibi katmanlı bir yapıysa, bu yapıyı korumak istediğiniz içinSelectdoğru tercih olabilir. - Hedefiniz
IEnumerable<X>ise,SelectManykullanarak iç koleksiyonların düzleştirildiğini doğrulayın. - İç koleksiyonun
nullolma ihtimali varsa?? Enumerable.Empty<X>()ile güvenli bir varsayılan koleksiyon sağlayın.
| İfade | Her kaynak elemanının ürettiği değer | Sonuç tipi |
|---|---|---|
Select(x => x.Items) |
Bir koleksiyon | IEnumerable<IEnumerable<string>> |
SelectMany(x => x.Items) |
Bir koleksiyonun elemanları | IEnumerable<string> |
Örneğin CourseCategory adlı bir DTO içinde List<string> CourseNames alanı bulunduğunu düşünün. Kategorileri ekranda kategori bazında göstermek istiyorsanız Select kullanarak her kategoriye ait ders listesini koruyabilirsiniz. Arama kutusunda tüm ders adlarını tek bir akışta filtrelemek istiyorsanız SelectMany daha uygundur. Buradaki karar, hangi metodun “daha iyi” olduğundan çok, çıktıdaki veri katmanını korumak mı yoksa kaldırmak mı istediğinizle ilgilidir.
Her durumda SelectMany kullanmak doğru değildir. İç içe gruplama, kategori bilgisi veya hiyerarşik yapı daha sonra kullanılacaksa Select ile katmanları koruyun. C# öğrenimini video formatında, örnekleri tekrar izleyerek sürdürmek isteyenler asenkron C# ve programlama video eğitimlerini inceleyebilir.
Sık Sorulan Sorular
SelectMany ile Select arasındaki temel veri tipi farkı nedir?
Select, her kaynak elemanı için bir sonuç üretir. Sonuçların kendisi koleksiyonsa çıktı IEnumerable<IEnumerable<T>> gibi iç içe bir yapı olur. SelectMany ise bu iç koleksiyonların elemanlarını tek akışta birleştirerek IEnumerable<T> üretir.
SelectMany null veya boş bir iç listeyle karşılaşırsa ne olur?
Boş bir iç liste düzleştirme sonucuna hiç eleman eklemez. Ancak selector null döndürürse işlem güvenli değildir ve çalışma sırasında hata oluşabilir. Bu ihtimali önlemek için items ?? Enumerable.Empty<T>() yaklaşımı kullanılmalıdır.
Bir koleksiyonda Select mi yoksa SelectMany mi kullanmam gerektiğini nasıl anlarım?
Önce her kaynak elemanının tek değer mi, yoksa başka bir koleksiyon mu ürettiğine bakın. İç içe yapıyı koruyacaksanız Select, tüm alt elemanları tek akışta kullanacaksanız SelectMany seçin.
SelectMany kullanmadan iç içe listeleri düzleştirmek mümkün müdür?
Evet. İç içe listeler klasik foreach döngüleriyle gezilerek ayrı bir listeye eklenebilir. SelectMany, bu işlemi daha kısa ve fonksiyonel bir LINQ ifadesiyle yazmayı sağlar; zorunlu değil, pratik bir tercihtir.
Sonuç olarak doğru metodu seçmenin en güvenilir yolu, yazdığınız ifadenin ürettiği veri tipini ve sonraki işlemde ihtiyaç duyduğunuz veri şeklini birlikte değerlendirmektir.