1. Einführung
Erinnern wir uns, wie ein normales Select funktioniert:
var names = users.Select(user => user.Name);
Jedem User wird sein Name zugeordnet – wir bekommen eine Liste von Namen. Easy! Aber was, wenn wir eine Liste aller Bestellungen aller User haben wollen?
var ordersList = users.Select(user => user.Orders);
Was passiert hier? Für jeden User bekommen wir eine Liste von Bestellungen. Das Ergebnis ist also eine Collection von Collections (IEnumerable<List<Order>>). Das ist wie ein Regal mit Kisten: Um an jede Bestellung zu kommen, musst du jede Kiste (Bestellliste des Users) einzeln öffnen.
Wir wollen aber alles "auspacken", um mit einer ganz normalen Bestellliste zu arbeiten. Genau hier hilft uns SelectMany weiter.
Analogie: Kisten auskippen
Stell dir vor, all deine Sachen liegen nicht wild rum, sondern in Kisten. Jede Kiste ist eine Collection, zum Beispiel die Einkäufe eines Users. Wenn du ALLE Sachen auf einmal sehen willst (z.B. für einen großen Check), willst du nicht erst alle Kisten durchgehen und dann die Sachen darin, und das immer wieder. Viel praktischer ist es, einfach alles aus allen Kisten auf einen Haufen zu kippen – und dann diesen Haufen zu sortieren. Genau so funktioniert SelectMany.
2. Was ist SelectMany?
SelectMany ist eine LINQ-Methode, die eine Collection von Collections nimmt und sie in eine "flache" Collection verwandelt. "Flach" heißt hier, dass die resultierende Sequenz keine inneren Listen mehr enthält – alle Elemente aus den verschachtelten Collections sind auf einer Ebene, als hätte man sie "herausgezogen". Keine Matrjoschkas, einfach eine lange Kette von Werten.
Die Methodensignatur sieht so aus:
IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector
)
Das Prinzip ist simpel: Du hast eine Collection (z.B. eine User-Liste), und jedes Element enthält wiederum eine Collection (z.B. die Bestellungen dieses Users). SelectMany nimmt jeden User, holt dessen Bestellungen raus und packt sie in einen einzigen Bestell-Stream – ohne "Verpackung", ohne Verschachtelung. Genau das macht es so praktisch, wenn du von einer "Liste von Listen" zu einer normalen Liste wechseln willst.
3. Einfache Beispiele
Beispiel 1: User-Liste und ihre Bestellungen
Starten wir mit unserem App-Modell, das wir Schritt für Schritt erweitern:
// Angenommen, diese Klassen gibt's schon aus den vorherigen Lektionen
class User
{
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
}
Angenommen, wir haben eine User-Liste:
var users = new List<User>
{
new User
{
Name = "Ivan",
Orders = new List<Order>
{
new Order { Id = 1, ProductName = "Buch" },
new Order { Id = 2, ProductName = "Stift" }
}
},
new User
{
Name = "Maria",
Orders = new List<Order>
{
new Order { Id = 3, ProductName = "Laptop" }
}
}
};
Aufgabe: Erstelle eine Liste aller Produkte, die jemals von allen Usern gekauft wurden.
Normales Select:
var ordersPerUser = users.Select(user => user.Orders);
// IEnumerable<List<Order>>
Hier bekommen wir am Ende viele "Kisten", in jeder eine Bestellliste pro User.
SelectMany:
var allOrders = users.SelectMany(user => user.Orders);
// IEnumerable<Order>
Jetzt bekommen wir eine einzige "lange" Collection aller Bestellungen!
Visualisierung
| User | Bestellungen |
|---|---|
| Ivan | Buch, Stift |
| Maria | Laptop |
- Nach Select: [[Buch, Stift], [Laptop]] – Liste von Listen
- Nach SelectMany: [Buch, Stift, Laptop] – eine einzige Liste
Beispiel 2: Arbeiten mit mehreren Verschachtelungsebenen
Wenn z.B. eine Bestellung eine Produktliste (Warenkorb) hat, kannst du SelectMany auch mehrfach anwenden – wie bei einer Matrjoschka!
class Product
{
public string Name { get; set; }
}
class Order
{
public int Id { get; set; }
public List<Product> Products { get; set; }
}
class User
{
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
var users = new List<User>
{
new User
{
Name = "Peter",
Orders = new List<Order>
{
new Order
{
Id = 10,
Products = new List<Product>
{
new Product { Name = "Handy" },
new Product { Name = "Ladegerät" }
}
}
}
}
};
Um alle Produkte aller Bestellungen aller User zu bekommen, nutzt du doppeltes SelectMany:
var allProducts = users
.SelectMany(u => u.Orders)
.SelectMany(o => o.Products);
// IEnumerable<Product>
So packst du erst eine Ebene "Kisten" aus, dann die nächste.
Beispiel 3: SelectMany im Query Syntax
Wenn du mehr auf SQL-ähnlichen LINQ-Syntax (Query Syntax) stehst, dann drückst du SelectMany mit mehreren from hintereinander aus:
var allProducts =
from user in users
from order in user.Orders
from product in order.Products
select product;
Hier nimmt jeder folgende from ein Element aus der Collection, die vom vorherigen zurückgegeben wurde.
users
└─ user1
└─ order1
└─ product1, product2
└─ order2
└─ product3
└─ user2
└─ order3
└─ product4
Query Syntax läuft alle Wege ab und gibt eine lange Produktliste zurück.
4. Anwendungsfälle und Feinheiten
SelectMany für Transformation und Filterung
Mit SelectMany kannst du nicht nur einfach auspacken, sondern auch gleich filtern oder Elemente projizieren. Zum Beispiel: Nur Produkte mit einem Preis über 1000 Euro aus allen Bestellungen auswählen:
var expensiveProducts = users
.SelectMany(u => u.Orders)
.SelectMany(o => o.Products)
.Where(p => p.Price > 1000);
Oder – mit etwas Logik direkt im SelectMany (über einen zusätzlichen Selector):
var expensiveProducts = users
.SelectMany(u => u.Orders.SelectMany(o => o.Products))
.Where(p => p.Price > 1000);
Das ist Geschmackssache: Beide Varianten teilen das Problem in einzelne, aufeinanderfolgende Schritte.
Überladung von SelectMany mit Projektion nutzen
Eine zusätzliche "fortgeschrittene" Überladung der Methode erlaubt es, nicht nur auszupacken, sondern gleich das Ergebnis zu formen. Zum Beispiel: Paare "User-Name – Produkt-Name" bekommen:
var userProductPairs = users.SelectMany(
user => user.Orders.SelectMany(order => order.Products),
(user, product) => new { user.Name, product.Name }
);
Wie funktioniert das? Der erste Parameter – user => user.Orders.SelectMany(order => order.Products) – packt alle Produkte des Users aus, der zweite kombiniert das ursprüngliche User-Objekt und das Produkt im inneren Enumerator.
5. Wichtige Punkte und typische Fehler
Du kannst in die Falle tappen, dass du SelectMany vergisst und statt einer flachen Sequenz einen Haufen "Kisten" bekommst. Zum Beispiel, wenn du normales Select benutzt, wo du eigentlich alle Elemente direkt auspacken willst. Dann kannst du die Elemente nicht direkt durchgehen oder bearbeiten.
Gerade bei mehreren Verschachtelungsebenen kann man schnell durcheinanderkommen. Merke dir: Wenn du am Ende keine Liste von Listen, sondern eine flache Liste willst – nimm SelectMany.
Wo ist das praktisch?
Immer wenn du ein Objekt mit einer Collection hast, und jedes Element darin hat wieder eine eigene Collection (z.B. "User – Bestellungen", "Kurs – Studenten", "News – Kommentare"), wirst du zu SelectMany zurückkommen. In Vorstellungsgesprächen ist das ein Lieblingsthema: Nicht alle Anfänger unterscheiden Select von SelectMany und können Collections korrekt auspacken.
In echten Projekten sorgt das für schönen, knackigen und schnellen Code ohne unnötige Verschachtelung. Auf der .NET-Plattform ist das der Standard- und optimierte Weg, um "geschichtete" Collections zu verarbeiten.
Unterschied zwischen Select und SelectMany
Die Methode Select behält die Struktur bei: Wenn du sie auf eine User-Collection anwendest, bei der jeder User eine Bestellliste hat, bekommst du eine Collection von Collections – zum Beispiel [[A, B], [C]]. Das ist praktisch, wenn du weiter gruppiert arbeiten willst (z.B. pro User).
SelectMany hingegen "flacht ab" – alle verschachtelten Collections werden zu einer gemeinsamen Sequenz: [A, B, C]. Das ist nützlich, wenn dir die Gruppierung egal ist und du mit allen inneren Elementen als eine Masse arbeiten willst – z.B. alle Produkte finden, egal zu welchem User oder welcher Bestellung sie gehören.
+---------------------+ +--------------------+
| users | | allOrders |
+---------------------+ +--------------------+
| Ivan: [A, B] | --+ +-> Order A |
| Maria: [C] | --+ | +-> Order B |
+---------------------+ | | +-> Order C |
| | +--------------------+
Select: | |
[[A, B], [C]] <--------+ +
|
SelectMany: <--------+
[A, B, C]
Kurz gesagt:
- Select → „Gib mir die Bestelllisten pro User“;
- SelectMany → „Gib mir einfach alle Bestellungen“.
GO TO FULL VERSION