Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Über den Autor
Vorwort zur 4. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign und Vererbung
4 Weitere .NET-Datentypen
5 Weitere Möglichkeiten von C#
6 Projektmanagement und Visual Studio 2008
7 Fehlerbehandlung und Debugging
8 LINQ
9 Multithreading und asynchrone Methodenaufrufe
10 Arbeiten mit Dateien und Streams
11 Serialisierung
12 Einige wichtige .NET-Klassen
13 Grundlagen zum Erstellen einer Windows-Anwendung
14 Die wichtigsten Steuerelemente
15 Tastatur- und Mausereignisse
16 MDI-Anwendungen
17 Grafische Programmierung mit GDI+
18 Das Drucken (Printing)
19 Steuerelemente entwickeln
20 Programmiertechniken
21 WPF – die Grundlagen
22 Die Layoutcontainer
23 Die WPF-Controls
24 Konzepte von WPF
25 ADO.NET – die Verbindung zu einer Datenbank herstellen
26 Die Datenbankabfrage
27 Der SqlDataAdapter
28 Daten im lokalen Speicher – das DataSet
29 Eine Datenbank aktualisieren
30 Stark typisierte DataSets
31 Weitergabe von Anwendungen
Stichwort

Download:
- ZIP, ca. 13,6 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2008 von Andreas Kuehnel
Das umfassende Handbuch
Buch: Visual C# 2008

Visual C# 2008
geb., mit DVD
1.366 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1172-7
Pfeil 8 LINQ
Pfeil 8.1 Was ist LINQ?
Pfeil 8.2 Neue Sprachfeatures in C# 2008
Pfeil 8.2.1 Implizit typisierte Variablen (Typinferenz)
Pfeil 8.2.2 Lambda-Ausdrücke
Pfeil 8.2.3 Erweiterungsmethoden
Pfeil 8.2.4 Anonyme Typen
Pfeil 8.2.5 C# 3.0 und LINQ-Abfragen
Pfeil 8.3 LINQ to Objects
Pfeil 8.3.1 Musterdaten
Pfeil 8.3.2 Die Abfrage-Syntax
Pfeil 8.3.3 Übersicht über die Abfrageoperatoren
Pfeil 8.3.4 Die »from«-Klausel
Pfeil 8.3.5 Der Restriktionsoperator »where«
Pfeil 8.3.6 Die Projektionsoperatoren
Pfeil 8.3.7 Sortieroperatoren
Pfeil 8.3.8 Gruppieren mit »GroupBy«
Pfeil 8.3.9 Verknüpfungen mit »Join«
Pfeil 8.3.10 Die Set-Operatoren-Familie
Pfeil 8.3.11 Die Familie der Aggregatoperatoren
Pfeil 8.3.12 Generierungsoperatoren
Pfeil 8.3.13 Quantifizierungsoperatoren
Pfeil 8.3.14 Aufteilungsoperatoren
Pfeil 8.3.15 Die Elementoperatoren


Galileo Computing - Zum Seitenanfang

8.3 LINQ to Objects Zur nächsten ÜberschriftZur vorigen Überschrift

Visual C# 3.0 ist im Vergleich zu Visual C# 2.0 um zahlreiche neue Sprachfeatures erweitert worden, wie Sie in den letzten Kapiteln gesehen haben. Lambda-Ausdrücke, Erweiterungsmethoden, Typinferenz und anonyme Typen mussten in die .NET-Sprachen integriert werden, um LINQ zu unterstützen und zu dem zu machen, was es ist: eine Architektur, die es gestattet, mit SQL-ähnlicher Syntax Datenquellen abzufragen.

Darüber hinaus wurde die Liste der C#-Schlüsselwörter um einige ergänzt, die an dieser Stelle erwähnt werden sollten. Wie Sie mit ihnen umgehen, werden Sie im weiteren Verlauf des Kapitels erfahren.


Tabelle 8.1 Liste der LINQ-spezifischen C#-Schlüsselwörter

from

where

into

orderby

select

group

join

let



Galileo Computing - Zum Seitenanfang

8.3.1 Musterdaten Zur nächsten ÜberschriftZur vorigen Überschrift

Wir werden uns in diesem Abschnitt auf LINQ to Objects konzentrieren und uns weniger mit den spezifischen LINQ-Implementierungen befassen. Dabei werden wir die wichtigsten Klassen und Operatoren untersuchen.

Ehe wir uns mit LINQ beschäftigen, müssen wir uns eine angemessene Datenquelle beschaffen. Die meisten Beispiele in diesem Kapitel arbeiten mit den Daten, die von einer Klassenbibliothek bereitgestellt werden. Sie finden das Projekt auf der Buch-DVD unter \Beispiele\Kapitel 8\Musterdaten.

In der Anwendung sind die vier Klassen Customer, Product, Order und Service sowie die Enumeration Cities definiert. Bis auf Service sind alle sehr einfach gehalten.

public class Order { 
  public int OrderID; 
  public int ProductID; 
  public int Quantity; 
  public bool Shipped; 
}

public class Customer { 
  public string Name; 
  public Cities City; 
  public Order[] Orders; 
}

public class Product { 
  public int ProductID; 
  public string ProductName; 
  public double Price; 
}

public enum Cities { 
  Aachen, 
  Bonn, 
  Köln 
}

In der Klasse Service werden drei Arrays definiert, die mehrere Produkte, Kunden und Bestellungen beschreiben. Beachten Sie bitte, dass die einzelnen Bestellungen den Kunden direkt in einem Feld zugeordnet werden. Zudem sind in Service drei Methoden implementiert, die als Datenlieferant entweder die Liste der Kunden, der Bestellungen oder der Produkte zurückliefern. Sämtliche Klassenmitglieder sind static.

public class Service { 
  public static Product[] GetProducts() {return product; } 
  public static Customer[] GetCustomers() {return customers; } 
  public static Order[] GetOrders() { return orders; }

  public static Product[] product = new Product[]{ 
    new Product{ ProductID = 1, ProductName = "Käse", Price = 10}, 
    new Product{ ProductID = 2, ProductName = "Wurst", Price = 5}, 
    new Product{ ProductID = 3, ProductName = "Obst", Price = 8.56}, 
    new Product{ ProductID = 4, ProductName = "Gemüse", Price = 4}, 
    new Product{ ProductID = 5, ProductName = "Fleisch", Price = 17.5}, 
    new Product{ ProductID = 6, ProductName = "Süßwaren", Price = 3}, 
    new Product{ ProductID = 7, ProductName = "Bier", Price = 2.8}, 
    new Product{ ProductID = 8, ProductName = "Pizza", Price = 7} 
  };

  public static Order[] orders = new Order[] { 
    new Order{ OrderID= 1, ProductID = 4, 
               Quantity = 2, Shipped = true}, 
    new Order{ OrderID= 2, ProductID = 1, 
               Quantity = 1, Shipped = true}, 
    new Order{ OrderID= 3, ProductID = 5, 
               Quantity = 4, Shipped = false}, 
    new Order{ OrderID= 4, ProductID = 4, 
               Quantity = 5, Shipped = true}, 
    new Order{ OrderID= 5, ProductID = 8, 
               Quantity = 6, Shipped = true}, 
    new Order{ OrderID= 6, ProductID = 3, 
               Quantity = 3, Shipped = false}, 
    new Order{ OrderID= 7, ProductID = 7, 
               Quantity = 2, Shipped = true}, 
    new Order{ OrderID= 8, ProductID = 8, 
               Quantity = 1, Shipped = false}, 
    new Order{ OrderID= 9, ProductID = 4, 
               Quantity = 1, Shipped = false}, 
    new Order{ OrderID= 10, ProductID = 1, 
               Quantity = 8, Shipped = true}, 
    new Order{ OrderID= 11, ProductID = 3, 
               Quantity = 3, Shipped = true}, 
    new Order{ OrderID= 12, ProductID = 6, 
               Quantity = 6, Shipped = true}, 
    new Order{ OrderID= 13, ProductID = 1, 
               Quantity = 4, Shipped = false}, 
    new Order{ OrderID= 14, ProductID = 6, 
               Quantity = 3, Shipped = true}, 
    new Order{ OrderID= 15, ProductID = 5, 
               Quantity = 7, Shipped = true}, 
 
    new Order{ OrderID= 16, ProductID = 1, 
               Quantity = 9, Shipped = true} 
  };

  public static Customer[] customers = new Customer[]{ 
      new Customer{ Name = "Herbert", City = Cities.Aachen, 
              Orders = new Order[]{orders[3], orders[2], 
                                   orders[8], orders[10] } }, 
      new Customer{ Name = "Willi", City = Cities.Köln, 
              Orders = new Order[]{orders[6], orders[7], 
                                   orders[9] } }, 
      new Customer{ Name = "Hans", City = Cities.Bonn, 
              Orders = new Order[]{orders[4], orders[11], 
                                   orders[14] } }, 
      new Customer{ Name = "Freddy", City = Cities.Bonn, 
              Orders = new Order[]{orders[1], orders[5], 
                                   orders[13] } }, 
      new Customer{ Name = "Theo", City = Cities.Aachen, 
              Orders = new Order[]{orders[15], orders[12] } } 
  }; 
}

Sollten Sie selbst in einem eigenen Projekt mit den Daten experimentieren, müssen Sie die Assembly Musterdaten.dll unter Verweise in das Projekt einbinden. Zudem sollten Sie auch den Namespace Musterdaten mit using importieren.


Galileo Computing - Zum Seitenanfang

8.3.2 Die Abfrage-Syntax Zur nächsten ÜberschriftZur vorigen Überschrift

LINQ ähnelt SQL. Mit from wird die Datenquelle angegeben, mit where das Filterkriterium, und mit select legen Sie fest, welche Daten zurückgegeben werden. Dazu gesellt sich eine große Zahl weiterer Operatoren, die weitere Operationen auf den abzufragenden Daten ausführen.

Eine LINQ-Abfrage beginnt mit from und nicht wie unter SQL mit select. Der Grund dafür ist, dass zuerst die Datenquelle ausgewählt sein muss, auf der alle nachfolgenden Operationen ausgeführt werden. Dies gestattet es uns darüber hinaus, mit der IntelliSense-Liste im Codeeditor zu arbeiten.

Mit der folgenden LINQ-Abfrage können wir uns die Kundennamen mit einer Länge von weniger als sechs Zeichen sortiert ausgeben lassen:

Customer[] customers = Service.GetCustomers(); 
var cust = from customer in customers 
           where customer.Name.Length < 6 
           select customer.Name;

foreach (var p in cust) 
  Console.WriteLine(p);

Beachten Sie, dass eine LINQ-Abfrage mit einem Semikolon abgeschlossen wird. In der Anweisung

from customer in customers

wird die Liste customers durchlaufen. Die einzelnen Einträge werden danach mit where nach dem vorgegebenen Kriterium selektiert. select gibt an, welche Elemente eines Customer-Objekts in die Ergebnisliste eingetragen werden. In unserem Code ist es nur der Name des Kunden. Das Ergebnis wird einer implizit typisierten Variablen zugewiesen. Diese Anweisung könnte auch durch

IEnumerable<string> cust = from customer in customers

ersetzt werden, da eine LINQ-Abfrage Collections vom Typ IEnumerable<T> und IEnumerable abfragt.

In LINQ werden grundsätzlich zwei Schreibweisen unterschieden:

  • Abfrage-Syntax (Query Expression-Syntax)
  • Erweiterungsmethoden-Syntax (Extension Method-Syntax)

Ich habe Ihnen die Abfrage-Syntax gezeigt. Wie Sie sehen, kommen dabei die neuen Schlüsselwörter from, where und select von C# zum Einsatz.

Nun möchte ich Ihnen auch die gleiche Abfrage in der Erweiterungsmethoden-Syntax vorstellen. Diese ist zwar schwerer zu lesen, gestattet Ihnen aber, die volle Leistungsfähigkeit von LINQ auszuschöpfen. Unser Beispiel von oben würde in dieser Schreibweise folgendermaßen geschrieben:

var cust = customers 
           .Where(customer => customer.Name.Length < 6) 
           .Select(c => c.Name);

Sie können sehr schön erkennen, wie bei dieser Schreibweise mit Where und Select tatsächlich Erweiterungsmethoden zum Einsatz kommen, denen Lambda-Ausdrücke übergeben werden.

Nicht alle Abfrageausdrücke lassen sich in der Schreibweise der Abfrage-Syntax ausdrücken. In einigen Fällen kommen Sie an der Erweiterungsmethoden-Syntax nicht vorbei. Sie können beide Schreibweisen mischen. In jedem Fall wandelt der Compiler die Abfrage in die Erweiterungsmethoden-Syntax um.


Galileo Computing - Zum Seitenanfang

8.3.3 Übersicht über die Abfrageoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

LINQ stellt Ihnen zahlreiche Abfrageoperatoren zur Verfügung. Alle sind in der Klasse Enumerable im Namespace System.Linq definiert. Die Abfrageoperatoren sind als Erweiterungsmethoden implementiert, die für Typen gelten, die die Schnittstelle IEnumerable<T> implementieren.

In Tabelle 8.2 sind alle LINQ-Abfrageoperatoren angegeben.


Tabelle 8.2 Die LINQ-Abfrageoperatoren

Operatortyp Operator

Aggregatoperatoren

Aggregate, Average, Count, LongCount, Min, Max, Sum

Castingoperatoren

Cast, OfType, ToArray, ToDictionary, ToList, ToLookup, ToSequence

Elementoperatoren

DefaultIfEmpty, ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault

Gleichheitsoperatoren

EqualAll

Sequenzoperatoren

Empty, Range, Repeat

Gruppierungsoperatoren

GroupBy

Join-Operatoren

Join, GroupJoin

Sortieroperatoren

OrderBy, ThenBy, OrderByDescending, ThenByDescending, Reverse

Aufteilungsoperatoren

Skip, SkipWhile, Take, TakeWhile

Quantifizierungsoperatoren

All, Any, Contains

Restriktionsoperatoren

Where

Projektionsoperatoren

Select, SelectMany

Set-Operatoren

Concat, Distinct, Except, Intersect, Union


Wir werden im weiteren Verlauf des Kapitels auf die meisten der hier aufgeführten LINQ-Operatoren genauer eingehen.


Galileo Computing - Zum Seitenanfang

8.3.4 Die »from«-Klausel Zur nächsten ÜberschriftZur vorigen Überschrift

Ein Abfrageausdruck beginnt mit der from-Klausel. Diese beschreibt, welche Datenquelle abgefragt werden soll, und definiert eine lokale Bereichsvariable, die jedes Element in der Datenquelle repräsentiert. Die Datenquelle muss entweder die Schnittstelle IEnumerable<T> oder IEnumerable implementieren. Zu den abfragbaren Datenquellen zählen auch diejenigen, die sich auf IQueryable<T> zurückführen lassen.

Datenquelle und Bereichsvariable sind streng typisiert. Wenn Sie mit

from customer in customers

das Array aller Kunden als Datenquelle angeben, ist die Bereichsvariable vom Typ Customer.

Etwas anders ist der Sachverhalt, wenn die Datenquelle beispielsweise vom Typ ArrayList ist. Wie Sie wissen, können in einer ArrayList Objekte unterschiedlichsten Typs verwaltet werden. Um auch solche Datenquellen abfragen zu können, muss die Bereichsvariable explizit typisiert werden, z. B.:

ArrayList arr = new ArrayList(); 
arr.Add(new Circle()); 
arr.Add(new Circle());

var cust = from Circle kreis in arr 
           select kreis;

Manchmal kommt es vor, dass jedes Element einer Datenquelle seinerseits selbst eine Liste untergeordneter Elemente beschreibt. Ein gutes Beispiel dafür ist in unserer Anwendung zu finden, die die Musterdaten bereitstellt.

public class Customer { 
  public string Name; 
  public Cities City; 
  public Order[] Orders; 
}

Jedem Kunden ist ein Array vom Typ Order zugeordnet. Um die Bestellungen abzufragen, muss eine weitere from-Klausel angeführt werden, die auf die Bestellliste des jeweiligen Kunden zugreift. Jede from-Klausel kann separat mit where gefiltert oder mit orderby sortiert werden.

var p = from cust in customers 
        where cust.Name == "Hans" 
           from order in cust.Orders 
           where order.Quantity > 6 
           select order.OrderID;

In diesem Codefragment wird die Liste aller Kunden zuerst nach Hans durchsucht. Die gefundene Dateninformation extrahiert anschließend die Bestellinformationen und beschränkt das Ergebnis auf alle Bestellungen von Hans, die eine Bestellmenge > 6 haben.


Galileo Computing - Zum Seitenanfang

8.3.5 Der Restriktionsoperator »where« Zur nächsten ÜberschriftZur vorigen Überschrift

Angenommen, Sie möchten alle Kunden auflisten, deren Wohnort Aachen ist. Um eine Folge von Elementen zu filtern, verwenden Sie den Where-Operator.

Customer[] customers = Service.GetCustomers();

var result = from cust in customers 
             where cust.City == Cities.Aachen 
             select cust.Name; 
foreach (var p in result) 
  Console.WriteLine(p);

Mit dem Select-Operator geben Sie das Element an, das in die Ergebnisliste aufgenommen werden soll. In diesem Fall ist das der Name jeder entsprechend durch den Where-Operator gefundenen Person. Die Ergebnisliste wird in der foreach-Schleife durchlaufen und an der Konsole ausgegeben. Sie werden Herbert und Theo in der Ergebnisliste finden.

Sie können die Abfrage-Syntax auch durch die Erweiterungsmethoden-Syntax ersetzen. Geben Sie dabei direkt das zu durchlaufende Array an. An der Codierung der Konsolenausgabe ändert sich nichts.

var result = customers 
             .Where( cust => cust.City == Cities.Aachen) 
             .Select(cust => cust.Name);

Um aus einem Customer-Objekt für die Ergebnisliste mehrere spezifische Daten zu filtern, übergeben Sie dem Select-Operator einen anonymen Typ, der sich aus den gewünschten Elementen zusammensetzt. Interessiert Sie beispielsweise neben dem Namen auch das Alter der gefundenen Person in der Ergebnisliste, sieht der Code der LINQ-Abfrage wie folgt aus:

var result = from cust in customers 
             where cust.City == Cities.Aachen 
             select new { cust.Name, cust.City };

foreach (var p in result) 
  Console.WriteLine("{0,-7}{1}", p.Name, p.City);

Die Ergebnisliste setzt sich fortan aus den objektspezifischen Elementen Alter und Name zusammen und muss bei der Ausgabe beachtet werden.

Mehrere Filterkriterien zu berücksichtigen ist nicht weiter schwierig. Sie müssen nur den Where-Operator ergänzen. Dazu benutzen Sie die C#-spezifischen Operatoren. Im nächsten Codefragment werden alle noch nicht ausgelieferten Bestellungen gesucht, deren Bestellmenge größer 3 ist.

Order[] orders = Service.GetOrders(); 
var result = from order in orders 
             where order.Quantity > 3 && order.Shipped == false 
             select order.OrderID;

oder:

var result = orders 
             .Where(order => order.Quantity > 3 && 
                    order.Shipped == false) 
             .Select(ord => ord.OrderID);

Die Überladungen des Where-Operators

Wenn Sie sich die .NET-Dokumentation des Where-Operators ansehen, finden Sie die beiden folgenden Signaturen:

public static IEnumerable<T> Where<T>( 
       this IEnumerable<T> source, 
       Func<T, bool> predicate

public static IEnumerable<T> Where<T>( 
       this IEnumerable<T> source, 
       Func<T, int, bool> predicate

Die erste wird für Abfragen verwendet, wie wir sie weiter oben eingesetzt haben. Die IEnumerable<T>-Collection wird dabei komplett gemäß den Filterkriterien durchsucht.

Mit der zweiten Signatur können Sie den Bereich der Ergebnisliste einschränken, und zwar anhand des nullbasierten Index, der als Integer angegeben wird. Nehmen wir an, Sie interessieren sich für alle Bestellungen, deren Bestellmenge > 3 ist. Allerdings möchten Sie, dass die Ergebnisliste sich auf Indizes in der Datenquelle beschränkt, die < 10 sind. Es werden demnach nur die Indizes 0–9 berücksichtigt.

var result = orders 
             .Where((order, index) => order.Quantity > 3 && 
                                      index < 10) 
             .Select(ord => ord.OrderID);

Hier müssen Sie die Schreibweise der Erweiterungsmethoden-Syntax einsetzen, um der überladenen Erweiterungsmethode Where die erforderlichen Argumente übergeben zu können.

Wie funktioniert der »Where«-Operator?

Betrachten wir noch einmal die folgende Anweisung:

var result = customers 
             .Where( cust => cust.City == Cities.Aachen)

Where ist eine Erweiterungsmethode der Schnittstelle IEnumerable<T> und gilt auch für das Array vom Typ Customer. Der Ausdruck

cust => cust.City == Cities.Aachen

ist ein Lambda-Ausdruck, im eigentlichen Sinne demnach also das Delegate auf eine anonyme Methode. In der Definition des Where-Operators wird dieses Delegate durch das Delegate

Func<T, bool> predicate

beschrieben (siehe Definition von Where oben). Der generische Typparameter T wird durch die Elemente in der zugrunde liegenden Collection beschrieben, die bekanntlich die Schnittstelle IEnumenerable<T> implementiert. In unserem Beispiel handelt es sich um Customer-Objekte. Daher können wir bei korrekter Codierung innerhalb des Lambda-Ausdrucks auch auf die IntelliSense-Liste zurückgreifen. Der zweite Parameter teilt uns mit, von welchem Datentyp der Rückgabewert des Lambda-Ausdrucks ist. Hier wird ein boolescher Typ vorgegeben, denn über true weiß LINQ, dass auf das untersuchte Element das Suchkriterium zutrifft und bei einer Rückgabe von false eben nicht.

Das Zusammenspiel zwischen den neuen Lambda-Ausdrücken und Erweiterungsmethoden im Kontext generischer Typen und Delegates ist hier sehr gut zu erkennen. In ähnlicher Weise funktionieren auch viele andere Operatoren. Ich werde daher im Folgenden nicht jedes Mal erneut das komplexe Zusammenspiel der verschiedenen Sprachkomponenten erörtern.


Galileo Computing - Zum Seitenanfang

8.3.6 Die Projektionsoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Der »Select«-Operator

Der Select-Operator macht die Ergebnisse der Abfrage über ein Objekt verfügbar, das die Schnittstelle IEnumerable<T> implementiert, z. B.:

var result = from order in orders 
             select order.OrderID;

oder alternativ:

var result = orders.Select(order => order.OrderID);

Die Rückgabe ist in beiden Fällen eine Liste mit den Bestellnummern der in der Collection vertretenen Bestellungen.

Liefert der Select-Operator eine Liste mit neu strukturierten Datenzeilen, müssen Sie einen anonymen Typ als Ergebnismenge definieren:

var result = from customer in customers 
             select new { customer.Name, customer.City };

Der Operator »SelectMany«

SelectMany kommt dann sinnvoll zum Einsatz, wenn es sich bei den einzelnen Elementen in einer Elementliste um Arrays handelt, deren Einzelelemente von Interesse sind. In der Anwendung Musterdaten trifft das auf alle Objekte vom Typ Customer zu, weil die Bestellungen in einem Array verwaltet werden.

var result = from cust in customers 
             where cust.Name == "Hans" 
                from order in cust.Orders 
                where order.Quantity > 6 
                select order.OrderID;

Weiter oben hatten wir uns bereits mit Untermengen dieser Art beschäftigt. Allerdings hatte ich Ihnen noch nicht die Erweiterungsmethoden-Syntax vorgestellt. Das soll nun geschehen, denn dazu dient der Operator SelectMany:

var result = customers 
             .Where(cust => cust.Name == "Hans") 
             .SelectMany(cust => cust.Orders) 
             .Where(order => order.Quantity > 6) 
             .Select(order => order.OrderID);

Galileo Computing - Zum Seitenanfang

8.3.7 Sortieroperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Sortieroperatoren ermöglichen eine Sortierung von Elementen in Ausgabefolgen mit einer angegebenen Sortierrichtung.

Mit dem Operator OrderBy können Sie auf- und absteigend sortieren, mit OrderByDescending nur absteigend. Hier sehen Sie ein Beispiel für eine aufsteigende Sortierung. Dabei werden die Bestellmengen aller Bestellungen der Reihe nach in die Ergebnisliste geschrieben.

Order[] orders = Service.GetOrders(); 
var result = from order in orders 
             orderby order.Quantity 
             select new { order.OrderID, order.Quantity }; 
foreach (var temp in result) 
  Console.WriteLine("ID: {0,-3}{1}", temp.OrderID, temp.Quantity);

Sehen wir uns diese LINQ-Abfrage noch in der Erweiterungsmethoden-Syntax an:

var result = orders 
             .OrderBy( order => order.Quantity) 
             .Select(order => new{order.OrderID, order.Quantity});

Durch die Ergänzung von descending lässt sich auch eine absteigende Sortierung erzwingen:

... 
orderby order.Quantity descending 
...

Das folgende Codefragment zeigt, wie Sie mit dem Operator OrderByDescending zum gleichen Ergebnis kommen:

var result = orders 
             .OrderByDescending( order => order.Quantity) 
             .Select(order => new{order.OrderID, order.Quantity});

Wenn Sie mehrere Sortierkriterien festlegen wollen, helfen Ihnen die beiden Operatoren ThenBy und ThenByDescending weiter. Deren Einsatz setzt aber voraus, dass vorher OrderBy oder OrderByDescending verwendet worden sind. Nehmen wir an, die erste Sortierung soll die Bestellmenge berücksichtigen und die zweite, ob die Bestellung bereits ausgeliefert ist. Die Anweisung dazu lautet:

Order[] orders = Service.GetOrders();

var result = orders 
             .OrderBy( order => order.Quantity) 
             .ThenBy ( order => order.Shipped) 
             .Select( order =>  new { order.OrderID, order.Quantity, 
                                      order.Shipped });

foreach (var temp in result) 
  Console.WriteLine("ProductID: {0,-3}Menge:{1,-4} Geliefert:{2}", 
             temp.OrderID, temp.Quantity, temp.Shipped);

Manchmal kann es vorkommen, dass Sie die gesamte Ergebnisliste in umgekehrter Reihenfolge benötigen. Hier kommt der Operator Reverse zum Einsatz, der am Ende auf die Ergebnisliste angewendet wird:

var result = orders 
             .Select(order => new { order.ProductID, order.Quantity }) 
             .Reverse();

Wie Sie wissen, werden einige Abfrageoperatoren als Schlüsselwörter von C# angeboten und gestatten die sogenannte Abfrage-Syntax. Reverse und ThenBy zählen nicht dazu. Möchten Sie die von einer Abfrage-Syntax gelieferte Ergebnismenge umkehren, können Sie sich eines kleinen Tricks bedienen. Sie schließen die Abfrage-Syntax in runde Klammern ein und können darauf den Punktoperator mit folgendem Reverse angeben:

var result = (from order in orders 
              select new { order.ProductID, order.Quantity }) 
             .Reverse();

Galileo Computing - Zum Seitenanfang

8.3.8 Gruppieren mit »GroupBy« Zur nächsten ÜberschriftZur vorigen Überschrift

Manchmal ist es notwendig, Ergebnisse anhand spezifischer Kriterien zu gruppieren. Dazu dient der Operator GroupBy. Machen wir uns das zuerst an einem Beispiel deutlich. Ausgangspunkt sei wieder das Array mit Customer-Objekten.

// ---------------------------------------------------------
// Beispiel: ...\Kapitel 8\GroupBy //----------------------------------------------------------
class Program { 
  static void Main(string[] args) { 
    Customer[] customers = Service.GetCustomers(); 
    var result = customers 
                .GroupBy(cust => cust.City);

    foreach (IGrouping<Cities, Customer> temp in result) { 
      Console.WriteLine(new string('=', 40)); 
      Console.WriteLine("Stadt: {0}", temp.Key); 
      Console.WriteLine(new string('-', 40)); 
      foreach (var item in temp) 
        Console.WriteLine("       {0}", item.Name); 
    } 
 
    Console.ReadKey(); 
  } 
}

Die Ausgabe an der Konsole sehen Sie in Abbildung 8.1.

Abbildung 8.1 Die Ausgabe des Beispielprogramms »GroupBy«

Der Operator GroupBy ist vielfach überladen. Sehen wir uns eine Überladung an:

public static IEnumerable<IGrouping<K,T>> GroupBy<T,K>( 
   this IEnumerable<T> source, Func<T,K> keyselector);

Alle Überladungen geben dabei den Typ IEnumerable<IGrouping<K,T>> zurück. Die Schnittstelle IGrouping<K,T> ist dabei eine spezialisierte Form von IEnumerable<T>.

public interface IGrouping<K,T> : IEnumerable<T> { 
   K key { get; } 
}

Betrachten wir nun die äußere Schleife:

foreach (IGrouping<Cities, Customer> temp in result)

Sie müssen der Schnittstelle IGrouping im ersten Typparameter in unserem Beispiel Cities zuweisen, den Datentyp des Elements, nach dem gruppiert werden soll. Der zweite Typparameter beschreibt den Typ des zu gruppierenden Elements.

Die äußere Schleife beschreibt die einzelnen Gruppen und gibt als Resultat alle die Elemente zurück, die zu der entsprechenden Gruppe gehören. In unserem Beispielcode wird diese Untergruppe in der Variablen item erfasst. In der inneren Schleife werden anschließend alle Elemente von temp durchlaufen und die gewünschten Informationen ausgegeben.

Der GroupBy-Operator kann auch in der Schreibweise der Abfrage-Syntax dargestellt werden.

var result = from customer in customers 
               group customer by customer.City

Galileo Computing - Zum Seitenanfang

8.3.9 Verknüpfungen mit »Join« Zur nächsten ÜberschriftZur vorigen Überschrift

Mit dem Join-Operator definieren Sie Beziehungen zwischen mehreren Auflistungen, ähnlich wie Sie in SQL mit dem gleichnamigen Join-Statement Tabellen miteinander in Beziehung setzen.

In unseren Musterdaten liegen insgesamt 16 Bestellungen vor. Es soll nun für jede Bestellung die Bestellnummer des bestellten Artikels, die Bestellmenge und der Einzelpreis des Artikels ausgegeben werden. Die Listen der Produkte und Bestellungen spielen in diesem Fall eine entscheidende Rolle.

Order[] orders = Service.GetOrders(); 
Product[] products = Service.GetProducts();

var liste = orders 
            .Join(products, 
                  ord => ord.ProductID, 
                  prod => prod.ProductID, 
                  (a, b) => new {a.OrderID, 
                                 a.ProductID, 
                                 b.Price, 
                                 a.Quantity});

foreach(var m in liste) 
  Console.WriteLine("Order: {0,-3} Product: {1} Menge: {2} Preis: {3}", 
   m.OrderID, m.ProductID, m.Quantity, m.Price);

Der Join-Operator ist überladen. In diesem Beispiel haben wir den folgenden benutzt:

public static IEnumerable<V> Join<T, U, V, K>( 
     this Enumerable<T> outer, 
     IEnumerable<U> inner, 
     Func<T, K> outerKeySelector, 
     Func<U, K> innerKeySelector, 
     Func<T, U, V> resultSelector);

Join wird als Erweiterungsmethode der Liste definiert, auf die Join aufgerufen wird. In unserem Beispiel ist es die durch orders beschriebene Liste aller Bestellungen. Die innere Liste wird durch das Argument beschrieben und ist in unserem Beispielcode die Liste aller Produkte products. Als zweites Argument erwartet Join im Parameter outerKeySelector das Schlüsselfeld der äußeren Liste (hier: orders), das im vierten Argument mit dem Schlüsselfeld der inneren Liste (hier: products) in Beziehung gesetzt wird.

Im vierten Argument wird die Ergebnisliste bestimmt. Dazu werden zwei Parameter übergeben: Der erste projiziert ein Element der äußeren Liste, der zweite ein Element der inneren Liste in das Ergebnis der Join-Abfrage.

Beachten Sie, dass in der Definition von Join der generische Typ T die äußere Liste beschreibt und der Typ U die innere. Die Schlüssel (in unserem Beispiel werden dazu die Felder genommen), die die ProductID beschreiben, verstecken sich hinter dem generischen Typ K, die Ergebnisliste hinter V.

Sie können eine Join-Abfrage auch in Abfragesyntax notieren:

var liste = from ord in orders 
            join prod in products 
                 on ord.ProductID equals prod.ProductID 
            select new { ord.OrderID, ord.ProductID, 
                         prod.Price, ord.Quantity};

Die Ergebnisliste sehen Sie in Abbildung 8.2.

Sie sollten darauf achten, dass Sie beim Vergleich links von equals den Schlüssel der äußeren Liste angeben, rechts davon den der inneren. Wenn Sie beide vertauschen, erhalten Sie einen Compilerfehler.

Abbildung 8.2 Resultat der Join-Abfrage

Der Operator »GroupJoin«

Join führt Daten aus der linken und rechten Liste genau dann zusammen, wenn die angegebenen Kriterien alle erfüllt sind. Ist eines oder sind mehrere der Kriterien nicht erfüllt, so entsteht kein Datensatz in der Ergebnismenge. Damit ist der Join-Operator mit dem INNER JOIN-Statement einer SQL-Abfrage vergleichbar.

Wenn Sie ein Äquivalent zu einem LEFT OUTER JOIN oder RIGHT OUTER JOIN suchen, hilft Ihnen der GroupJoin-Operator weiter. Nehmen wir an, Sie möchten wissen, welche Bestellungen für die einzelnen Produkte vorliegen. Sie können die LINQ-Abfrage dann wie folgt definieren:

var liste = products 
           .GroupJoin(customers.SelectMany(cust => cust.Orders), 
            prod => prod.ProductID, 
            ord => ord.ProductID, 
            (a, b) => new { a.ProductID, Orders = b });

foreach (var t in expr) { 
  Console.WriteLine("ProductID: {0}", t.ProductID, t.Orders); 
  foreach (var order in t.Orders) 
    Console.WriteLine("   OrderID: {0}", order.OrderID); 
}

GroupJoin arbeitet sehr ähnlich wie der Join-Operator. Der Unterschied zwischen den beiden Operatoren besteht darin, was in die Ergebnismenge aufgenommen wird. Mit Join sind es nur Daten, deren Schlüssel sowohl in der outer-Liste als auch der inner-Liste vertreten sind. Findet Join in der inner-Liste kein passendes Element, wird das outer-Element nicht in die Ergebnisliste aufgenommen.

Ganz anders ist das Verhalten von GroupJoin. Dieser Operator nimmt auch dann ein Element aus der outer-Liste in die Ergebnisliste auf, wenn keine entsprechenden Daten in inner vorhanden sind. Sie können das sehr schön in Abbildung 8.3 sehen, denn der Artikel mit der ProductID = 2 ist in keiner Bestellung zu finden.

Abbildung 8.3 Ergebnisliste der LINQ-Abfrage mit dem »GroupJoin«-Operator

Sie können den GroupJoin-Operator auch in einem Abfrageausdruck beschreiben. Er wird mit join... into... definiert.

Product[] products = Service.GetProducts(); 
Customer[] customers = Service.GetCustomers();

var liste = from cust in customers 
            from ord in cust.Orders 
            select ord;

var expr = from prod in products 
           join custord in liste 
           on prod.ProductID equals custord.ProductID into allOrders 
           select new { prod.ProductID, Orders = allOrders};

Alle Beispiele dieses Abschnitts finden Sie auf der Buch-DVD unter \Beispiele\Kapitel 8\Join.


Galileo Computing - Zum Seitenanfang

8.3.10 Die Set-Operatoren-Familie Zur nächsten ÜberschriftZur vorigen Überschrift

Der Operator »Distinct«

Vielleicht kennen Sie die Wirkungsweise von DISTINCT bereits von SQL. In LINQ hat der Distinct-Operator die gleiche Aufgabe: Er garantiert, dass in der Ergebnismenge ein Element nicht doppelt auftritt.

string[] cities = new string[]{ 
   "Aachen", "Köln", "Bonn", "Aachen", "Bonn", "Frankfurt"}; 
var liste = (from p in cities 
             select p).Distinct(); 
foreach (string city in liste) 
   Console.WriteLine(city);

Im Array cities kommen die beiden Städte Aachen in Bonn je zweimal vor. Der auf die Ergebnismenge angewendete Distinct-Opertor erkennt dies und sorgt dafür, dass jede Stadt nur einmal angezeigt wird.

Der Operator »Union«

Der Union-Operator verbindet zwei Listen miteinander. Dabei werden doppelte Vorkommen ignoriert.

string[] cities = new string[]{ 
         "Aachen", "Bonn", "Aachen", "Frankfurt"}; 
string[] namen = new string[]{ 
         "Peter", "Willi", "Hans"};

var listeCities = from c in cities 
                  select c; 
var listeNamen  = from n in namen 
                  select n; 
var listeComplete = listeCities.Union(listeNamen);

foreach (var p in listeComplete) 
  Console.WriteLine(p);

In der Ergebnisliste werden der Reihe nach Aachen, Köln, Bonn, Frankfurt, Peter, Willi und Hans erscheinen.

Der Operator »Intersect«

Der Intersect-Operator bildet eine Ergebnisliste aus zwei anderen Listen. In der Ergebnisliste sind aber nur die Elemente enthalten, die in beiden Listen gleichermaßen enthalten sind. Intersect bildet demnach eine Schnittmenge ab.

string[] cities1 = new string[]{ 
        "Aachen", "Köln", "Bonn", "Aachen", "Frankfurt"}; 
string[] cities2 = new string[]{ 
        "Düsseldorf", "Bonn", "Bremen", "Köln"};

var listeCities1 = from c in cities1 
                   select c; 
var listeCities2  = from n in cities2 
                    select n; 
var listeComplete = listeCities1.Intersect(listeCities2);

foreach (var p in listeComplete) 
  Console.WriteLine(p);

Das Ergebnis wird durch die Städte Köln und Bonn gebildet.

Der Operator »Except«

Während Intersect die Gemeinsamkeiten aufspürt, sucht der Operator Except nach allen Elementen, durch die sich die Listen voneinander unterscheiden. Dabei sind nur die Elemente in der Ergebnisliste enthalten, die in der ersten Liste angegeben sind und in der zweiten Liste fehlen.

WennSie in dem Codefragment anstelle von Intersect den Operator Except verwenden, enthält die Ergebnisliste die Orte Aachen und Frankfurt.


Galileo Computing - Zum Seitenanfang

8.3.11 Die Familie der Aggregatoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

LINQ stellt mit Count, LongCount, Sum, Min, Max, Average und Aggregate eine Reihe von Aggregatoperatoren zur Verfügung, um Berechnungen an Quelldaten durchzuführen.

Die Operatoren »Count« und »LongCount«

Sehr einfach einzusetzen sind die beiden Operatoren Count und LongCount. Beide unterscheiden sich dahingehend, dass Count einen int als Typ zurückgibt und LongCount einen long.

Um Count zu testen, wollen wir zuerst wissen, wie viele Bestellungen insgesamt eingegangen sind:

Order[] orders = Service.GetOrders(); 
var anzahl = (from x in orders 
              select x).Count(); 
Console.WriteLine("Anzahl der Bestellungen gesamt = {0}", anzahl);

Alternativ können Sie auch Folgendes formulieren:

var anzahl = orders.Count();

Das Ergebnis lautet 16.

Vielleicht interessiert uns auch, wie viele Bestellungen jeder einzelne Kunde aufgegeben hat. Wir müssen dann den folgenden Code schreiben:

Customer[] customers = Service.GetCustomers(); 
var orderCounts = from c in customers 
                  select new { c.Name, OrderCount = c.Orders.Count() }; 
foreach (var k in orderCounts) 
  Console.WriteLine("{0} - {1}", k.Name, k.OrderCount);

Der Operator »Sum«

Sum ist grundsätzlich zunächst einmal sehr einfach einzusetzen. Der Operator liefert eine Summe als Ergebnis der LINQ-Abfrage. Im folgenden Codefragment wird die Summe aller Integerwerte ermittelt, die das Array bilden. Das Ergebnis lautet 114.

int[] arr = new int[] { 1, 3, 7, 4, 99 }; 
var sumInt = arr.Sum(); 
Console.WriteLine("Integer-Summe = {0}", sumInt);

Das folgende Beispiel ist nicht mehr so einfach. Hier soll der Gesamtbestellwert über alle Produkte für jeden Kunden ermittelt werden.

var allOrders = 
   from cust in customers 
   from ord in cust.Orders 
   join prod in products on ord.ProductID equals prod.ProductID 
   select new { cust.Name, ord.ProductID, 
                OrderAmount = ord.Quantity * prod.Price };

var summe = 
   from cust in customers 
   join ord in allOrders 
   on cust.Name equals ord.Name into custWithOrd 
   select new { cust.Name, TotalSumme = custWithOrd.Sum(s => s.OrderAmount) };

foreach(var s in summe) 
  Console.WriteLine("Name: {0,-7} Bestellsumme: {1}", 
                    s.Name, s.TotalSumme);

Analysieren wir den Code schrittweise, und überlegen wir, was das Resultat des folgenden Abfrageteilausdrucks ist.

var allOrders = 
   from cust in customers 
   from ord in cust.Orders 
   join prod in products on ord.ProductID equals prod.ProductID 
   select new { cust.Name, ord.ProductID, 
                OrderAmount = ord.Quantity * prod.Price };

Zuerst ist es notwendig, die Bestellungen aus jedem Customer-Objekt zu filtern. Danach wird ein Join gebildet, der die ProductIDs aus den einzelnen Bestellungen eines Kunden mit der ProductID aus der Liste der Artikel verbindet. Das Ergebnis ist eine Art Tabelle mit Spalten für den Bestellernamen, die ProductID und die Gesamtsumme für diesen Artikel, die anhand der Bestellmenge gebildet wurde (siehe Abbildung 8.4).

Abbildung 8.4 Bestellwert als Zwischenergebnis

Nun gilt es noch, die Ergebnisliste nach den Kunden zu gruppieren und dann die Gesamtsumme aller Bestellungen zu bilden.

var summe = 
   from cust in customers 
   join ord in allOrders 
   on cust.Name equals ord.Name into custWithOrd 
   select new { cust.Name, 
        TotalSumme = custWithOrd.Sum(s => s.OrderAmount) };

Wir sollten uns daran erinnern, dass der GroupJoin-Operator mit diesen Fähigkeiten ausgestattet ist. Es müssen zuerst die beiden Listen customers und allOrders zusammengeführt werden. Sie können sich das so vorstellen, dass die Gruppierung mit GroupJoin zur Folge hat, dass für jeden Customer eine eigene »Tabelle« erzeugt wird, in der alle seine Bestellungen beschrieben sind. Die Variable s steht hier für ein Gruppenelement, letztendlich also für eine Bestellung. Die Gruppierung nach Customer-Objekten gestattet es uns nun, mit dem Operator Sum den Inhalt der Spalte OrderAmount zu summieren.

Das Resultat der kompletten LINQ-Abfrage sehen Sie in Abbildung 8.5.

Abbildung 8.5 Ergebnis der Abfrage der Gesamtbestellsumme

Die Operatoren »Min«, »Max« und »Average«

Die Aggregatoperatoren Min und Max ermitteln den minimalen bzw. maximalen Wert in einer Datenliste, Average das arithmetische Mittel.

Grundsätzlich ist der Einsatz der Operatoren sehr einfach, wie das folgende Codefragment exemplarisch an Max zeigt:

var max = (from p in products 
           select p.Price).Max();

Das funktioniert aber auch nur, solange numerische Werte als Datenquelle vorliegen. Sie brauchen den Code nur wie folgt leicht zu ändern, um festzustellen, dass nun eine ArgumentException ausgelöst wird.

var max = (from p in products 
           select new { p.Price }).Max();

Die Meldung zu der Exception besagt, dass mindestens ein Typ die IComparable-Schnittstelle implementieren muss. In der ersten funktionsfähigen Version des Codes stand in der Ergebnisliste ein numerischer Wert, der der Forderung entspricht. Im zweiten, fehlerverursachenden Codefragment hingegen wird ein anonymer Typ beschrieben, der mit der geforderten Schnittstelle überhaupt nicht dienen kann.

Diese Lösung der Problematik ist nicht schwer. Die Operatoren sind alle so überladen, dass ihnen einen Wertselektor übergeben werden kann. Mit anderen Worten: Geben Sie das gewünschte Element aus der Liste der Elemente, die den anonymen Typ bilden, als zu bewertenden Ausdruck an.

var max = (from p in products 
           select new { p.Price }) 
           .Max( x => x.Price);

Galileo Computing - Zum Seitenanfang

8.3.12 Generierungsoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Der Operator »Range«

Dieser Operator liefert ausgehend von einem Startwert eine Gruppe von Integerwerten, die aus einem spezifizierten Wertebereich ausgewählt werden. Die Definition des Operators lautet wie folgt:

public static IEnumerable<int> Range(int start, int count);

Bei genauer Betrachtung ist dieser Operator mit einer for-Schleife vergleichbar. Sie übergeben dem ersten Parameter den Startwert und teilen mit dem zweiten Parameter mit, wie oft eine bestimmte Operation ausgeführt werden soll.

Im folgenden Codefragment werden alle Produkte gesucht, deren Preis größer 5 ist. Das ist der definierte Startwert. Unsere Liste wird dabei nur einmal durchlaufen.

Product[] products = Service.GetProducts(); 
var numbers = Enumerable.Range(5, 1) 
              .SelectMany(x => (from prod in products 
              where prod.Price > x 
              select new { prod.ProductName })); 
foreach (var res in numbers) { 
  Console.WriteLine(res); 
}

Keine Frage, wir könnten mit einer einfachen where-Bedingung zum gleichen Resultat kommen. Der Range-Operator ist auch viel besser dazu geeignet, mathematische Operationen zu codieren. Dies demonstriert der folgende Code:

var nums = Enumerable.Range(1, 10).Select(x => 2 * x); 
foreach (var num in nums) 
  Console.WriteLine(num);

Der Operator »Repeat«

Der Repeat-Operator arbeitet ähnlich wie der zuvor besprochene Range-Operator. Repeat gibt eine Gruppe zurück, in der dasselbe Element mehrfach enthalten ist. Die Anzahl der Wiederholungen ist dabei festgelegt.

Auch zu diesem Operator wollen wir uns zunächst die Definition ansehen.

public static IEnumerable<T> Repeat<T>(T element, int count);

Dem ersten Parameter übergeben Sie das Element, das wiederholt werden soll. Dem zweiten Parameter teilen Sie die Anzahl der Wiederholungen mit. Mit

Product[] products = Service.GetProducts(); 
var prods = Enumerable.Repeat((from p in products 
                               select p.ProductName), 3) 
                               .SelectMany(x => x); 
foreach (var p in prods) 
  Console.WriteLine(p);

werden beispielsweise alle Produktnamen dreimal ausgegeben.


Galileo Computing - Zum Seitenanfang

8.3.13 Quantifizierungsoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn Sie beabsichtigen, die Existenz von Elementen in einer Liste anhand von Bedingungen oder definierten Regeln zu überprüfen, helfen die Quantifizierungsoperatoren Ihnen weiter.

Der Operator »Any«

Any ist ein Operator, der ein Prädikat auswertet und einen booleschen Wert zurückliefert. Nehmen wir an, Sie möchten wissen, ob der Kunde Willi auch das Produkt mit der ID = 6 bestellt hat. Any hilft Ihnen dabei, das festzustellen.

bool result = (from cust in customers 
               from ord in cust.Orders 
               where cust.Name == "Willi" 
               select new { ord.ProductID }) 
               .Any(ord => ord.ProductID == 7); 
if (result) 
  Console.WriteLine("ProductID 3 ist enthalten"); 
else 
  Console.WriteLine("ProductID 3 ist nicht enthalten");

Die Elemente werden so lange ausgewertet, bis der Operator auf ein Element stößt, das die Bedingung erfüllt.

Der Operator »All«

Während Any schon true liefert, wenn für ein Element die Bedingung erfüllt ist, liefert der Operator All nur dann true, wenn alle untersuchten Elemente der Bedingung entsprechen.

Möchten Sie beispielsweise feststellen, ob alle Preise der Einzelprodukte > 3 sind, genügt die folgende LINQ-Abfrage:

bool result = (from prod in products 
               select prod).All(p => p.Price > 3);

Galileo Computing - Zum Seitenanfang

8.3.14 Aufteilungsoperatoren Zur nächsten ÜberschriftZur vorigen Überschrift

Mit where und select filtern Sie eine Datenquelle noch vorgegebenen Kriterien. Das Ergebnis ist anschließend eine neue Menge von Daten, die den Kriterien entspricht. Möchten Sie nur eine Teilmenge der Datenquelle betrachten, ohne Filterkriterien einzusetzen, eignen sich die Aufteilungsoperatoren.

Der Operator »Take«

Sie könnten zum Beispiel daran interessiert sein, nur die ersten drei Produkte aus der Liste aller Produkte auszugeben. Mit dem Take-Operator ist das sehr einfach zu realisieren:

Product[] prods = Service.GetProducts(); 
var result = prods.Take(3); 
foreach (var prod in result) 
  Console.WriteLine(prod.ProductName);

Wir greifen in unserem Beispiel auf eine Datenquelle zu, die uns der Aufruf der Methode GetProducts liefert. Natürlich kann die zu untersuchende Datenquelle zuvor durch einen anderen LINQ-Ausdruck gebildet werden:

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              where prod.Price > 3 
              select new { prod.ProductName, prod.Price }) 
               .Take(3); 
foreach (var prod in result) 
  Console.WriteLine("{0,-7}{1}", prod.ProductName, prod.Price);

Der Operator »TakeWhile«

Der Operator Take basiert auf einem Integer als Zähler. Sehr ähnlich arbeitet auch TakeWhile. Der Unterschied zum zuvor behandelten Operator ist, dass Sie ein Prädikat angeben können, das als Kriterium der Filterung angesehen wird. TakeWhile durchläuft die Datenquelle und gibt das gefundene Element zurück, wenn das Ergebnis der Prüfung true ist. Beendet wird der Durchlauf unter zwei Umständen:

  • Das Ende der Datenquelle ist erreicht.
  • Das Ergebnis einer Untersuchung lautet false.

Wir wollen uns das an einem Beispiel ansehen. Auch dabei wird als Quelle auf die Liste der Produkte zurückgegriffen. Das Prädikat sagt aus, dass die Produkte in der Ergebnisliste erfasst werden sollen, deren Preis höher als 3 ist:

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .TakeWhile(n => n.Price > 3); 
foreach (var prod in result) 
  Console.WriteLine("{0,-7}{1}", prod.ProductName, prod.Price);

Es werden die folgenden Produkte angezeigt:

  • Käse
  • Wurst
  • Obst
  • Gemüse
  • Fleisch

Beachten Sie, dass in der Ergebnisliste das Produkt Pizza nicht enthalten ist, da die Schleife beendet wird, ehe Pizza einer Untersuchung unterzogen werden kann.

Die Operatoren »Skip« und »SkipWhile«

Take und TakeWhile werden um Skip und SkipWhile ergänzt.

Skip überspringt eine bestimmte Anzahl von Elementen in einer Datenquelle. Der verbleibende Rest bildet die resultierende Ergebnismenge. Um zum Beispiel die ersten beiden in der Liste enthaltenen Produkte aus der Ergebnisliste auszuschließen, codieren Sie die folgenden Anweisungen:

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .Skip(2);

SkipWhile erwartet ein Prädikat. Die Elemente werden damit verglichen. Dabei werden die Elemente so lange übersprungen, wie das Ergebnis der Überprüfung true liefert. Sobald eine Überprüfung false ist, werden das betreffende Element und alle Nachfolgeelemente in die Ergebnisliste aufgenommen.

Das Prädikat im folgenden Codefragment sucht in der Liste aller Produkte nach dem ersten Produkt, für das die Bedingung nicht gilt, dass der Preis > 3 ist. Dieses und alle darauf folgenden Elemente werden in die Ergebnisliste geschrieben.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .SkipWhile(x => x.Price > 3 );

Ausgegeben werden folgende Produkte:

  • Süßwaren
  • Bier
  • Pizza

Galileo Computing - Zum Seitenanfang

8.3.15 Die Elementoperatoren topZur vorigen Überschrift

Bisher lieferten uns alle Operatoren immer eine Ergebnismenge zurück. Oft möchten Sie aber aus einer Liste ein bestimmtes einzelnes Element herausfinden. Hierbei unterstützen uns die Operatoren, denen wir uns nun widmen.

Der Operator »First«

Dieser Operator sucht das erste Element in einer Datenquelle. Wegen der Überladung kann es sich um das positional erste Element handeln oder um das erste Element einer mit einem Prädikat gebildeten Ergebnisliste.

Das folgende Beispiel zeigt, wie einfach der Einsatz von First ist. Als Ergebnis wird Käse an der Konsole ausgegeben.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName }) 
              .First(); 
Console.WriteLine("{0}", result.ProductName);

Vielleicht möchten Sie aber eine Liste aller Produkte haben, deren Preis kleiner 10 ist und aus dieser Liste nur das erste Listenelement herausfiltern.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .First(item => item.Price < 10); 
Console.WriteLine("{0}", result.ProductName);

Hier lautet das Produkt Wurst.

Der Operator »FirstOrDefault«

Versuchen Sie einmal, das letzte Codefragment mit dem Prädikat

item => item.Price < 1

auszuführen. Sie werden eine Fehlermeldung erhalten, weil kein Produkt in der Datenquelle enthalten ist, das der genannten Bedingung entspricht.

In solchen Fällen empfiehlt es sich, anstelle des Operators First den Operator FirstOrDefault zu benutzen. Für den Fall, dass kein Element gefunden wird, liefert der Operator default(T) zurück. Handelt es sich um einen Referenztyp, ist das null.

FirstOrDefault liegt ebenfalls in zwei Überladungen vor. Sie können neben der parameterlosen Variante auch die parametrisierte Überladung benutzen, der Sie das gewünschte Prädikat übergeben.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .FirstOrDefault(item => item.Price < 1); 
if (result == null) 
  Console.WriteLine("Kein Element entspricht der Bedingung."); 
