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

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 11 LINQ
Pfeil 11.1 Was ist LINQ?
Pfeil 11.1.1 Verzögerte Ausführung
Pfeil 11.1.2 LINQ-Erweiterungsmethoden an einem Beispiel
Pfeil 11.2 LINQ to Objects
Pfeil 11.2.1 Musterdaten
Pfeil 11.2.2 Die allgemeine LINQ-Syntax
Pfeil 11.3 Die Abfrageoperatoren
Pfeil 11.3.1 Übersicht der Abfrageoperatoren
Pfeil 11.3.2 Die »from«-Klausel
Pfeil 11.3.3 Mit »where« filtern
Pfeil 11.3.4 Die Projektionsoperatoren
Pfeil 11.3.5 Die Sortieroperatoren
Pfeil 11.3.6 Gruppieren mit »GroupBy«
Pfeil 11.3.7 Verknüpfungen mit »Join«
Pfeil 11.3.8 Die Set-Operatoren-Familie
Pfeil 11.3.9 Die Familie der Aggregatoperatoren
Pfeil 11.3.10 Quantifizierungsoperatoren
Pfeil 11.3.11 Aufteilungsoperatoren
Pfeil 11.3.12 Die Elementoperatoren
Pfeil 11.3.13 Die Konvertierungsoperatoren

Rheinwerk Computing - Zum Seitenanfang

11.3 Die AbfrageoperatorenZur nächsten Überschrift


Rheinwerk Computing - Zum Seitenanfang

11.3.1 Übersicht der AbfrageoperatorenZur nächsten ÜberschriftZur vorigen Überschrift

LINQ stellt Ihnen zahlreiche Erweiterungsmethoden zur Verfügung, die auch als Abfrageoperatoren bezeichnet werden. Sie sind alle in der Klasse Enumerable des Namespaces System.Linq definiert. In Tabelle 11.1 sind alle LINQ-Abfrageoperatoren angegeben.

Tabelle 11.1 Die LINQ-Abfrageoperatoren

Operatortyp Operator

Aggregatoperatoren

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

Konvertierungsoperatoren

Cast, OfType, ToArray, ToDictionary, ToList, ToLookup

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 viele der hier aufgeführten LINQ-Abfrageoperatoren genauer eingehen.


Rheinwerk Computing - Zum Seitenanfang

11.3.2 Die »from«-KlauselZur nächsten ÜberschriftZur vorigen Überschrift

Ein LINQ-Abfrageausdruck beginnt mit der from-Klausel. Diese gibt vor, welche Datenquelle abgefragt werden soll, und definiert eine lokale Bereichsvariable, die ein 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.

Die LINQ-Abfragen arbeiten mit Methoden, die meist Sequenzen verwenden. Diese Objekte implementieren entweder die IEnumerable<T>- oder die IQueryable<T>-Schnittstelle. Es stellt sich oft die Frage nach dem Unterschied, weil in beiden Fällen die Methode GetEnumerator() veröffentlicht wird.

Mit IEnumerable<T> kann man gut mit einer Datenstruktur im Speicher arbeiten. Die einzelnen Erweiterungsmethoden arbeiten im Prinzip wie sequenzielle Filter – das Ergebnis des ersten Filters ist der Input für den zweiten Filter usw. Mit einer externen Datenquelle will man so normalerweise nicht arbeiten, denn es wäre extrem ineffizient, zunächst eine ganze Tabelle in den Cache zu laden und diese als IEnumerable<T> zu repräsentieren, um dann anschließend und auf der Ergebnisliste eine Where-Filterbedingung anzuwenden. Deshalb arbeiten IQueryable<T>-implementierende Objekte so, dass sie erst mal den kompletten Abfrageausdruck zusammenbauen, der dann als Ganzes gegen die Datenquelle abgesetzt wird.

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;

Listing 11.11 from-Klausel und ArrayList

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 unsere Musterdaten für dieses Kapitel bereitstellt.

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

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 beispielsweise mit orderby sortiert werden.

Customer[] customers = Service.GetCustomers();
var query = from customer in customers
where customer.Name == "Hans"
from order in customer.Orders
where order.Quantity > 6
select new {order.OrderID, order.ProductID};

Listing 11.12 Filtern einer untergeordneten Menge (Abfrage-Syntax)

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.

Es sei an dieser Stelle auch dieselbe Abfrage in Erweiterungsmethoden-Syntax gezeigt:

Customer[] customers = Service.GetCustomers();
var query = customers
.Where(c => c.Name == "Hans")
.SelectMany(c => c.Orders)
.Where(order => order.Quantity > 6)
.Select(order => new { order.OrderID, order.ProductID});

Listing 11.13 Untergeordnete Menge mit »SelectMany« (siehe Buch-DVD)

Enthält ein gefundenes Element eine Untermenge (hier werden die Bestellungen eines Customer-Objekts durch ein Array beschrieben), benötigen wir den Operator SelectMany. An diesem Beispiel können Sie erkennen, dass sich in manchen Fällen Abfrage-Syntax und Erweiterungsmethoden-Syntax doch deutlich unterscheiden.


Rheinwerk Computing - Zum Seitenanfang

11.3.3 Mit »where« filternZur 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;
foreach (var item in result)
Console.WriteLine(item);

Listing 11.14 Die »where«-Klausel (siehe Buch-DVD)

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);

Listing 11.15 Die »where«-Klausel (Erweiterungsmethoden-Syntax)

Auch mehrere Filterkriterien zu berücksichtigen ist nicht weiter schwierig. Sie müssen nur den where-Operator ergänzen und benutzen zur Formulierung des Filters 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);

Listing 11.16 Mehrere Filterkriterien

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 bis einschließlich 9 in der Datenquelle orders berücksichtigt.

Order[] orders = Service.GetOrders();
var result = orders
.Where((order, index) => order.Quantity > 3 && index < 10)
.Select(ord => ord.OrderID, ord.ProductID, ord.Quantity});
foreach (var item in result)
Console.WriteLine("{0,-5}{1,-5}{2}",
item.OrderID, item.ProductID, item.Quantity);

Listing 11.17 Resultate mit »where« einschränken (siehe Buch-DVD)

Das Ergebnis wird mit den Bestellungen gebildet, die die OrderIDs 3, 4, 5 und 10 haben.

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 also der Delegat auf eine anonyme Methode. In der Definition des Where-Operators wird dieser Delegate durch den Delegate

Func<T, bool> predicate

beschrieben (siehe Definition von Where weiter oben). Der generische Typparameter T wird durch den Datentyp der Elemente in der zugrunde liegenden Collection beschrieben, die bekanntlich die Schnittstelle IEnumerable<T> implementiert. In unserer Anweisung 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 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 Operatoren erörtern.


Rheinwerk Computing - Zum Seitenanfang

11.3.4 Die ProjektionsoperatorenZur 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 Liste vertretenen Bestellungen.

Soll der Select-Operator eine Liste neu strukturierter Objekte liefern, müssen Sie einen anonymen Typ als Ergebnismenge definieren:

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

Hierbei wird auch von einer Selektion gesprochen.

Der Operator »SelectMany«

SelectMany kommt dann 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 query = customers
.Where(c => c.Name == "Hans")
.SelectMany(c => c.Orders)
.Where(order => order.Quantity > 6)
.Select(order => new { order.OrderID, order.ProductID});

Listing 11.18 Der Operator »SelectMany«

In Listing 11.14 hatten wir bereits dieses Beispiel, so dass an dieser Stelle auf weitere Ausführungen verzichtet wird.


Rheinwerk Computing - Zum Seitenanfang

11.3.5 Die SortieroperatorenZur 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. Nachfolgend 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 item in result)
Console.WriteLine("ID: {0,-3}{1}", item.OrderID, item.Quantity);

Listing 11.19 Sortieren mit »orderby« in Abfrage-Syntax (siehe Buch-DVD)

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 });

Listing 11.20 Sortieren mit »OrderBy« in Erweiterungsmethoden-Syntax

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

orderby order.Quantity descending

Das folgende Listing 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 });

Listing 11.21 Sortieren mit »OrderByDescending«

Wenn Sie mehrere Sortierkriterien festlegen wollen, helfen Ihnen die beiden Operatoren ThenBy beziehungsweise ThenByDescending weiter. Deren Einsatz setzt aber die vorhergehende Verwendung von OrderBy oder OrderByDescending voraus. Nehmen wir an, die erste Sortierung soll die Bestellmenge berücksichtigen und die zweite, ob die Bestellung bereits ausgeliefert ist. Der Programmcode 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 item in result)
Console.WriteLine("ProductID: {0,-3}Menge:{1,-4} Geliefert:{2}",
item.OrderID, item.Quantity, item.Shipped);

Listing 11.22 Mehrere Sortierkriterien (siehe Buch-DVD)

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();

Listing 11.23 Ergebnisliste mit »Reverse« umkehren

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();

Listing 11.24 Sortieren mit »OrderByDescending« (Abfrage-Syntax)


Rheinwerk Computing - Zum Seitenanfang

11.3.6 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 das Array mit Customer-Objekten. Es sollen die Kunden (Customer-Objekte) nach deren Wohnsitz (Cities) gruppiert werden.

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);
}

Listing 11.25 Gruppieren der Ergebnisliste (siehe Buch-DVD)

Die Ausgabe in der Konsole sehen Sie in Abbildung 11.1.

Abbildung

Abbildung 11.1 Die Ausgabe des Listings 11.26

Der Operator GroupBy ist vielfach überladen. Sehen wir uns eine davon 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 eine spezialisierte Form von IEnumerable<T>. Sie definiert die schreibgeschützte Eigenschaft Key, die den Wert der zu bildenden Gruppe abruft.

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

Im Beispiel oben werden mittels key die Städte aus dem generischen Typ K (also Cities) abgefragt. 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 durchläuft die einzelnen Gruppen und gibt als Resultat alle Elemente zurück, die zu der entsprechenden Gruppe gehören. In unserem Beispielcode wird diese Untergruppe mit der Variablen item beschrieben. In der inneren Schleife werden anschließend alle Elemente von temp erfasst 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

Rheinwerk Computing - Zum Seitenanfang

11.3.7 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);

Listing 11.26 Einsatz des »Join«-Operators (siehe Buch-DVD)

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 erste 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 mit dem im dritten Argument angegebenen Schlüsselfeld der inneren Liste in Beziehung gesetzt wird.

Im vierten Argument wird die Ergebnisliste festgelegt. 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 Abfrage-Syntax 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};

Listing 11.27 Joins mit der Abfrage-Syntax

Die Ergebnisliste sehen Sie in Abbildung 11.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

Abbildung 11.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, befindet sich kein Datensatz in der Ergebnismenge. Damit ist der Join-Operator mit dem INNER JOIN-Statement einer SQL-Abfrage vergleichbar.

Suchen Sie ein Äquivalent zu einem LEFT OUTER JOIN oder RIGHT OUTER JOIN, 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:

Product[] products = Service.GetProducts();
Customer[] customers = Service.GetCustomers();
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 liste) {
Console.WriteLine("ProductID: {0}", t.ProductID, t.Orders);
foreach (var order in t.Orders)
Console.WriteLine(" OrderID: {0}", order.OrderID);
}

Listing 11.28 LEFT OUTER JOIN mit dem Operator »GroupJoin«

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 in 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 11.3 sehen, denn der Artikel mit der ProductID=2 ist in keiner Bestellung zu finden.

Abbildung

Abbildung 11.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};

Listing 11.29 LEFT OUTER JOIN in der Abfrage-Syntax


Rheinwerk Computing - Zum Seitenanfang

11.3.8 Die Set-Operatoren-FamilieZur 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 = {"Aachen", "Köln", "Bonn", "Aachen", "Bonn", "Hof"};
var liste = (from p in cities select p).Distinct();
foreach (string city in liste)
Console.WriteLine(city);

Listing 11.30 Der Operator »Distinct« (siehe Buch-DVD)

Im Array cities kommen die beiden Städte Aachen und Bonn je zweimal vor. Der auf die Ergebnismenge angewendete Distinct-Operator 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 = {"Aachen", "Bonn", "Aachen", "Frankfurt"};
string[] namen = {"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);

Listing 11.31 Der »Union«-Operator (siehe Buch-DVD)

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 = {"Aachen", "Köln", "Bonn", "Aachen", "Frankfurt"};
string[] cities2 = {"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);

Listing 11.32 Der Operator »Intersect« (siehe Buch-DVD)

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.

Verwenden Sie in Listing 11.33 anstelle von Intersect den Operator Except, enthält die Ergebnisliste die Orte Aachen und Frankfurt.


Rheinwerk Computing - Zum Seitenanfang

11.3.9 Die Familie der AggregatoperatorenZur 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);

Listing 11.33 Der Operator »Count« (siehe Buch-DVD)

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);

Listing 11.34 Anzahl der Elemente einer untergeordneten Menge (siehe Buch-DVD)

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 Integer-Werte 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);

Listing 11.35 Der einfache Einsatz des Operators »Sum«

Das folgende Listing 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);

Listing 11.36 Der Operator »Sum« (siehe Buch-DVD)

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 Besteller, die ProductID und die Gesamtsumme für diesen Artikel, die anhand der Bestellmenge gebildet wurde (siehe Abbildung 11.4).

Abbildung

Abbildung 11.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 (hier vertreten durch das Schlüsselwort join) 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 11.5.

Abbildung

Abbildung 11.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. Der Einsatz der Operatoren ist 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 geworfen 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 die geforderte Schnittstelle nicht implementiert. Die Lösung dieser Problematik ist nicht schwierig. Die Operatoren sind alle so überladen, dass auch ein Selektor übergeben werden kann. Dazu geben Sie das gewünschte Element aus der Liste der Elemente, die den anonymen Typ bilden, als Bedingung an.

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

Rheinwerk Computing - Zum Seitenanfang

11.3.10 QuantifizierungsoperatorenZur nächsten ÜberschriftZur vorigen Überschrift

Beabsichtigen Sie, 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 ProductID = 7 bestellt hat. Any hilft, das festzustellen.

Customer[] customers = Service.GetCustomers();
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=7 ist enthalten");
else
Console.WriteLine("ProductID=7 ist nicht enthalten");

Listing 11.37 Lising 11.38: Der Operator »Any« (siehe Buch-DVD)

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 die Preise aller Produkte > 3 sind, genügt die folgende LINQ-Abfrage:

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

Rheinwerk Computing - Zum Seitenanfang

11.3.11 AufteilungsoperatorenZur nächsten ÜberschriftZur vorigen Überschrift

Mit where und select filtern Sie eine Datenquelle nach vorgegebenen Kriterien. Das Ergebnis ist eine Datenmenge, die den vorgegebenen 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);

Listing 11.38 Der Operator »Take« (siehe Buch-DVD)

Der Operator »TakeWhile«

Der Operator Take basiert auf einem Integer als Zähler. Sehr ähnlich arbeitet auch TakeWhile. Im Unterschied zum Operator Take können Sie eine Bedingung angeben, die als Filterkriterium angesehen wird. TakeWhile durchläuft die Datenquelle und gibt das gefundene Element zurück, wenn das Ergebnis der Bedingungsprü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);

Listing 11.39 Operationen mit »TakeWhile« (siehe Buch-DVD)

Es werden die folgenden Produkte angezeigt: Käse, Wurst, Obst, Gemüse und 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, weil das erste Produkt, das die Bedingung nicht mehr erfüllt (Süßwaren, siehe die Liste der Produkte in Abschnitt 11.2.1), das Ende der Schleife erzwingt.

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 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 auch 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 und Pizza.


Rheinwerk Computing - Zum Seitenanfang

11.3.12 Die ElementoperatorenZur nächsten ÜberschriftZur vorigen Überschrift

Bisher lieferten uns alle Operatoren immer eine Ergebnismenge zurück. Möchten Sie aber aus einer Liste ein bestimmtes Element herausfiltern, stehen Ihnen zahlreiche weitere Operatoren zur Verfügung. Diesen wollen wir uns nun widmen.

Der Operator »First«

Der First-Operator sucht das erste Element in einer Datenquelle. Dabei kann es sich um das erste Element aus einer Liste handeln oder um das erste Element einer mit einem Prädikat gebildeten Ergebnisliste. Daraus können Sie den Schluss ziehen, dass der First-Operator überladen ist. Das folgende Beispiel zeigt, wie einfach der Einsatz von First ist. Aus der Gesamtliste aller Produkte soll nur das an erster Position stehende Produkt als Resultat zurückgeliefert werden.

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

Listing 11.40 Der Operator »First« (siehe Buch-DVD)

Als Ergebnis wird Käse an der Konsole ausgegeben. Vielleicht möchten Sie aber eine Liste aller Produkte haben, deren Preis kleiner 10 ist, und aus dieser Liste nur das erste Listenelement herausfiltern. Dazu können Sie mit einem Lambda-Ausdruck eine Bedingung formulieren, die als Argument an First übergeben wird.

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);

Listing 11.41 Der Operator »First« mit Filterung

Hier lautet das Produkt Wurst. Dasselbe Resultat erreichen Sie natürlich auch, wenn Sie stattdessen die LINQ-Abfrage wie folgt formulieren:

var result = (from prod in prods
where prod.Price < 10
select new { prod.ProductName, prod.Price }).First();

Der Operator »FirstOrDefault«

Versuchen Sie einmal, das letzte Beispiel 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. Wird kein Element gefunden, 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);

Listing 11.42 Der Operator »FirstOrDefault« mit Filterung

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 gleiche 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);

Listing 11.43 Der Operator »LastOrDefault« mit Filterung

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 nach einem eindeutig identifizierbaren Element suchen. Werden mehrere gefunden, wird eine InvalidOperationException ausgelöst. Auch für dieses Operator-Pärchen gilt: Besteht die Möglichkeit, dass kein Element gefunden wird, sollten Sie den Operator SingleOrDefault einsetzen, der ebenfalls default(T) als Rückgabewert liefert und keine Ausnahme auslöst, wie das bei dem Einsatz von Single der Fall wäre.

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);

Listing 11.44 Der Operator »Single« mit Filterung

Die Operatoren »ElementAt« und »ElementOrDefault«

Möchten Sie ein bestimmtes Element aus einer Liste anhand seiner Position extrahieren, sollten Sie entweder die Methode ElementAt oder die Methode ElementAtOrDefault verwenden. ElementAtOrDefault liefert wieder den Standardwert, falls der Index negativ oder größer als die Elementanzahl ist.

Bekanntermaßen werden Listenelemente mit Indizes versehen. Den beiden Methoden übergeben Sie einfach nur den Index des gewünschten Elements aus der Liste. Sind Sie zum Beispiel am vierten Element aus einer Liste interessiert, übergeben Sie die Zahl 3 als Argument an ElementAt oder ElementAtOrDefault, z. B.:

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);

Listing 11.45 Der Operator »ElementAtOrDefault«

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);
}

Listing 11.46 Der Operator »DefaultIfEmpty«

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


Rheinwerk Computing - Zum Seitenanfang

11.3.13 Die KonvertierungsoperatorenZur vorigen Überschrift

Die Konvertierungsoperatoren dienen dazu, eine Sequenz in eine andere Collection umzuwandeln. Insbesondere die Operatoren ToList und ToArray sind oft hilfreich, wenn die sofortige Ausführung einer Abfrage gewünscht wird und das Resultat zwischengespeichert werden soll. Das Abfrageergebnis ist eine Momentaufnahme der Daten. Dabei speichert ToList das Abfrageergebnis in einer List<T> und ToArray in einem typisierten Array.

Die beiden folgenden Listings zeigen den Einsatz der Methoden ToList und ToArray.

IEnumerable<string> names = (Service.GetCustomers()
.Select(cust => cust.Name ).ToArray());

Listing 11.47 Die Konvertierungsmethode »ToArray«

List<string> customers = (Service.GetCustomers()
.Select(cust => cust.Name).ToList());

Listing 11.48 Die Konvertierungsmethode »ToList«



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual C# 2012

Visual C# 2012
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: C/C++






 C/C++


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern