1. Einführung
Stell dir zwei Stapel Briefe vor: Der erste – Umschläge mit Namen drauf, der zweite – Postkarten für diese Empfänger. Deine Aufgabe: Jeden Umschlag mit der Postkarte aus derselben Position im zweiten Stapel matchen. Der erste Umschlag – mit der ersten Postkarte, der zweite – mit der zweiten, usw. Du gehst beide Stapel durch, nimmst jeweils ein Element und "zippst" sie zusammen.
In der Programmierung nennt man das zip – wie bei einem Reißverschluss, der zwei Seiten exakt an den Positionen zusammenfügt.
In LINQ ist das ein praktisches und mächtiges Tool, um Daten aus zwei (manchmal auch mehr) Sequenzen zu kombinieren, wenn die Reihenfolge zählt: Erstes Element mit erstem, zweites mit zweitem usw.
Wichtig zu merken: Zip funktioniert nur solange, wie in beiden Collections noch Elemente sind. Wenn eine kürzer ist – wird das Ergebnis auf die Länge der kürzesten abgeschnitten.
Syntax und Funktionsweise
Zip nimmt zwei (oder mehr) Listen und kombiniert sie zu einer neuen: Die Elemente werden nach Index zusammengefügt, also 0 mit 0, 1 mit 1 usw. Wenn eine Liste früher endet, werden keine neuen Paare mehr "gezipped" – das Ergebnis hat die Länge der kürzesten Liste.
Signatur (Standard):
IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector
)
Achte darauf: Zip gibt nicht einfach Paare zurück, sondern das, was du im resultSelector angibst. Das kann ein Tuple, ein String, ein Objekt oder sonst was sein – je nach deiner Logik.
- first und second – die Collections, die kombiniert werden.
- resultSelector – eine Funktion, die die aktuellen Elemente beider Collections nimmt und ein neues Ergebnis zurückgibt.
Einfach gesagt: Wir gehen beide Collections gleichzeitig durch und machen aus jedem Paar das, was wir brauchen.
2. Beispiele für Zip – von einfach bis komplex
Das einfachste Beispiel: Zwei Zahlen-Arrays addieren
Angenommen, wir haben zwei Listen:
- Punkte in Mathe
- Punkte in Literatur
// Zwei Arrays mit Punkten der Studenten
int[] mathScores = { 5, 4, 3, 5, 2 };
int[] literatureScores = { 4, 5, 3, 4, 3 };
// Nach Position zusammenfügen und addieren
var totalScores = mathScores.Zip(literatureScores, (math, literature) => math + literature);
foreach (var score in totalScores)
Console.WriteLine($"Gesamtpunktzahl des Studenten: {score}");
Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 6
Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 5
Wie du siehst, alles ganz logisch: Der erste Student bekommt die Summe der ersten Punkte, der zweite die zweiten usw.
Strings und Zahlen kombinieren: Produkte mit Preisen beschriften
Angenommen, du hast eine Liste mit Produkten und eine mit Preisen. Du willst ausgeben: "Produktname — Preis".
string[] productNames = { "Apfel", "Birne", "Banane" };
decimal[] productPrices = { 50.5m, 60.0m, 35.2m };
var info = productNames.Zip(productPrices, (name, price) => $"{name} — {price} Euro");
foreach (var s in info)
Console.WriteLine(s);
Apfel — 50,5 Euro
Birne — 60,0 Euro
Banane — 35,2 Euro
Mit Objekt-Collections arbeiten
Nehmen wir an, wir hatten vorher eine Klasse Student und eine Collection von Studenten. Jetzt gibt es ein separates Array mit Bewertungen, das nach Reihenfolge gematcht wird:
public class Student
{
public string Name { get; set; }
}
List<Student> students = new List<Student>
{
new Student { Name = "Vasya" },
new Student { Name = "Petya" },
new Student { Name = "Masha" }
};
int[] ratings = { 7, 9, 8 };
var studentsWithRatings = students.Zip(ratings, (student, rating) =>
$"{student.Name}: Bewertung {rating}");
foreach (var s in studentsWithRatings)
Console.WriteLine(s);
Vasya: Bewertung 7
Petya: Bewertung 9
Masha: Bewertung 8
Komplexere Objekte kombinieren
Im echten Leben muss man oft z.B. zwei Bestelllisten aus verschiedenen Systemen zusammenführen: Jeder Bestellung wird ein Bearbeitungsergebnis oder Status zugeordnet.
string[] orders = { "A-1", "B-2", "C-3" };
string[] statuses = { "Erledigt", "In Bearbeitung", "Abgelehnt" };
var orderStatusList = orders.Zip(statuses, (order, status) => new { Order = order, Status = status });
foreach (var os in orderStatusList)
Console.WriteLine($"Bestellung {os.Order}: {os.Status}");
Bestellung A-1: Erledigt
Bestellung B-2: In Bearbeitung
Bestellung C-3: Abgelehnt
4. Wichtige Besonderheiten und typische Fehler
Wenn du Zip benutzt, denk dran: Die Ergebnis-Collection ist so lang wie die kürzeste der Eingaben.
Wenn du z.B. 10 Studenten hast, aber nur 7 Bewertungen, enthält das Ergebnis nur 7 Elemente. Das ist ein häufiger Grund für unerwartete „Datenverluste“.
Fehler Nr. 1: Unterschiedliche Längen oder Reihenfolge der Collections.
Wenn du versehentlich die Reihenfolge der Listen vertauschst oder vergisst, sie vorher so zu sortieren, dass die Elemente zueinander passen, ist das Ergebnis falsch.
Stell dir vor: Die Studentenliste passt nicht zur Reihenfolge der Bewertungen – Vasya bekommt vielleicht Petjas Note. Das ist wie zwei verschiedene Socken anziehen: Jeder ist zwar da, aber das Gefühl ist komisch.
Fehler Nr. 2: Eine der Collections ist leer.
Wenn mindestens eine Liste leer ist, ist das Ergebnis leer.
Zip funktioniert nur, solange in beiden Sequenzen Elemente sind. Wenn eine Quelle keine Daten hat, bekommst du „nichts“.
5. Zip mit mehreren Collections
Ursprünglich konnte LINQ nur zwei Collections kombinieren. Aber ab .NET 6 gibt es eine Überladung, mit der du DREI (ja!) Collections gleichzeitig zippen kannst.
Beispiel mit drei Collections:
string[] names = { "Rey", "Luke", "Leia" };
string[] planets = { "Tatooine", "Tatooine", "Alderaan" };
int[] ages = { 19, 23, 19 };
// Ab .NET 6 gibt's die Überladung:
var characters = names.Zip(planets, ages, (name, planet, age) =>
$"{name} ({planet}), {age} Jahre alt");
foreach (var c in characters)
Console.WriteLine(c);
Rey (Tatooine), 19 Jahre alt
Luke (Tatooine), 23 Jahre alt
Leia (Alderaan), 19 Jahre alt
Die Anzahl der Elemente im Ergebnis – nach der kürzesten Liste! Wenn ages eins weniger hat, geht der letzte Skywalker "ohne Alter".
6. Zip und LINQ Query Syntax: Gibt's das?
Du hast vielleicht gemerkt, dass wir immer die "Methoden"-Syntax benutzen. In der LINQ Query Syntax gibt es kein eigenes Wort für Zip, und mit from ... in ... geht das nicht. Der Grund: Das Konzept von "zippen" ist positionsbasiertes Kombinieren, aber LINQ-Querys arbeiten mit join, select usw., meist zum Verknüpfen nach Schlüssel, nicht nach Position.
Fazit: Für Zip nehmen wir ausschließlich die Punkt- (also Methoden-)Syntax:
var zipped = collection1.Zip(collection2, (a, b) => ...);
Wenn du unbedingt Query-Syntax willst – lass es lieber. Und wenn du es wirklich unbedingt willst, kannst du dir einen eigenen Extension-Method schreiben. 🙂
7. Zip in echten Anwendungen
Beispiel aus deiner Praxis: Alte und neue Preise vergleichen
Angenommen, in unserer Lern-App gibt es eine Produktliste und dazu kommen regelmäßig neue Preise rein. Wir wollen ausgeben, wie sich der Preis jedes Produkts geändert hat:
string[] productNames = { "Apfel", "Banane", "Ananas" };
decimal[] oldPrices = { 80.0m, 30.0m, 110.0m };
decimal[] newPrices = { 75.0m, 33.0m, 120.0m };
var priceChanges = productNames
.Zip(oldPrices, newPrices, (name, oldPrice, newPrice) =>
$"{name}: war {oldPrice}, jetzt {newPrice}, Änderung: {newPrice - oldPrice:+#;-#;0}");
foreach (var s in priceChanges)
Console.WriteLine(s);
Apfel: war 80, jetzt 75, Änderung: -5
Banane: war 30, jetzt 33, Änderung: +3
Ananas: war 110, jetzt 120, Änderung: +10
Praxis: Ergebnisse aus verschiedenen Quellen zusammenführen
In „echten“ Projekten wird Zip oft genutzt, um Ergebnisse aus verschiedenen APIs oder Tabellen zu kombinieren, wenn die Reihenfolge der Daten von außen garantiert ist (z.B. Arrays mit Wetterprognosen und echten Messwerten).
8. Zip in LINQ-Chains: Kombinieren mit anderen Operatoren
Oft wird Zip nicht einzeln, sondern in langen LINQ-Chains verwendet. Zum Beispiel kann man Listen erst filtern, dann zippen und danach eine Statistik berechnen.
var passedMath = mathScores.Where(x => x >= 3);
var passedLit = literatureScores.Where(x => x >= 3);
var passedPairs = passedMath.Zip(passedLit, (m, l) => m + l);
var avgScore = passedPairs.Average();
Console.WriteLine($"Durchschnittliche Gesamtpunktzahl der Bestanden: {avgScore}");
Das Ding ist: Wenn nach Where eine Liste kürzer ist, schneidet Zip das Ergebnis auf die kürzeste zu.
Vergleich von Zip und anderen Kombinationsmethoden
| Methode | Essenz | Kombiniert nach… | Ergebnis-Länge… |
|---|---|---|---|
|
Kombiniert Elemente nach Index | Index (Position) | Kürzeste Collection |
|
Kombiniert nach Schlüssel | Gemeinsamer Schlüssel | Alle passenden Paare |
|
Für jeden Schlüssel eine Collection | Schlüssel | Nach erster Collection |
|
Hängt Collections hintereinander | Einfach ans Ende | Summe der Längen |
Zip – das ist positionsbasiertes Matching. Join – das ist Matching nach einem gemeinsamen Wert (z.B. Code oder id).
9. Praktische Tipps und typische Fehler
Wenn du Zip in deiner App verwendest, dann prüfe vor allem:
- Die Reihenfolge der Elemente in beiden Collections stimmt: Sonst „matchst“ du Vasya mit Petjas Noten.
- Collections sind korrekt sortiert oder vorher für synchrones Durchgehen vorbereitet.
- Das Ergebnis hat immer die Länge der kürzesten Collection.
- Benutze Zip nicht, wenn du nach einem Schlüssel matchen willst! Dafür gibt's Join.
- Noch ein häufiger Fehler: Du hast in einer Collection Duplikate, in der anderen nicht. Oder du hast beim Filtern die Reihenfolge geändert und das Matching verloren.
GO TO FULL VERSION