LINQ für Einsteiger

    • VB.NET
    • .NET (FX) 4.5–4.8

    Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

      LINQ für Einsteiger

      Servus!

      Wie in diesem Thread hier: Bindingsource.Find bereits gefragt und weil ich LINQ auch in letzter Zeit immer mal wieder gebrauchen konnte, habe ich mir nun dazu entschlossen, ein kleines Tutorial über LINQ zu machen

      Einführung:
      Was ist LINQ überhaupt? LINQ steht für Language Integrated Query, auf deutsch: Sprachintegrierte Abfrage. Mit LINQ ist es möglich Sequenzen, d.h. Klassen, die das IEnumerable-Interface implementieren (=Basis-Interface für Enumerationsklassen, sprich Klassen, die eine Auflistung ermöglichen) zu "queryen". Das bedeutet im Endeffekt nichts anderes, als dass ich eine Liste, die ich bekomme, wie eine Datenbank abfragen kann, um mir gewisse Mengen (hier ist nicht der allgemein bekannte Ausdruck "Menge", sprich Quantität, gemeint, sondern der mathematische Terminus Menge - die Menge aller positiven, natürlichen Zahlen, kleiner 10 sind 0 bis 9 (eine mathematische Menge von Integern)) liefern zu lassen.
      Bleiben wir bei dem bsp. Menge aller positiven, natürlichen Zahlen, kleiner 10 . Wenn ich diese Zahlenmenge erhalten will, dann werden diese von einem Anfänge der Programmierung gerne in einem banalen Array gespeichert:

      VB.NET-Quellcode

      1. Dim menge() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      2. 'Kann (eher sollte) aber auch über die Enumerable-Klasse aus System.Linq gelöst werden:
      3. Dim menge() As Integer = Enumerable.Range(0, 9)

      Will ich jetzt aus dieser Menge an Zahlen eine Untermenge haben (alle Zahlen mit Teilungsrest 0 bei Teilung durch 2), dann kann ich das entweder über eine For-Schleife und durchprobieren machen, oder ich löse das ganze mit LINQ - wer hätte es gedacht ;D

      Eines Vorweg: LINQ ist NICHT dazu da, bereits bestehende Listen zu verändern (ist ja nur "Query" und nicht "Manipulate")! Heißt konkret: Wenn ich eine bestehende Liste mit LINQ filtere, erzeuge ich eine Kopie und arbeite von da an nur noch mit der Kopie, NICHT mit der originalen Liste. Änderungen der gefilterten Liste betreffen nicht die originale Liste!

      Wie verwende ich LINQ?
      Und da sind wir auch schon mitten drin! LINQ an sich hat den großen Vorteil: Man spart sich unglaublich viel Code (und Nerven) - falls man es richtig verwendet.
      Um jetzt besagte Abfragen durchführen zu können braucht man eigentlich nur 2 Sachen: Das .NET Framework 3.5 und den Namespace System.Linq importieren

      Für die Verwendung von LINQ gibt es zwei verschiedene Anlaufstellen: Entweder über die Erweiterungsmethoden aus System.Linq der Klasse Enumerable oder über sprachintegriere Keywords (FROM, WHERE,...). Beide Möglichkeiten lieferen als Ergebnis wieder ein IEnumerable-Sequenz.
      Wie bei dem vorher benannten Beispiel kann ich jetzt aus der Menge von ganzen Zahlen (Integern) mir eine Untermenge ausgeben lassen:

      VB.NET-Quellcode

      1. Imports System.Linq
      2. Module Module1
      3. Sub Main()
      4. 'Das besprochene Array
      5. Dim menge() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      6. 'Die erste Möglichkeite (kürzere Variante)
      7. Dim menge21 = menge.Where(Function(z) z Mod 2 = 0)
      8. 'Die zweite (wenn komplexere Bedinungen zutreffen müssen)
      9. Dim menge22 = menge.Where(Function(z)
      10. Return z Mod 2 = 0
      11. End Function)
      12. 'Die dritte Variante mit From und Where
      13. Dim menge3 = From m In menge
      14. Where m Mod 2 = 0
      15. End Sub
      16. End Module


      Wer sich mal unter Visual Studio den Katalog an LINQ-Erweiterungsmethoden angesehen hat, wird feststellen, dass es insgesamt 44 verschiedene Erweiterungen gibt - diese jedoch in diversen Überladungen (20 alleine für Sum())
      Ich gehe mal davon aus, dass man im normalen Alltag nicht recht viel mehr als eine Hand voll dieser Methoden braucht. Ich werde mich hier allerdings auch nur über die folgenden Methoden auslassen. Falls andere gefragt sind, bitte melden, ich werde das Tut ASAP editieren
      Spoiler anzeigen

      Any
      Cast
      Count
      ElementAt
      First
      GroupBy
      Last
      OrderBy
      Select
      ToArray
      ToList
      Where


      Any()
      Wenn man sich die Signatur und den Rückgabewert von Any() ansieht, stellt man fest, dies Methode will keine Parameter und gibt einen Boolean zurück. Diese Methode ist von der Verwendung her dazu gedacht, zu prüfen, ob ein Sequenz überhaupt Elemente enthält. Geschwindigkeitstechnisch nehmen sich die beiden nicht wirklich viel (einfach mal nach "vb any vs count" googlen - oder dem Link folgen). Der Rückgabewert Boolean besagt wie gesagt nur, ob Elemente vorhanden sind oder nicht, Count kann gleichzeitig auch auswerten, wie viele Elemente es genau sind.
      Dabei hat die Any()-Methode auch eine Überladung, welche es dem Programmierer erlaubt, ein Predicate, also eine Bedingung - anzugeben, nach welchem Unterschieden werden soll. So kann gesucht werden, ob die Liste Elemente enthält, welche sich Modulus 2 teilen lassen und als Restwert 0 ergeben.

      VB.NET-Quellcode

      1. 'Das besprochene Array
      2. Dim menge() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      3. Dim b1 = menge.Any()
      4. Dim b2 = menge.Any(Function(z) z Mod 2 = 0)


      Cast
      Diese Methode erlaubt es jedes Element der originalen Sequenz in einen anderen Typen zu casten (Sequenz von Integern in eine von Shorts oder UIntegern)

      VB.NET-Quellcode

      1. Dim menge() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      2. Dim menge4 = menge.Cast(Of UInteger)


      Count
      Die Count-Erweiterung erlaubt es die Elemente der Sequenz zu zählen, dabei kann wie bei fast jeder Überladung ein Predicate mitgegeben werden, anhand der die Ergebnise eingeschränkt werden(wie bei Any, nur Elemente mit Modulo 2 = 0)

      VB.NET-Quellcode

      1. Dim anzahl1 = menge.Count()
      2. Dim anzahl2 = menge.Count(Function(z) z Mod 2 = 0)


      ElementAt
      Ist der Gegenpart zur Find()-Methode, hierfür muss nicht ein Attribut sondern der Index bekannt sein. Die ElementAt Methode fordert den Index und liefert eine Referenz auf das gewünschte Element

      VB.NET-Quellcode

      1. Dim element1 = menge.ElementAt(2)


      First
      First() Liefert eine Referenz auf das erste Element der Sequenz, bestückt mir einem Predicate kann auch angegeben werden, welche Bedinung das erste Element erfüllen muss. So kann aus einer Liste von Mitarbeitern der erste mit Alter größer 35 bestimmt werden. Hierzu im Predicate einen "Mitarbeiter empfangen" und das Ergebnis des Vergleichs "Alter größer 35" zurück geben

      VB.NET-Quellcode

      1. Dim element2 = menge.First()
      2. Dim element3 = menge.First(Function(z) z Mod 2 = 0)

      Falls kein Element vorhanden ist, oder auf das Predicate zutrifft, wird eine InvalidOperationException geworfen (Danke @Trade). Dies hat logischerweise zur Folge, dass das Programm abstürzt. Um besagte Exception zu vermeiden, kann man die FirstOrDefault-Erweiterung verwenden. Diese gibt im Falle eine Treffers das besagte erste Element zurück. Sollte sich jedoch kein Treffer finden, wird der Standardwert - in VB ist das Nothing, in C# wäre es null - zurück gegeben. Gleiches gilt auch für Last bzw. LastOrDefault!

      GroupBy
      GroupBy erledigt das selbe wie die gleichnamige SQL-Anweisung und gruppiert die Elemente einer Sequenz. Da es für die Methode jedoch sehr viele, seeehr komplizierte Überladungen gibt, werde ich mir hier nur auf die erste beschränken:

      VB.NET-Quellcode

      1. Dim menge5 = menge.GroupBy(Function(z) z Mod 2 = 0)

      Hierzu wird der Methode wieder ein Predicate mit gegeben, welches überprüfen soll, ob die Zahl Modulo 2 gleich 0 ergibt. Es wird eine Sequenz von IGrouping(Of Boolean, Integer) zurück gegeben. Diese Sequen enthält 2 Listen. Einmal die mit Restwert 0 und dann die mit Restwert != 2

      Last
      Funktioniert genau so wie First(), hat ebenfalls eine Überladung mit Predicate, liefert nur nicht das ERSTE sondern das Letzte Element der Sequenz

      OrderBy
      Liefert eine aufgesteigend sortierte Kopie der originalen Liste. Die Methode hat ebenfalls eine Erweiterung, dieser kann man einen IComparer übergeben, den man selber neu implementieren kann, um so auch eigene Datentypen sortieren zu können. Mit einer Integer-Auflistung lässt sich das schlecht machen, einfach mal MSDN nachschlagen
      OrderBy hat als Rückgabewert ein IOrderedEnumerable, welches über ThenDescending() weiter sortiert werden kann.

      SelectLaut MSDN: Projeziert jedes Elemente einer Sequenz in eine neue Form. Bedeutet: Man erhält eine bearbeitet Kopie der originalen Liste, ohne Ausschlüsse. So kann aus einer Liste von Teilnehmern oder Mitarbeitern ein Kürzelfeld mit den ersten 3 Buchstaben von Nach- und Vornamen gefüllt werden und das mit nur einer einzigen Zeile!

      VB.NET-Quellcode

      1. Dim ma As New List(Of Mitarbeiter)
      2. ma = ma.Select(Function(m As Mitarbeiter)
      3. m.Kuerzel = m.Nachname.Substring(0, 3) & m.Vorname.Substring(0, 3)
      4. Return m
      5. End Function).ToList()
      6. 'Mitarbeiter:
      7. Public Class Mitarbeiter
      8. Public Property ID As Integer
      9. Public Property Nachname As String
      10. Public Property Vorname As String
      11. Public Property Kuerzel As String
      12. End Class


      ToArray und ToList
      Ich gehe mal davon aus, diese Methoden sind selbst erklärend.

      Where
      Last but not Least - Least wäre die Zip-Methode ;D - die Where-Methode. Diese Methode ist gedacht, um Wertemengen zu filtern und nur bestimmte Einträge zu übernehmen. Auf Ebene von List(Of ) gibt es dann die Find()-Methode, machen im Prinzip das selbe, Find ist aber wie gesagt erst ab List verfügbar.

      VB.NET-Quellcode

      1. Dim menge7 = menge.Where(Function(z) z Mod 2 = 0)


      Sprachintegrierte Schlüsselwörter
      Wie im ersten Beispiel gegeben, ist neben den Erweiterungen der Enumerable-Klasse aus System.Linq auch die Abfrage über sprachintegrierte Schlüsselwörter möglich (Danke @thefiloe). Dabei können Queries, die wie SQL-Statements gelesen werden können direkt im Quellcode erzeugt werden. Mit allen Vorteilen, die Visual Studio mit sich bringt: AutoCompletion, IntelliSense, Typenprüfung, etc. Dabei ist es möglich SQL-Abfragen, die man vorher über beliebige DbCommand-Objekte auf der Datenbank ausgeführt hat, fast komplett übernehmen zu können. So wird aus einer SQL-Abfrage, die aus einer Datenbank mit mehreren Tabellen eine Menge an Daten zieht, durch einfache Sprachintegration eine übersichtliche, durch den Code-Editor (layout-technisch) unterstütze Linq-Abfrage.
      Eine Beispielanwendung werde ich nachliefern, sobald ich bei mir die Datenbank zum laufen gebracht habe.

      Abschluss:
      Zum Ende: LINQ an sich ist echt eine super Erfindung, auch wenn es aufgrund des ganzen Generischen von der Dokumentation her schwer zu lesen ist, aber wenn man es mal raus hat, dann kann man recht gut damit arbeiten. Wenn sich wer von euch noch weiter mit dem Thema auseinander setzen will, kann ich nur empfehlen sich entweder auf MSDN die Artikel durchzulesen (einfach mal nach msdn und LINQ googlen, die ersten 3 Treffer) oder sich das Buch Datenbank-Programmierung mit Visual C# 2012 von Microsoft Press (gibts auch in VB.NET ;D) zuzulegen. Wie im Titel bereits verraten, es soll nur ein Einstieg sein - das Ganze in einem Tut zu verpacken ist IMHO unmöglich, dafür ist LINQ zu groß.

      Danke für's lesen und viel Spaß :D
      Lg Radinator

      ~blaze~: Thema verschoben
      In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „~blaze~“ ()

      Was imho noch zu sagen wäre: Wenn z.B. die ​First-Methode kein Element findet, das das Predicate erfüllt, fliegt eine ​InvalidOperationException. Um das zu vermeiden und in so einem Fall einfach den Standardwert (für Referenztypen ​null bzw. ​Nothing) zu erhalten, gibt es dann die Methode ​FirstOrDefault. Das Gleiche natürlich bei ​Last mit ​LastOrDefault usw.

      Und evtl. würde ich mir wünschen, dass nicht nur hauptsächlich auf die Extensions eingegangen wird, sondern auch ein wenig mehr auf die entsprechenden Keywords (​select, ​from, ​where, ...), die LINQ ja auch ausmachen. Ein Beispiel finde ich da ein bisschen wenig, selbst wenn es nur grundlegend ist.

      ​SelectMany und ​SequenceEqual finde ich noch recht praktisch, sodass Du die evtl. noch hinzufügen könntest.

      Grüße
      #define for for(int z=0;z<2;++z)for // Have fun!
      Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

      Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
      Da fehlen ja einige der wichtigsten Funktionen wie z.B. SelectMany, FirstOrDefault,...
      Dazu kommt, dass einige Beispiele wirklich schlecht gewählt sind. z.B. beim Select... was soll das sein? a) kompiliert dir das Beispiel nichtmal, b) ist das sicherlich nicht der Sinn und Zweck des Selects. Der Select soll etwas zurückgeben und nicht den Parameter befüllen.
      Und was die ganzen ToArray, ToList, ToDictionary,... Funktionen angeht, wäre schon auch der Hintergrund dieser etwas zu erläutern. Kommt Linq z.B. in Kombination mit dem EntityFramework zum Einsatz(was ein sehr sehr häufiger Anwendungsfall ist), dann ist das genau der Moment wo die eine Abfrage aufgebaut wird und diese an die Datenbank versendet wird. Hat vor allem die Performance große Auswirkungen, wann ein solcher Call erfolgt.
      Allgemein: Bei ToArray, ToList etc. wird der Enumerator, der bis zu diesem Zeitpunkt beliebig aufgebaut werden kann, durchlaufen. Ist auch einer der riesen Vorteile: Du kannst damit z.B. SQL-Queries im Code dynamisch aufbauen, den Enumerator an eine andere Methode übergeben, die klatscht nochmal Funktionen drüber, gibt das zurück, du rufst ToList auf und intern werden aus allen angefügten Funktionen automatisch die SQL-Queries generiert.

      Deine Vorstellung von Linq ist schon sehr sehr bescheiden und geht kaum auf die Möglichkeiten ein.

      Und wenn wir schon dabei sind... solche Dinge:

      VB.NET-Quellcode

      1. ​{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

      bitte gleich mit

      VB.NET-Quellcode

      1. Enumerable.Range
      erzeugen.

      Bitte zukünftig den Beitrag nach der Freischaltung bearbeiten. Beiträge zusammengefügt. ~Trade


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Trade“ ()

      @Trade: Post wird entsprechend editiert. Danke für die Hinweise :D!
      Linq-Abfrage mit from und so werde ich noch einbauen. Zu den SequenzEqual: Wenn du der Meinung bist, dass das auch noch recht sinnvol ist, werde ich es gerne einbauen. Habs selber (leider) noch nicht verwendet. Bis dahin, wen es interessiert: MSDN hat da Beispiele

      thefiloe schrieb:

      Da fehlen ja einige der wichtigsten Funktionen wie z.B. SelectMany, FirstOrDefault,...
      Ok war mir nicht bewusst, dass SelectMany und FirstOrDefault einen derartig hohen Stellenwert haben (FirstOrDefault, gebe ich zu, braucht man doch hin und wieder). Posting (wird) entsprechend editiert.

      thefiloe schrieb:

      Sinn und Zweck des Selects
      Für was denn dann, als für das Bearbeiten von IEnumerables ? Ich habe fast genau die selbe Funktionalität in meinem Abschlussprojekt Ausbildung zum FIAN gebraucht. Dabei habe ich in einem String alle Vorkommen eines Leerzeichens durch eine '0' ersetzt. Gut in dem Beispiel habe ich den Fehler gemacht, dass hier der Compiler einen Vergleich annimmt (doofes "=" für Vergleich und Zuweisung in VB...C# kennt einen Unterschied zwischen = und ==). Posting entsprechend editiert.

      Zu den ToList, ToArray und Konsorten: Da es sich hier in meinen Augen um ein Einsteiger-Tut handelt, habe ich besagte Methoden übersprungen. Wenn es dir aber am Herzen liegt, dass der Hintergrund genannt wird, werde ich ihn in den Text mit übernehmen. Und das mit dem Enumerable.Range war ja auch nur der Einstieg. Wie gesagt: Ein Anfänger würde es so machen. Die Enumearable.Range-Variante ist halt dann für die Leute, die das schon öfters machen. Wobei ich zugeben muss, dass ich den Weg noch nie gebraucht habe.

      Deine Aussage über meine Vorstellungen nehme ich dir nicht übel, da ich Linq bisher nur in rudimentärer Form (hin und wieder eine Liste Linq-en oder ein bisschen mit BindungSource, BindungList und Co arbeite, erstreckt sich mein Fundus an Kenntnissen von Linq halt nur über begrenzten Bereich. Ich bin aber sowieso immer auf der Suche nach neuen Anwendungsfällen.

      Lg Radinator
      In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
      Select projiziert entsprechende Elemente, die Du auswählst, in ein neues Enumerable. Somit kannst Du bspw. die Nachnamen aller Mitarbeiter in ein Enumerable packen:

      VB.NET-Quellcode

      1. Dim ma As New List(Of Mitarbeiter)
      2. ' Füllen der Liste...
      3. Dim nachnamen As IEnumerable(Of String) = ma.Select(Function(m) m.Nachname)
      4. 'Mitarbeiter:
      5. Public Class Mitarbeiter
      6. Public Property ID As Integer
      7. Public Property Nachname As String
      8. Public Property Vorname As String
      9. Public Property Kuerzel As String
      10. End Class


      So müsste das in VB.NET gehen, aber keine Gewähr.
      Natürlich kannst Du z. B. auch sowas machen:

      VB.NET-Quellcode

      1. Dim squares As IEnumerable(Of Integer) = _
      2. Enumerable.Range(1, 10).Select(Function(x) x * x)

      Quelle: msdn.microsoft.com/de-de/libra…cs-lang=vb#code-snippet-2

      Andernfalls gibt es eben SelectMany. Stell Dir vor, Mitarbeiter hat noch 'ne Property Kunden vom Typ IEnumerable(Of Kunde), die ihm zugewiesen sind.
      Nun würde beim Aufruf von Select folgendes herauskommen: IEnumerable(Of IEnumerable(Of Kunde))
      Das ist natürlich suboptimal. Mit SelectMany wird das ganze nun abgeflacht, sodass man nur ein IEnumerable(Of Kunde) erhält.

      Dein Beispiel oben ist in der Tat ein wenig unglücklich. Der Wert für das Kuerzel sollte innerhalb der Klasse selbst gesetzt werden (bspw. in einem überladenen Konstruktor, wo man die weiteren Parameter angibt) und nicht extern über LINQ, wenn man schon eine ganze Collection hat. Die Extension soll ja, wie thefiloe sagte, Daten (in anderer Form) herausholen/extrahieren und nicht irgendwelche Properties setzen.

      Grüße
      #define for for(int z=0;z<2;++z)for // Have fun!
      Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

      Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Trade“ () aus folgendem Grund: Ausgeweitet