Did you know that you can navigate the posts by swiping left and right?

Die Dreifaltigkeit in C# 3.0 - λ, LINQ und Extension-Methods (Teil 3/3)

29 Jul 2008 . Unknown . Comments

Nachdem wir nun in Teil 1 und Teil 2 eine Menge Vorarbeit geleistet haben wird es nunmehr Zeit im dritten und letzten Teil dieser Serie zum eigentlichen Hauptthema zu kommen: Das Language Integrated Query, kurz LINQ. Ohne Übertreibung kann man sagen, dass LINQ eines der umfangreichsten Sprach-Features der letzten Jahre im .NET-Framework ist und über kein Thema wurde seit Veröffentlichung so kontrovers diskutiert. LINQ bietet eine an den SQL-Syntax angelehnte Abfragemöglichkeit auf Listen an.

Nun haben wir ja bereits im vorherigen Abschnitt gelesen, dass man das auch mit den neuen Lambda-Expressions kann und intern werden alle LINQ-Queries auch in Lambda-Ausdrücke umgewandelt! Allerdings sind diese Abfragen dank des an SQL angelehnten Syntax leichter zu verstehen wenngleich auch nicht so mächtig wie die Lambda-Ausdrücke. So gibt es beispielsweise für das Zusammenfügen mehrerer Listen mittels Concat() kein Äquivalent in LINQ und auch eine Entsprechung für die Aggregatfunktionen wie Count(), Min() oder Max() fehlt.

Es wurde auch die Möglichkeit implementiert LINQ auf Daten anzusetzen so dass Abfragen auf beispielsweise XML, DataSets oder SQL-Datenbanken möglich sind. Hier bietet einem LINQ sogar in der Datenbankvariante (LINQ to SQL) einen O/R-Mapper. Mit den sich hieraus ergebenden vielfältigen Möglichkeiten kann man ganze Bücher füllen, weswegen ich mich hier auf die Grundlagen von LINQ beim Zugriff auf Listen beschränken werde. Für weiterführende Informationen verweise ich auf entsprechende Fachliteratur.


<div style="border-right: #000000 1px solid; padding-right: 4px; border-top: #000000 1px solid; padding-left: 4px; padding-bottom: 4px; vertical-align: top; border-left: #000000 1px solid; width: 523px; padding-top: 4px; border-bottom: #000000 1px solid; background-color: yellow">Obwohl viele Neuerungen sowohl für C# 3.0 als auch Visual Basic .NET 9.0 und bsp. Chrome 2.0 verfügbar sind, werde ich im Folgenden lediglich auf die Implementierung in C# eingehen. Als IDE wurde die englische Version von Microsoft Visual Studio 2008 unter Vista SP1 und Windows XP SP3 eingesetzt. Die Beispiele sind allesamt in C# geschrieben und basieren auf der Version 3.5 des .NET-Frameworks.</div> <hr /> <p>Die vollständige Serie habe ich auch in der Knowledge Base auf www.dotnet-forum.de in einem Artikel veröffentlicht. Hier findet der geneigte Leser auch noch zahlreiche Artikel von anderen Entwicklern und ein gut besuchtes Support-Forum für alle Aspekte der Programmierung mit dem .NET-Framework. Ein Besuch lohnt sich auf jeden Fall!</p> <p>[more]</p> <h2>LINQ</h2> <h3>Einführung</h3> <p>Greifen wir noch einmal auf unser erstes Beispiel mit Lambda-Ausdrücken zurück, in dem wir alle Personen ausgelesen haben deren Nachname mit einem „P“ beginnt und formulieren das dieses mal als LINQ-Query: </p> <div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:9ff70652-b5f5-4837-a098-e9301cf37dea" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"><pre name="code" class="c#:nocontrols">List<Person> Adressbuch = PersonFactory.CreateRandomPeople( 100 ); List<person> Ergebnis = from person in this.Adressbuch where person.LastName.StartsWith( “P” ) select person;</pre></div>

Gehen wir auch diesen Ausdruck Schritt für Schritt durch: In Zeile 2 wird definiert auf welche Liste die LINQ-Abfrage ausgeführt werden soll (this.Adressbuch) und wie wir den Parameter nennen wollen, der ein einzelnes Element dieser Liste repräsentiert (person). Der Datentyp dieses Parameters muss hier nicht angegeben werden da er bereits hinreichend durch den Datentyp der Liste festgelegt ist.

