CodeGym /Kurse /C# SELF /Datenprojektion mit Select...

Datenprojektion mit Select

C# SELF
Level 31 , Lektion 3
Verfügbar

1. Einführung

Wenn du eine Serie schaust, bekommst du auch nicht immer alle Infos über alle Charaktere, sondern nur das, was für die Story wichtig ist. In der Programmierung gibt’s oft ähnliche Situationen: Wir brauchen nicht das ganze Objekt, sondern nur einzelne Felder, Werte oder sogar ein paar Berechnungen darauf.

Projektion in LINQ bedeutet, die Elemente einer ursprünglichen Sequenz in eine neue Form zu bringen. Meistens mit der Select-Methode. Du kannst dir Select wie eine Zaubermaschine vorstellen, die jedes Element nimmt, eine Funktion darauf anwendet und das Ergebnis zurückgibt. Das war’s, keine Magie.

Wann braucht man Projektion?

  • Wir wollen nur die Namen der User anzeigen, nicht alle Daten über sie.
  • Wir bereiten einen E-Mail-Newsletter vor: Aus dem Kundenobjekt nehmen wir nur Adresse und Name.
  • Wir machen eine Berechnung: Zum Produktpreis kommt die Steuer dazu und wir bekommen eine neue Collection.
  • Fürs UI: Es sollen nur ein paar Daten angezeigt werden, damit das Interface nicht überladen wird.

2. Die Select-Methode: Syntax und Basics

Grundsyntax

LINQ unterstützt zwei Stile: Methodensyntax (Method Syntax) und Abfragesyntax (Query Syntax). Für Select ist das Ergebnis bei beiden gleich.

Methodensyntax

var query = myCollection.Select(x => x.WasZurückgeben);

Hier ist x die Variable für jedes Element der Collection, und rechts steht der Ausdruck, der das Element in das verwandelt, was du brauchst.

Abfragesyntax

var query = from x in myCollection
            select x.WasZurückgeben;

Hier sieht’s ein bisschen mehr nach SQL aus.

Einfaches Beispiel: Liste von Zahlen

var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Ich will ihre Quadrate bekommen
var squares = numbers.Select(n => n * n);

foreach (var s in squares)
{
    Console.WriteLine(s); // 1, 4, 9, 16, 25
}

Gerade eben haben wir eine Collection von Zahlen in eine Collection ihrer Quadrate verwandelt – sofort, ohne eine manuelle Schleife!

Beispiel 2

In den früheren Beispielen hatten wir schon eine Klasse Product:

public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

Angenommen, wir haben eine Liste:

var products = new List<Product>
{
    new Product { Name = "Schokolade", Price = 120 },
    new Product { Name = "Käse", Price = 250 },
    new Product { Name = "Brot", Price = 55 }
};

Wenn wir einfach nur die Namen aller Produkte haben wollen, brauchen wir nicht das ganze Objekt, oder? Wir brauchen nur die Liste der Namen:

var names = products.Select(p => p.Name);
foreach (var name in names)
{
    Console.WriteLine(name); // Schokolade, Käse, Brot
}

3. Rückgabe-Collection – welche Varianten gibt’s?

Beachte, dass Select immer eine Collection zurückgibt (genauer gesagt – IEnumerable<TOutput>), wobei TOutput der Typ des Funktions-Ergebnisses ist. Du bestimmst selbst, was das ist: String, Zahl, anonymer Typ, sogar ein neues Objekt.

Projektion in einen neuen Typ

Zum Beispiel willst du nicht das rohe Objekt, sondern seine "Projektion":

var projections = products.Select(p => new { p.Name, PreisMitMwSt = p.Price * 1.2 });

foreach (var item in projections)
{
    Console.WriteLine($"{item.Name}: {item.PreisMitMwSt}");
}

Hier haben wir eine neue Collection von anonymen Objekten erstellt – mit Name und Preis inklusive imaginärer Mehrwertsteuer!

4. Rückgabe eines neuen Klassentyps oder anonymen Typs

Du kannst wählen: Entweder du erstellst richtige Klassen für das Ergebnis oder du bleibst bei anonymen Typen, wenn die Struktur nur "hier und jetzt" gebraucht wird.

Anonyme Typen