else 
  Console.WriteLine("{0}", result.ProductName);

Die Operatoren »Last« und »LastOrDefault«

Sicherlich können Sie sich denken, dass die beiden Operatoren Last und LastOrDefault Ergänzungen der beiden im Abschnitt zuvor behandelten Operatoren sind. Beide operieren auf die gleich Weise wie First und FirstOrDefault, nur dass das letzte Element der Liste das Ergebnis bildet.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductName, prod.Price }) 
              .LastOrDefault(item => item.Price < 5); 
if (result == null) 
  Console.WriteLine("Kein Element entspricht der Bedingung."); 
else 
  Console.WriteLine("{0}", result.ProductName);

Die Operatoren »Single« und »SingleOrDefault«

Alle bislang vorgestellten Elementoperatoren lieferten eine Ergebnismenge, aus der ein Element herausgelöst wurde: Entweder liefern sie das erste oder das letzte Element. Mit Single bzw. SingleOrDefault können Sie nach einem bestimmten, eindeutigen Element Ausschau halten. Eindeutig bedeutet in diesem Zusammenhang, dass es kein Zwischenergebnis gibt, aus dem anschließend ein Element das Ergebnis bildet. In der Musterdaten-Anwendung ist beispielsweise das Feld ProductID eindeutig, vergleichbar mit der Primärschlüsselspalte einer Datenbanktabelle.

Mit Single und SingleOrDefault können Sie ein eindeutiges Element finden. Werden mehrere gefunden, wird eine InvalidOperationException ausgelöst. Auch für dieses Pärchen gilt: Besteht die Möglichkeit, dass kein Element gefunden wird, sollten Sie den Operator SingleOrDefault einsetzen, der – wie bei den anderen Operatoren auch – default<T> als Rückgabewert liefert und keine Ausnahme auslöst wie Single in diesem Fall.

Sie können beide Operatoren parameterlos aufrufen oder ein Prädikat angeben.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductID, prod.ProductName }) 
             .Single( p => p.ProductID == 2); 
if (result == null) 
  Console.WriteLine("Kein Element entspricht der Bedingung."); 
else 
  Console.WriteLine("{0}", result.ProductName);

Die Operatoren »ElementAt« und »ElementOrDefault«

Wenn Sie ein bestimmtes Element anhand seiner Position aus einer Liste extrahieren möchten, sollten Sie entweder die Methode ElementAt oder die Methode ElementAtOrDefault verwenden.

Product[] prods = Service.GetProducts(); 
var result = (from prod in prods 
              select new { prod.ProductID, prod.ProductName ) 
              .ElementAtOrDefault(3); 
if (result == null) 
   Console.WriteLine("Kein Element entspricht der Bedingung."); 
else 
  Console.WriteLine("{0}", result.ProductName);

Beide Methoden erwarten die Angabe des Index in der Liste. Da Listen nullbasiert sind, wird bei der Angabe »3« das vierte Element extrahiert. ElementAtOrDefault liefert wieder den Standardwert, falls der Index negativ oder größer als die Elementanzahl ist.

Der Operator »DefaultIfEmpty«

Standardmäßig liefert dieser Operator eine Liste von Elementen ab. Sollte die Liste jedoch leer sein, führt dieser Operator nicht sofort zu einer Exception. Stattdessen ist der Rückgabewert dann entweder default<T> oder – falls Sie die überladene Fassung von DefaultIfEmpty eingesetzt haben – ein spezifischer Wert.

List<string> liste = new List<string>(); 
liste.Add("Peter"); 
liste.Add("Uwe"); 
foreach (string tempStr in liste.DefaultIfEmpty("leer")) { 
  Console.WriteLine(tempStr); 
}

In diesem Codefragment wird vorgegeben, dass bei einer leeren Liste die Zeichenfolge leer das Ergebnis der Operation darstellt.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2008






Visual C# 2008
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Coding for Fun






 Coding for Fun


Zum Katalog: Einstieg in Visual C# 2008






 Einstieg in
 Visual C# 2008


Zum Katalog: Videotraining - Visual C# 2008






 Videotraining -
 Visual C# 2008


Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2008






 Fortgeschrittene
 Programmierung mit
 Visual C# 2008


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Visual Basic 2008






 Visual Basic 2008


Zum Katalog: Einstieg in XML






 Einstieg in XML


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2008
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de