Um nun unser Vergleichskriterium festzulegen verwenden wir die where-Klausel. Hierbei werden wir in Visual Studio 2008 von IntelliSense unterstützt. Als Ergebnis dieser Klausel erhalten wir eine Liste vom Datentyp IEnumerable<Person>. Um nun festzulegen was wir vom LINQ-Query zurückgeliefert bekommen wird die select-Klausel verwendet; in unserem Beispiel also eine Sequenz von person-Objekten.

Es fällt auf, dass im Gegensatz zum bekannten SQL-Syntax die Reihenfolge der einzelnen Befehle etwas verdreht ist. So wird zunächst am Anfang definiert, was unsere Datenquelle ist und darauf dann diverse Operationen (wie bsp. Filtern oder Sortieren) angewendet. Erst am Schluß wird dann festgelegt in welcher Form diese Daten ausgegeben werden sollen. Diese Reihenfolge entspricht auch eher unserer Leserichtung. Es sei weiterhin angemerkt, dass der Kompiler die Schlüsselworte wie from und select nur innerhalb eines solchen LINQ-Queries interpretiert. Alter Code, in dem beispielsweise eine Variabel from genannt wurde, funktioniert also auch weiterhin; man sollte aber dennoch künftig auf eine solche Benennung zu Gunsten der Übersicht verzichten um Verwechslungen zu vermeiden.

Die Operatoren

LINQ besitzt im Vergleich zu den Lambda-Expressions nur wenige Operatoren. Das bedeutet im Umkehrschluß das man zwar jedes LINQ-Query auch als Lambda-Expression wiedergeben kann, umgekehrt jedoch (wie bereits erwähnt) nicht. Im folgenden werde ich die unterstützten Operatoren kurz mit ihrer jeweiligen Funktion vorstellen.

from

Wie bereits erwähnt wird in der from-Klausel die Datenquelle angegeben, auf welche die komplette Abfrage angewendet wird. Sie definiert also die Bereichsvariabel, die wir bei Lambda-Expressions vor dem Lambda-Operator ( => ) gefunden haben.

where

Um die Ergebnisse unserer Abfrage zu filtern wird dieser Operator verwendet. Mehrere Bedingungen können wie in C# üblich mit den Operatoren && (logisches UND) bzw. || (logisches ODER) verknüpft werden.

select

Mittels der select-Klausel wird die Rückgabe des LINQ-Queries definiert. Sie entspricht somit der Select()-Anweisung bei den λ-Ausdrücken. Dem entsprechend ist es auch mit diesem Operator möglich entweder das ganze Element zurückzuliefern (bsp. person) oder entweder einen anderen bereits existierenden oder auch anonymen Datentyp mittels Projektion.

orderby

Mit Hilfe dieses Operators lässt sich die Ausgabe nach einem oder mehreren Kriterien sortieren. Hierbei werden ebenfalls die Schlüsselworte für eine aufsteigende (ascending, der Standard) bzw. eine absteigende (descending) Sortierung unterstützt: orderby person.LastName, person.Age descending (zunächst eine aufsteigende Sortierung nach dem Nachnamen, anschließend absteigend nach dem Alter der jeweiligen Personen).

group

Um meine Ergebnisse nach bestimmten Kriterien zu gruppieren kann man die group-Klausel verwenden. Sie entspricht in ihrer Funktion der GroupBy()-Methode, auf die ich bei den Lambda-Ausdrücken nicht näher eingegangen bin. Kurz zusammengefasst kann man sagen, dass das Resultat eine Liste aus IGrouping<K, V> Elementen ist. Der mit K bezeichnete Datentyp ist der Schlüssel, nach dem gruppiert wurde, und der mit V bezeichnete Datentyp ist der enthaltene Wert – also beispielsweise eine Liste mit person-Objekten. Zur Veranschaulichung ein einfaches Beispiel, bei dem wir das Resultat nach Postleitzahlen gruppieren:

List<Person> Adressbuch = PersonFactory.CreateRandomPeople( 100 );
var Ergebnis = from item in this.Adressbuch
               group item by item.PLZ into ausgabe
               select ausgabe;

Wir haben hier außerdem noch das Schlüsselwort into verwendet, um das Ergebnis der Gruppierung in einer temporären „Variabeln“ zu speichern damit wir später erneut bei der select-Klausel darauf zurückgreifen können.

let

Um weitere Unterabfragen innerhalb eines einzelnen Queries zu machen müssen die Abfrageergebnisse mit dem Operator let temporär zwischengespeichert werden um sie verwenden zu können. Man verwendet ihn in der Regel um Unterabfragen (in SQL auch „Subselects“ genannt) zu verknüpfen, für die sich der join-Operator nicht eignet (siehe unten) und wird hier nur der Vollständigkeit halber aufgeführt.

join

Genau wie die Methode Join() bei Lambda-Ausdrücken dient die join-Klausel dem Zusammenfügen mehrerer Ergebnisse zu einer resultierenden Liste, einen so genannten Equijoin. Hiermit bezeichnet man die Verknüpfung zweier Listen und nimmt als Grundlage die Gleichheit jeweils ein Elementes aus jeder Liste (bsp. eine ID). Diese Art von Vergleich ist vor allem in relationalen Datenbanken in der 2. oder 3. Ableitung häufig zu finden.

Fazit

Zunächst freut es mich, dass Sie bis hierher gekommen sind! Neben den relativ wenigen hier exemplarisch aufgeführten Punkten gibt es noch eine vielzahl weiterer Anwendungszwecke die für den geneigten Programmierer sicherlich interessant sind. Mein Ziel war es jedoch lediglich die rudimentären Grundlagen zu liefern und für das nötige Hintergrundwissen zu sorgen damit man überhaupt versteht was passiert wenn man eine LINQ-Abfrage startet. Ich hoffe das ich die hier erwähnten neuen Technologien von .NET 3.5 – allen voran natürlich LINQ – dem einen oder anderen schmackhaft machen konnte. Einige Bereiche habe ich dabei bewußt nur kurz angeschnitten um Einsteiger nicht hoffnungslos zu überfordern und interessierten Lesern einen Anreiz auf weitere Möglichkeiten zu bieten. Auf der Basis dieser Grundlagen kann man nun weiter experimentieren und sich mit den zahlreichen Erweiterungen (LINQ to SQL, LINQ to XML, LINQ to Amazon,  …) vertraut machen die zwischenzeitlich entwickelt wurden.

Programm-Tipp

Wer gerne ein wenig mit LINQ-Abfragen oder Lambda-Ausdrücken experimentieren möchte dem empfehle ich das kostenlose ProgrammLINQPad von Joseph Albahari, über welches ich bereits berichtete. Das Programm ist recht klein und kommt als eine einzelne ausführbare Datei ohne eigene Installation aus – lädt also geradezu zum experimentieren ein. Ein Must-Have für jeden, der sich für LINQ & co. interessiert!

Buch-Tipp

Im Addison Wesley Verlag ist im Juni 2008 das Buch Visual C# 2008 von Frank Eller erschienen (ISBN: 978-3-8273-2641-6). Mit seinen rund 1.300 Seiten und einem Preis von 49,95 Euro ist es sicher keine Lektüre für zwischendurch. Es wird jedoch auf sowohl die Grundlagen als auch die fortgeschrittenen Funktionen von C# 3.0 eingegangen. Allerdings empfehle ich dem gewillten Leser bereits ein wenig Erfahrung in Bezug auf Objektorientierung und die Programmierung im allgemeinen (bsp. mit classic ASP oder auch Java). Neueinsteiger ohne jede Vorkenntnis haben vor allem am Anfang eine recht hohe Lernkurve bei diesem Buch. Einsteiger mit Vorkenntnissen, Umsteiger und fortgeschrittene Programmierer erhalten jedoch ein Praxis- und Nachschlagebuch, das sie bei der täglichen Arbeit mit C# begleitet (so auf dem Buchrücken formuliert). Aus diesem Buch sind auch ein paar der in diesem Artikel genannten Beispiele entlehnt.

Questions/Suggestions
As always, for questions or feedback, contact me or leave a comment.

Octocat by GitHubEdit this page on GitHub