var result = products.Select(p => new { Bezeichnung = p.Name, AlterPreis = p.Price, NeuerPreis = p.Price + 40 });
foreach (var p in result)
{
    Console.WriteLine($"{p.Bezeichnung}: War {p.AlterPreis}, ist jetzt {p.NeuerPreis}");
}

Anonyme Typen sind praktisch, um schnell Ergebnisse zu formen, besonders wenn es keinen Sinn macht, für eine einzige Operation extra Klassen zu bauen.

Eigenen Klassentyp für Projektion nutzen

Wir erstellen eine neue Klasse:

public class ProductDto
{
    public string Name { get; set; }
    public double PriceWithTax { get; set; }
}

Jetzt im LINQ-Query:

var productsWithTax = products.Select(p => new ProductDto
{
    Name = p.Name,
    PriceWithTax = p.Price * 1.2
});

foreach (var p in productsWithTax)
{
    Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}

5. Zugriff auf innere Collections und Properties

Was, wenn im Objekt eine Collection steckt? Zum Beispiel hat jeder User eine Liste von Bestellungen.

public class User
{
    public string Name { get; set; }
    public List<Product> Purchases { get; set; }
}

Du willst die Liste aller Einkaufslists bekommen? Kein Problem!

var allPurchases = users.Select(u => u.Purchases);

foreach (var purchaseList in allPurchases)
{
    foreach (var product in purchaseList)
    {
        Console.WriteLine(product.Name);
    }
}

Wichtig! In diesem Fall haben wir keine "flache" Liste von Produkten, sondern eine Collection von Collections. Wie bekommt man EINE Liste mit allen Produkten aller User? Das ist Thema der nächsten Vorlesung – da lernen wir SelectMany kennen.

6. Nützliche Feinheiten

Umwandlung in Collection-Typ: ToList() und Co.

Select gibt ein IEnumerable<T> zurück. Wenn du eine normale Liste (List<T>) willst, häng einfach .ToList() dran:

var nameList = products.Select(p => p.Name).ToList();

Jetzt hast du eine ganz normale Liste, mit der du wie gewohnt arbeiten kannst.

Den Index des Elements nutzen

Manchmal ist es praktisch zu wissen, das wievielte Element in der Collection gerade bearbeitet wird. Die Select-Methode hat eine Überladung:

var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
    Console.WriteLine($"{item.Index}: {item.Name}");
}

So kann man zum Beispiel Daten für nummerierte Listen vorbereiten.

Projektion in der Programmierung

Fit in Select zu sein, ist eine häufige Frage im Vorstellungsgespräch. Ohne diese Konstruktion kann man sich moderne Anwendungen kaum vorstellen:

  • Nur die nötigen Daten aus der DB oder externen Services holen.
  • Daten für die Übergabe zwischen App-Schichten vorbereiten.
  • Daten ins passende Format bringen, um sie auf dem Screen anzuzeigen oder übers Netz zu schicken.
  • Speicher sparen (wir schleppen nicht das ganze Objekt mit, sondern nur die Felder, die wir brauchen).

Firmen, die mit großen Datenmengen arbeiten, bauen darauf oft ihre ganze Business-Logik auf.

7. Typische Fehler und Besonderheiten

Manchmal stolpern Einsteiger über nervige Bugs – meistens, weil sie nicht ganz checken, wie Lazy Evaluation (deferred execution) in LINQ funktioniert. Zum Beispiel: Nach dem Aufruf von Select wird die Query nicht sofort ausgeführt – erst, wenn du wirklich über die Collection iterierst (foreach, ToList(), usw.). Das erlaubt dir, eine "Verarbeitungspipeline" zu bauen, führt aber manchmal dazu, dass sich das Ergebnis ändert, wenn die Ursprungsdaten vor der Nutzung des Query-Ergebnisses geändert wurden.

Vergiss auch nicht: Select ändert nicht die Ursprungs-Collection – es baut eine neue. Wenn du also erwartest, in products neue Felder nach products.Select(...) zu finden, wird das nicht passieren.

Null-Behandlung: Wenn die Projektion vorsieht, dass du auf potenziell null-Felder zugreifst, denk dran, das abzusichern. In C# 8+ gibt’s einen Compiler-Warning, wenn du dir einen NullReferenceException einfangen könntest.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION