LINQ

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche
Dieser Artikel oder Abschnitt bedarf einer Überarbeitung. Näheres ist auf der Diskussionsseite angegeben. Hilf mit, ihn zu verbessern, und entferne anschließend diese Markierung.

LINQ (Abkürzung für Language Integrated Query; Aussprache Link) ist ein programmtechnisches Verfahren von Microsoft zum Zugriff auf Daten. LINQ wurde federführend von Erik Meijer entwickelt.[1]

Ziel von LINQ[Bearbeiten]

Die Daten, auf die ein Programm zugreift, stammen aus unterschiedlichen Quellen.[2] Dazu gehören[3]

Jede dieser Datenquellen hat ihre eigenen Zugriffsmethoden. Dies führt dazu, dass sich der Programmierer in jedem Einzelfall mit den Details der jeweiligen Methode beschäftigen muss. Ferner muss jedes Mal das Programm geändert werden, wenn Daten aus einer Quelle in eine Quelle eines anderen Typs verschoben werden.

LINQ versucht dieses Problem zu beseitigen, indem es innerhalb der Entwicklungsplattform .NET eine einheitliche Methode für jeglichen Datenzugriff zur Verfügung stellt. Die Syntax der Abfragen in LINQ ist ähnlich der von SQL. Im Unterschied zu SQL stellt LINQ jedoch auch Sprachelemente zur Verfügung, die zum Zugriff auf hierarchische und Netzwerk-Strukturen geeignet sind, indem sie die dort vorhandenen Beziehungen ausnutzen.[4]

Arbeitsweise[Bearbeiten]

Wichtige LINQ-Anbieter[5]

LINQ ist eine Sammlung von Erweiterungsmethoden, die auf Monaden operieren.[6][7] Zudem gibt es in einigen .NET Sprachen wie C#, VB.NET und F# eigene Schlüsselwörter für eine vorbestimmte Menge an LINQ-Methoden.[8][9] Monaden werden in .NET als generische Klassen oder Interfaces mit einzelnem Typargument (z. B. IEnumerable<T>, IObservable<T>) abgebildet.

LINQ-Anweisungen sind unmittelbar als Quelltext in .NET-Programme eingebettet.[10] Somit kann der Code durch den Compiler auf Fehler geprüft werden. Andere Verfahren wie ADO und ODBC hingegen verwenden Abfragestrings. Diese können erst zur Laufzeit interpretiert werden; dann wirken Fehler gravierender und sind schwieriger zu analysieren.

Innerhalb des Quellprogramms in C# oder VB.NET präsentiert LINQ die Abfrage-Ergebnisse als streng typisierte Aufzählungen.[11] Somit gewährleistet es Typsicherheit bereits zur Übersetzungszeit.

Sogenannte LINQ-Anbieter[2] (engl: LINQ provider) übersetzen die LINQ-Anweisungen in die speziellen Zugriffsmethoden der jeweiligen Datenquelle. Innerhalb der .NET-Plattform stehen unter anderem folgende Anbieter zur Verfügung:[12]

  • LINQ to Objects zum Zugriff auf Objektlisten und -Hierarchien im Arbeitsspeicher
  • LINQ to SQL zur Abfrage und Bearbeitung von Daten in MS-SQL-Datenbanken
  • LINQ to Entities zur Abfrage und Bearbeitung von Daten im relationalen Modell von ADO.NET;[13]
  • LINQ to XML zum Zugriff auf XML-Inhalte
  • LINQ to DataSet zum Zugriff auf ADO.NET-Datensammlungen und -Tabellen
  • LINQ to SharePoint zum Zugriff auf SharePoint-Daten.[14]

Wichtige Konzepte[Bearbeiten]

Latente Evaluierung[Bearbeiten]

LINQ-Ausdrücke werden nicht bei ihrer Definition ausgeführt, sondern wenn der Wert abgefragt wird. Dies wird als Latente Evaluierung (englisch: deferred evaluation oder lazy evaluation) bezeichnet. Dadurch kann die Abfrage auch mehrfach verwendet werden.

List<int> Numbers = new List<int>() {1,2,3,4}; // list with 4 numbers
 
// query is defined but not evaluated
var query = from x in Numbers
            select x;
 
Numbers.Add(5); // add a 5th number
 
// now the query gets evaluated
Console.WriteLine(query.Count()); // 5
 
Numbers.Add(6); // add a 6th number
 
Console.WriteLine(query.Count()); // 6

Die latente Evaluierung kann auf verschiedene Weisen implementiert werden. Beispielsweise verwendet LINQ to Objects Delegates, während LINQ to SQL stattdessen das IQueryable<T>-Interface implementiert. Um die Veränderung des Ergebnisses der Abfrage zu verhindern, muss diese in einen anderen Typ konvertiert (englisch: conversion) werden. Hierzu dienen Konvertierungsmethoden wie AsEnumerable(), ToArray(), ToList(), ToDictionary(), ToLookup(), OfType(), Cast(), usw..

Falls die LINQ-Abfrage eine Funktion aufruft, die eine Ausnahmebehandlung erfordert, so muss der Try-Catch-Block die Verwendung der Abfrage umklammern und nicht deren Erstellung.

Auflösung von Erweiterungsmethoden[Bearbeiten]

LINQ-Funktionen werden als Erweiterungsmethoden (englisch: extension method) implementiert. Gegeben sei z. B. eine Klasse, welche eine Liste von Angestellten repräsentiert:

public sealed class Employees : List<Employee> 
{ 
   public Employees(IEnumerable<Employee> items) : base(items) { }
}

Über eine Erweiterungsmethode kann nun etwa die Where()-Methode implementiert werden:

public static class EmployeeExtension 
{
   public static IEnumerable<Employee> Where(this Employees source, Func<Employee, bool> predicate) 
   {
      return (source.AsEnumerable().Where(predicate)); 
   }
 
   public static IEnumerable<Employee> Where(this Employee source, Func<Employee, int, bool> predicate)
   {
      return (source.AsEnumerable().Where(predicate));
   }
}

Bestimmte .NET Sprachen besitzen eigene Schlüsselwörter um die Methoden aufzurufen:

var query = 
   from e in employees
   where e.DepartmentId == 5
   select e;

Diese Schlüsselwörter werden vom Compiler in die entsprechenden Methodenaufrufe aufgelöst:

var query = employees.Where(e => e.DepartmentId == 5).Select(e => e);

Da es sich um Erweiterungsmethoden handelt, muss der Compiler die Methodenaufrufe in einen Aufruf der Methoden der passenden Erweiterungsklasse auflösen:

var query = Enumerable.Select(EmployeeExtension.Where(developers, e => e.DepartmentId == 5), e => e);


Wichtige Operatoren[Bearbeiten]

From[Bearbeiten]

From definiert die Datenquelle einer Abfrage (query) oder Unterabfrage (subquery), sowie eine Bereichsvariable (range variable) die ein einzelnes Element der Datenquelle (data source) repräsentiert.

from rangeVariable in dataSource
// ...

Abfragen können mehrere from-Operationen besitzen, um Joins von mehreren Datenquellen zu ermöglichen. Hierbei gilt zu beachten, dass die Join-Bedingung bei mehreren from-Operationen durch die Datenstruktur definiert wird und sich vom Konzept eines Joins in Relationalen Datenbanken unterscheidet.

var queryResults = 
   from c in customers 
   from o in orders
   select new { c.Name, o.OderId, o.Price };

oder kürzer:

var queryResults = 
   from c in customers, o in orders
   select new { c.Name, o.OderId, o.Price };

Where[Bearbeiten]

Where definiert einen Filter auf den auszuwählenden Daten.

var queryResults = 
   from c in customers 
   from o in orders
   where o.Date > DateTime.Now - TimeSpan(7,0,0,0) // only orders from last week
   select new { c.Name, o.OderId, o.Price };

Select[Bearbeiten]

Select definiert die Form des Ergebnisses der LINQ-Abfrage.

Group[Bearbeiten]

Group wird verwendet um Elemente nach einem bestimmten Schlüssel zu gruppieren:

var groupedEmployees = 
   from e in Employees
   group e by e.Department // group first by department
   group e by e.Age // then group by age
   select e;

Als Schlüssel kann auch ein anonymer Typ verwendet werden, der sich aus mehreren Schlüsseln zusammensetzt:

var groupedEmployees = 
   from e in Employees
   group e by new { e.Department , e.Age } // group by department and age
   select e;

Into[Bearbeiten]

Into kann verwendet werden um das Ergebnis einer select, group oder join-Operation in einer temporären Variable zu speichern.

var groupedEmployees = 
   from e in Employees
   group e by e.Department into EmployeesByDepartment
   select new { Department = EmployeesByDepartment.Key, EmployeesByDepartment.Count() };

Orderby und ThenBy[Bearbeiten]

Orderby und ThenBy wird verwendet um eine Liste von Elementen in aufsteigender Reihenfolge zu sortieren.

var groupedEmployees = 
   from e in Employees
   orderby e.Age // order employees by age; youngest first
   thenby e.Name // order same-age employees by name; sort A-to-Z
   select e;

Mit Hilfe von OrderByDescending und ThenByDescending wird die Liste in absteigender Reihenfolge sortiert:

var groupedEmployees = 
   from e in Employees
   orderby e.Age descending // oldest first
   thenby e.Name descending // sort Z-to-A
   select e;

Reverse[Bearbeiten]

Reverse kehrt die Reihenfolge der Elemente um.

Join[Bearbeiten]

Join ermöglicht Inner Joins, Group Joins und Left Outer Joins.

Inner Join
Ein Inner Join bildet die äußere Datenquelle auf die innere Datenquelle ab und liefert ein „flaches“ Ergebnis zurück. Elemente der äußeren Datenquelle, zu denen kein passendes Element der inneren Datenquelle existiert, werden verworfen.
var productCategories = 
   from c in categories // outer datasource
   join p in products // inner datasource
   on c.CategoryId equals p.CategoryId // categories without products are ignored
   select new { c.CategoryName, p.ProductName };
Group Join
Ein Group Join erzeugt eine hierarchische Ergebnismenge. Hierbei werden die Elemente der inneren Datenquelle mit den entsprechenden Elementen der äußeren Datenquelle gruppiert. Elemente zu denen kein entsprechendes Element der äußeren Datenquelle existiert, werden mit einem leeren Array verbunden.
var productCategories = 
   from c in categories
   join p in products
   on c.CategoryId equals p.CategoryId
   into productsInCategory
   select new { c.CategoryName, Products = productsInCategory };
Ein Group Join ist in SQL nicht abbildbar, da SQL keine hierarchische Ergebnismenge zulässt. VB.NET besitzt mit Group Join ein eigenes Schlüsselwort.
Left Outer Join
Ein Left Outer Join bildet die äußere Datenquelle auf die innere Datenquelle ab und liefert ein „flaches“ Ergebnis zurück. Elemente der äußeren Datenquelle, zu denen kein passendes Element der inneren Datenquelle existiert, werden mit einem Standardwert versehen. Um den Standardwert zu definieren wird die DefaultIfEmpty()-Erweiterungsmethode verwendet.
var productCategories = 
   from c in categories
   join p in products
   on c.CategoryId equals p.CategoryId
   into productsInCategory
   from pic in productsInCategory.DefaultIfEmpty( 
      new Product(CategoryId = 0, ProductId = 0, ProductName = String.Empty)) 
   select new { c.CategoryName, p.ProductName };

Let[Bearbeiten]

Let ermöglicht es das Ergebnis einer Teilabfrage in einer Variable zu speichern um diese in später in der Abfrage verwenden zu können.

var ordersByProducts = 
   from c in categories
   join p in products 
   on c.CategoryId equals p.CategoryId
   into productsByCategory
   let ProductCount = productsByCategory.Count() 
   orderby ProductCount
   select new { c.CategoryName, ProductCount };

Any[Bearbeiten]

Any wird verwendet um festzustellen, ob eine Sequenz leer ist oder ein bestimmtes Prädikat enthält.

bool containsAnyElements = Enumerable.Empty<int>().Any(); // false 
bool containsSix = Enumerable.Range(1,10).Any(x => x == 6); // true

Contains[Bearbeiten]

Contains wird verwendet um festzustellen, ob ein bestimmter Wert in einer Sequenz enthalten ist.

bool containsSix = Enumerable.Range(1,10).Contains(6); // true

Skip und Take[Bearbeiten]

Skip wird verwendet um eine bestimmte Anzahl von Elementen einer Sequenz zu überspringen. Take wird verwendet um eine maximale Anzahl von Elementen einer Sequenz auszuwählen.

IEnumerable<int> Numbers = Enumerable.Range(1,10).Skip(2).Take(5); // {3,4,5,6,7}

Zusätzlich sind die Erweiterungsmethoden SkipWhile() und TakeWhile() definiert, für die in VB.NET eigene Schlüsselwörter definiert sind. Diese Methoden erlauben die Verwendung eines Prädikats, welches definiert welche Elemente übersprungen bzw. ausgewählt werden.

Distinct[Bearbeiten]

Distinct wird verwendet um eindeutige Elemente einer Sequenz auszuwählen.

IEnumerable<int> MultipleNumbers = new List<int>() {0,1,2,3,2,1,4}; 
IEnumerable<int> DistinctNumbers = MultipleNumbers.Distinct(); // {0,1,2,3,4}

Union, Intersect und Except[Bearbeiten]

Für eine Liste von Elementen können die Mengenoperatoren Union, Intersect und Except eingesetzt werden:

var NumberSet1 = {1,5,6,9};
var NumberSet2 = {4,5,7,11};
 
var union = NumerSet1.Union(NumberSet2); // 1,5,6,9,4,7,11
var intersect = NumerSet1.Intersect(NumberSet2); // 5
var except = NumerSet1.Except (NumberSet2); // 1,6,9

Aggregate[Bearbeiten]

Aggregate wird verwendet um eine Aggregat-Funktion auf eine Datenquelle anzuwenden.

var nums = new[]{1,2,3,4,5};
var sum = nums.Aggregate( (a,b) => a + b); // sum = 1+2+3+4+5 = 15

Zudem sind wichtige Aggregat-Funktionen vordefiniert. Vordefinierte Aggregat-Funktionen sind etwa Count(), LongCount(), Sum(), Min(), Max() und Average().

Zip[Bearbeiten]

Zip kombiniert zwei Sequenzen miteinander bis eine Sequenz zu Ende ist.

IEnumerable<string> Days = new List<string>() { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; 
IEnumerable<int> Numbers = Enumerable.Range(1,10); // {1,2,3,4,5,6,7,8,9,10}
// Numbers 8..10 will be ignored
IEnumerable<string> NumberedDays = Days.Zip(Numbers, (day, number) => String.Format("{1}:{2}", number, day) ); // {"1:Monday", "2:Tuesday", ..., "7:Sunday"}

Concat[Bearbeiten]

Concat hängt an eine Sequenz eine weitere Sequenz gleichen Typs.

SequenceEqual[Bearbeiten]

SequenceEqual prüft ob zwei Sequenzen die gleiche Länge aufweisen und ob die Elemente an der jeweiligen Position der entsprechenden Sequenzen gleich sind. Zum Vergleich wird entweder das IEqualityComparer<T> Interface, die Equals()-Methode von TSource, oder die GetHashCode()-Methode abgefragt.

SelectMany[Bearbeiten]

SelectMany[15] wird im Wesentlichen dazu eingesetzt eine Hierarchie abzuflachen. SelectMany funktioniert hierbei wie der Bind-Operator >>=, auch shovel (Schaufel) genannt, in Haskell.

class Book
{
   public string Title { get; set; }
   public List<Author> Authors { get; set; }
}
class Author
{
   public string Name { get; set; }
}
class Foo
{
   public IEnumerable<string> GetAutorsFromBooks(IEnumerable<Book> books) 
   {
      // Input-Monad:  Enumerable Book Author
      // Output-Monad: Enumerable Author
      return books.SelectMany( book => book.Authors); 
   }
}

Ein typischer Anwendungsfall für die Abflachung einer Hierarchie ist es alle Dateien in einem Verzeichnis, sowie den Unterverzeichnissen des Verzeichnisses, aufzulisten.

IEnumerable<string> GetFilesInSubdirectories(string rootDirectory)
{
   var directoryInfo = new DirectoryInfo(rootDirectory); // get the root directory
   return directoryInfo.GetDirectories() // get directories in the root directory
                       .SelectMany(dir => GetFilesInSubdirectories(dir.FullName)) // recursively flatting the hierarchy of directories
                       .Concat(directoryInfo.GetFiles().Select(file => file.FullName)); // get the file name for each file in the directories
}

Skalar-Selektoren[Bearbeiten]

LINQ definiert verschiedene Selektoren für Skalare Ergebnisse:

skalare LINQ Selektoren
Methode Ergebnis
Single() Gibt das eine Element zurück, welches die Anfrage liefert. Wirft eine Exception, falls keine oder mehrere Ergebnisse zurückgeliefert werden.
SingleOrDefault() Gibt das eine Element zurück, welches die Anfrage liefert. Gibt null zurück, falls keine Ergebnisse geliefert werden. Wirft eine Exception, falls mehrere Ergebnisse zurückgeliefert werden.
First() Gibt das erste Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Wirft eine Exception, falls keine Ergebnisse zurückgeliefert werden.
FirstOrDefault() Gibt das erste Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Gibt den Standardwert zurück, falls keine Ergebnisse zurückgeliefert werden.
Last() Gibt das letzte Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Wirft eine Exception, falls keine Ergebnisse zurückgeliefert werden.
LastOrDefault() Gibt das letzte Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Gibt den Standardwert zurück, falls keine Ergebnisse zurückgeliefert werden.
ElementAt(n) Gibt das n-te Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Wirft eine Exception, falls weniger als n Ergebnisse zurückgeliefert werden.
ElementAtOrDefault(n) Gibt das n-te Element zurück, falls die Anfrage ein oder mehrere Ergebnisse liefert. Gibt den Standardwert zurück, falls weniger als n Ergebnisse zurückgeliefert werden.

Erweitern von LINQ[Bearbeiten]

Definition eigener Monaden[Bearbeiten]

LINQ kann auf beliebige Monaden angewendet werden. Monaden sind hierbei Adapter (englisch: wrapper) für einen bestimmten Typ. Vordefinierte Monaden sind z. B. IEnumerable<T>, IList<T>, Nullable<int> und Task<T>.

Jedoch können auch eigene Monaden wie z. B. IRepository<T> oder IHandler<T> erstellt werden, um die Funktionalität von LINQ zu erweitern. Hierfür müssen passende Erweiterungsmethoden definiert werden. Die Verwendung von Monaden dient hierbei dazu die Menge an Boilerplate-Code zu reduzieren.

Identität[Bearbeiten]

Die einfachste Monade ist die Identität, welche in .NET üblicherweise als Identity<T> bezeichnet wird:

public class Identity<T>
{
    public T Value { get; private set; }
 
    public Identity(T value)
    {
        Value = value;
    }
}

Für diese Klasse lassen sich nun die folgenden Erweiterungsmethoden erstellen:

// Unit-Methode
// Konvertiert einen beliebigen Wert in eine Identität
public static Identity<T> ToIdentity<T>(this T value)
{
   return new Identity<T>(value);
}
 
// Bind-Methode
// Verknüpft Funktionen die eine Identität zurückgeben
public static Identity<B> Bind<A, B>(this Identity<A> m, Func<A, Identity<B>> f)
{
    return f(m.Value);
}

Diese Monade kann nun als Lambda-Ausdruck (als Arbitrary Composition) verwendet werden:

var hello = "Hello".ToIdentity().Bind(h => "Monad".ToIdentity().Bind( m => String.Format("{1} {2}!", h, m) ));
 
Console.WriteLine(hello.Value); // "Hello Monad!"

Um die Monade in LINQ verwenden zu können, muss eine SelectMany()-Erweiterungsmethode implementiert werden. Diese ist lediglich um einen Alias für Bind(). Es besteht daher die Möglichkeit

  1. die Bind()-Methode umzubenennen bzw. zu kapseln
  2. die Bind()-Methode mit Funktionskomposition zu erstellen und umzubenennen bzw. zu kapseln
  3. beides:
// SelectMany = Bind
public static Identity<B> SelectMany<A, B>(this Identity<A> m, Func<A, Identity<B>> f)
{
    return Bind(m, f);
    // alternativ mit aufgelöstem Bind(): 
    // return f(m.Value);
}
 
// Bind mit Funktionskomposition 
public static Identity<C> SelectMany<A, B, C>(this Identity<A> m, Func<A, Identity<B>> f, Func<A, B, C> select)
{
    return select(m.Value, m.Bind(f).Value).ToIdentity();
 
    // alternativ mit aufgelöstem Bind(): 
    // return select(m.Value, f(m.Value).Value).ToIdentity();
}

Die Monade kann nun mit Hilfe von LINQ-Schlüsselwörtern verarbeitet werden:

var hello = from h in "Hello".ToIdentity()
            from m in "Monad".ToIdentity()
            select String.Format("{1} {2}!", h, m);
 
Console.WriteLine(hello.Value); // "Hello Monad!"

Maybe[Bearbeiten]

Eine weitere einfache Monade ist Maybe<T>, welche ähnlich funktioniert wie die Nullable<T>-Struktur[16][17]. Die Maybe-Monade lässt sich hierbei auf verschiedene Arten implementieren:

Variante 1
HasValue-Eigenschaft bestimmt ob Maybe Nothing (dh. leer) ist.

Definition der Monade:

class Maybe<T>
{
    public readonly static Maybe<T> Nothing = new Maybe<T>();
 
    public T Value { get; private set; }
 
    public bool HasValue { get; private set; }
 
    Maybe()
    {
        HasValue = false;
    }
 
    public Maybe(T value)
    {
        Value = value;
        HasValue = true;
    }
 
    public override string ToString()
    {
        return (HasValue) ? Value.ToString() : String.Empty;
    }
}

Definition der Unit-Methode:

public static Maybe<T> ToMaybe<T>(this T value)
{
    return new Maybe<T>(value);
}

Definition der Bind-Methode:

private static Maybe<U> Bind<T, U>(this Maybe<T> m, Func<T, Maybe<U>> f)
{
    return (m.HasValue) ? f(m.Value) : Maybe<U>.Nothing;
}
 
public static Maybe<U> SelectMany<T, U>(this Maybe<T> m, Func<T, Maybe<U>> f)
{
    return Bind<T, U>(m, f);
}
 
public static Maybe<C> SelectMany<A, B, C>(this Maybe<A> m, Func<A, Maybe<B>> f, Func<A, B, C> select)
{
    return m.Bind(x => f(x).Bind(y => select(x,y).ToMaybe()));
}

Verwendung:

// null-propagation of nullables 
var r = from x in 5.ToMaybe()
        from y in Maybe<int>.Nothing
        select x + y;
 
Console.WriteLine(r.Value); // String.Empty
Variante 2
konkreter Typ bestimmt ob Nothing oder Something

Definition der Monade:

public interface Maybe<T>{}
 
public class Nothing<T> : Maybe<T>
{
    public override string ToString()
    {
        return String.Empty;
    }
}
 
public class Something<T> : Maybe<T>
{
    public T Value { get; private set; }
 
    public Something(T value)
    {
        Value = value;
    }
 
    public override string ToString()
    {
        return Value.ToString();
    }
}

Definition der Unit-Methode:

public static Maybe<T> ToMaybe<T>(this T value)
{
    return new Something<T>(value);
}

Definition der Bind-Methode:

private static Maybe<B> Bind<A, B>(this Maybe<A> m, Func<A, Maybe<B>> f)
{
    var some = m as Something<A>;
    return (some == null) ? new Nothing<B>() : f(some.Value);
}
 
public static Maybe<B> SelectMany<A, B>(this Maybe<A> m, Func<A, Maybe<B>> f)
{
    return Bind<A, B>(m, f);
}
 
public static Maybe<C> SelectMany<A, B, C>(this Maybe<A> m, Func<A, Maybe<B>> f, Func<A, B, C> select)
{
    return m.Bind(x => f(x).Bind(y => select(x,y).ToMaybe()));
}

Verwendung:

var r = from x in 5.ToMaybe() // Something<int>
        from y in new Nothing<int>()
        select x + y;
 
Console.WriteLine(r); // String.Empty

Definieren eigener Operatoren[Bearbeiten]

Operatoren in LINQ lassen sich erweitern, indem eine passende Erweiterungsmethode bereitgestellt wird. Hierbei können auch Standardoperatoren überschrieben werden.

Beispiel 1
Rückgabe von Personen, die zu einem bestimmten Datum Geburtstag haben.
public static class PersonExtensions 
{
   public static IEnumerable<TPerson> FilterByBirthday<TPerson>(this IEnumerable<TPerson> persons) where TPerson : Person
   {
      return FilterByBirthday(persons, DateTime.Now); 
   }
 
   public static IEnumerable<TPerson> FilterByBirthday<TPerson>(this IEnumerable<TPerson> persons, DateTime date) where TPerson : Person
   {
      var birthdayPersons = select p in persons
                            where p.Birthday.Day == date.Day
                            where p.Birthday.Month == date.Month
                            select p;
 
      // return the list of persons 
      foreach(Person p in youngestPersons)
         yield return p;
   }
}
Aufrufen der neuen Erweiterungsmethode
personsToCongratulate = persons.FilterByBirthday();
Beispiel 2
Definition einer Methode, welche die Menge der ältesten Personen einer Liste zurückliefert. Es soll zudem eine Delegate-Funktion angegeben werden können, um etwa verstorbene Personen auszufiltern.
public static class PersonExtensions 
{
   public static IEnumerable<TPerson> Oldest<TPerson>(this IEnumerable<TPerson> source, Func<TPerson, Boolean> predicate) where TPerson : Person
   {
      // filter Persons for criteria 
      var persons = from p in source
                    where predicate(p)
                    select p;
 
      // determine the age of the oldest persons
      int oldestAge = (from p in persons
                       orderby p.Age descending
                       select p.Age).First();
 
      // get the list of the oldest persons 
      var oldestPersons = select p in persons
                          where p.Age == youngestAge
                          select p;
 
      // return the list of youngest persons 
      foreach(Person p in oldestPersons)
         yield return p;
   }
 
   public static IEnumerable<TPerson> Oldest(this IEnumerable<TPerson> source) where TPerson : Person
   {
      return Oldest(source, x => true);
   }  
}
Aufrufen der neuen Erweiterungsmethode
oldestLivingPersons = persons.Oldest(p => p.Living == true);

Implementierung eigener LINQ-Provider[Bearbeiten]

Das schreiben eigener LINQ-Provider bietet sich an, wenn ein Service aufgerufen werden soll, welches eine bestimmte Syntax (SQL, XML, etc.) verlangt. Um dies zu ermöglichen muss das IQueryable-Interface implementiert werden. Über dieses Interface kann der LINQ-Ausdrucksbaum analysiert und in das passende Zielformat umgewandelt werden.


Reactive Extensions[Bearbeiten]

Reactive Extensions (kurz: Rx) ist eine Erweiterung von LINQ, welche statt auf IEnumerable<T> auf IObservable<T> arbeitet. Es handelt sich dabei um eine Implementierung des Beobachter-Entwurfsmusters.

Beispiele[Bearbeiten]

LINQ to DataSet[Bearbeiten]

Folgendes Beispiel zeigt die Abfrage einer Tabelle mit Linq. Vorausgesetzt wird eine bestehende Access-Datenbank unter dem Pfad: C:\database.mdb mit einer Tabelle Products, die die Felder ID, Name und EanCode enthält.

Die Tabelle wird als Klasse nachgebildet und mittels Attributen mit Metadaten versehen, die das Mapping auf die Datenbank beschreiben.

Dazu muss in der Projektmappe unter References ein Verweis auf die System.Data.Linq.Dll hinzugefügt werden.

    using System.Data.Linq.Mapping;
 
    [Table(Name = "Products")]
    class Product
    {
        [Column(Name = "id", IsPrimaryKey = true)]
        public int ID;
 
        [Column(Name = "Name")]
        public string Name;
 
        [Column(Name = "Ean")]
        public string EanCode;
    }

Nun kann man die Tabelle abfragen. In folgendem Beispiel werden alle Produkte aufgelistet, deren Produktbezeichnung mit einem A beginnt. Die Produkte werden nach ihrer ID sortiert.

using System.Configuration;  // for ConfigurationManager
using System.Data;           // for all interface types
using System.Data.Common;    // for DbProviderFactories
 
class Foo()
{
   public static void Main() {
      // get connection settings from app.config
      var cs = ConfigurationManager.ConnectionStrings["MyConnectionString"]; 
      var factory = DbProviderFactories.GetFactory(cs.ProviderName);
 
      using(IDbConnection connection = new factory.CreateConnection(cs.ConnectionString))
      {
         connection.Open();
 
         DataContext db = new DataContext(connection);
 
         Table<Product> table = db.GetTable<Product>();
 
         var query = from p in table
                     where p.Name.StartsWith("A")
                     orderby p.ID
                     select p;
 
         foreach (var p in query)
            Console.WriteLine(p.Name);
      }
   }
}

Alternativ können auch sogenannte Erweiterungsmethoden mit Lambda-Ausdrücken verwendet werden. In solche werden LINQ-Abfragen auch vom Compiler übersetzt.

  var query = products
                .Where(p => p.Name.StartsWith("A"))
                .OrderBy(p => p.ID);
 
  foreach (var product in query) {
      Console.WriteLine(product.Name);
  }

Mit der Funktion Single kann ein einzelner Datensatz ermittelt werden. Folgendes Beispiel ermittelt den Datensatz mit der ID 1.

   Console.WriteLine(products.Single(p => p.ID == 1).Name);

Falls aber die Abfrage mehrere Datensätze ermittelt, wird eine InvalidOperationException geworfen.

LINQ to XML[Bearbeiten]

Nachstehend ein Beispiel das zeigt wie LINQ verwendet werden kann um Informationen aus einer XML Datei auszulesen. Als XML Datei dient folgende XML Beispieldatei.[18]

<?xml version="1.0"?>
<!-- purchase_order.xml -->
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
  <Address Type="Shipping">
    <Name>Ellen Adams</Name>
    <Street>123 Maple Street</Street>
    <City>Mill Valley</City>
    <State>CA</State>
    <Zip>10999</Zip>
    <Country>USA</Country>
  </Address>
  <Address Type="Billing">
    <Name>Tai Yee</Name>
    <Street>8 Oak Avenue</Street>
    <City>Old Town</City>
    <State>PA</State>
    <Zip>95819</Zip>
    <Country>USA</Country>
  </Address>
  <DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
  <Items>
    <Item PartNumber="872-AA">
      <ProductName>Lawnmower</ProductName>
      <Quantity>1</Quantity>
      <USPrice>148.95</USPrice>
      <Comment>Confirm this is electric</Comment>
    </Item>
    <Item PartNumber="926-AA">
      <ProductName>Baby Monitor</ProductName>
      <Quantity>2</Quantity>
      <USPrice>39.98</USPrice>
      <ShipDate>1999-05-21</ShipDate>
    </Item>
  </Items>
</PurchaseOrder>

Wollte man beispielsweise die Artikelnummern (PartNumber) aller Einträge vom Typ <Item> auslesen, könnte man folgenden C# Code verwenden[19].

XElement purchaseOrder = XElement.Load("purchase_order.xml");
 
IEnumerable<string> items = 
   from item in purchaseOrder.Descendants("Item")
   select (string)item.Attribute("PartNumber");
 
foreach (var item in items)
{
   Console.WriteLine(partNumbers.ElementAt(i));
}
// Output:
// 872-AA
// 926-AA

Eine andere Möglichkeit, unter Zuhilfenahme einer bedingten Abfrage, wäre alle Artikel auszuwählen, deren Wert mehr als 100 Dollar beträgt. Zusätzlich könnte man das Resultat der Abfrage mittels orderby nach den Artikelnummern, sortieren.

XElement purchaseOrder = XElement.Load("purchase_order.xml");
 
IEnumerable<XElement> items = 
   from item in purchaseOrder.Descendants("Item")
   where (int)item.Element("Quantity") * (decimal)item.Element("USPrice") > 100
   orderby (string)item.Element("PartNumber")
   select item;
 
foreach(var item in items)
{
   Console.WriteLine(item.Attribute("PartNumber").Value);
}
// Output:
// 872-AA

LINQ mit Rx[Bearbeiten]

LINQ kann die Verwendung der Reactive Extensions (Rx) deutlich vereinfachen. Gegeben sei das folgende Programm:

class FizzBuzz : IObservable<string>, IObserver<int>
{
    #region IObservable<string>
    List<IObserver<string>> observers = new List<IObserver<string>>();
 
    public IDisposable Subscribe(IObserver<string> observer)
    {
        if (!observers.Contains(observer))
        {
            observers.Add(observer);
        }
        return new FizzBuzzDisposer(observers, observer);
    }
 
    private class FizzBuzzDisposer : IDisposable
    {
        private List<IObserver<string>> observers = new List<IObserver<string>>();
        private IObserver<string> observer;
 
        public FizzBuzzDisposer(List<IObserver<string>> observers, IObserver<string> observer)
        {
            this.observers = observers;
            this.observer = observer;
        }
 
        public void Dispose()
        {
            if (observers.Contains(observer))
                observers.Remove(observer);
        }
    }
    #endregion 
 
    #region IObserver<int>
    public void OnCompleted() { }
 
    public void OnError(Exception error) { }
 
    public void OnNext(int value)
    {
        foreach (IObserver<string> observer in observers)
        {
            observer.OnNext(IntToFizzBuzz(value));
        }
    }
    #endregion
 
    private string IntToFizzBuzz(int n)
    {
        return new StringBuilder(8)
            .Append((n % 2 == 0) ? "Fizz" : String.Empty)
            .Append((n % 5 == 0)? "Buzz" : String.Empty)
            .Append((n % 2 != 0 && n % 5 != 0)? n.ToString() : String.Empty)
            .ToString(); 
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        var numbers = System.Reactive.Linq.Observable.Range(1, 100);
        FizzBuzz fizzBuzz = new FizzBuzz();
        fizzBuzz.Subscribe(Console.WriteLine); // oder: fizzBuzz.Subscribe(str => Console.WriteLine(str));
        numbers.Subscribe(fizzBuzz.OnNext); // oder: numbers.Subscribe(n => fizzBuzz.OnNext(n));
    }
}

Mit Hilfe von LINQ kann dieses Programm stark vereinfacht geschrieben werden, da die Erstellung einer eigenen Klasse entfällt:

class Program
{
    static string IntToFizzBuzz(int n)
    {
        return new StringBuilder(8)
            .Append((n % 2 == 0) ? "Fizz" : String.Empty)
            .Append((n % 5 == 0)? "Buzz" : String.Empty)
            .Append((n % 2 != 0 && n % 5 != 0)? n.ToString() : String.Empty)
            .ToString(); 
    }
 
    static void Main(string[] args)
    {
        var numbers = System.Reactive.Linq.Observable.Range(1, 100);
        var fizzBuzz = from n in numbers select IntToFizzBuzz(n);
        fizzBuzz.Subscribe(Console.WriteLine); //oder: fizzBuzz.Subscribe(result => Console.WriteLine(result));
    }
}

Literatur[Bearbeiten]

  •  Özgür Aytekin: LINQ - Theorie und Praxis für Einsteiger. Addison-Wesley, München 2008, ISBN 9783827326164.
  •  Andreas Kühnel: Visual C# 2010. Galileo Press, Bonn 2010, ISBN 9783836215527, LINQ to Objects, S. 465–496.
  •  Paolo Pialorsi, Marco Russo: Datenbankprogrammierung mit Microsoft LINQ. Microsoft Press Deutschland, Unterschleißheim 2008, ISBN 9783866454286.
  •  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573.
  •  Paolo Pialorsi: Entwicklerbuch Microsoft SharePoint 2010. Microsoft Press Deutschland, Unterschleißheim 2011, ISBN 9783866455450, LINQ to SharePoint, S. 113–188.
  •  Jesse Liberty, Paul Betts: Programming Reactive Extensions and LINQ. Apress, 19. Oktober 2011, ISBN 9781430237471, S. 184.

Weblinks[Bearbeiten]

LINQ in anderen Programmiersprachen[Bearbeiten]

JavaScript, TypeScript
  • linq.js. In: CodePlex. Abgerufen am 3. April 2013 (englisch, LINQ für JavaScript).
  • JSINQ. In: CodePlex. Abgerufen am 3. April 2013 (englisch, LINQ to Objects für JavaScript).
Java
PHP
  • phinq. In: Google Code. Abgerufen am 3. April 2013 (englisch, LINQ für PHP).
  • PHPLinq. In: CodePlex. Abgerufen am 3. April 2013 (englisch, LINQ für PHP).
Python
  • asq. In: Google Code. Abgerufen am 3. April 2013 (englisch, Python Implementierung für LINQ to Objects und Parallel LINQ to Objects).

Einzelnachweise[Bearbeiten]

  1. Erik Meijer. Microsoft Research, abgerufen am 16. März 2013 (englisch).
  2. a b  Paolo Pialorsi: Entwicklerbuch Microsoft SharePoint 2010. Microsoft Press Deutschland, Unterschleißheim 2011, ISBN 9783866455450, LINQ to SharePoint, S. 115.
  3.  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573, S. 5.
  4.  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573, S. 8-17.
  5.  Paolo Pialorsi: Entwicklerbuch Microsoft SharePoint 2010. Microsoft Press Deutschland, Unterschleißheim 2011, ISBN 9783866455450, LINQ to SharePoint, S. 115 (In Anlehnung).
  6. Brian Beckman: Don't fear the Monad. In: Channel9. Microsoft, abgerufen am 20. März 2013.
  7. The Marvels of Monads. In: MSDN Blog. Microsoft, 10. Januar 2008, abgerufen am 20. März 2013.
  8. LINQ-Abfrageausdrücke (C#-Programmierhandbuch). In: MSDN. Microsoft, abgerufen am 21. März 2013.
  9. Query Expressions (F#). In: MSDN. Microsoft, abgerufen am 21. März 2013.
  10.  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573, S. 6-8.
  11.  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573, S. 7.
  12.  Paolo Pialorsi: Entwicklerbuch Microsoft SharePoint 2010. Microsoft Press Deutschland, Unterschleißheim 2011, ISBN 9783866455450, LINQ to SharePoint, S. 114.
  13.  Paolo Pialorsi, Marco Russo: Programming Microsoft LINQ in Microsoft .NET Framework 4. Microsoft Press, Sebastopol California 2010, ISBN 9780735640573, S. 241ff.
  14.  Paolo Pialorsi: Entwicklerbuch Microsoft SharePoint 2010. Microsoft Press Deutschland, Unterschleißheim 2011, ISBN 9783866455450, LINQ to SharePoint, S. 188ff.
  15. Enumerable.SelectMany-Methode. In: MSDN. Microsoft, abgerufen am 21. März 2013.
  16. Typen, die NULL-Werte zulassen (C#-Programmierhandbuch). In: MSDN. Microsoft, abgerufen am 21. März 2013.
  17. Nullable<T>-Struktur. In: MSDN. Microsoft, abgerufen am 21. März 2013.
  18. XML-Beispieldatei: Typischer Auftrag (LINQ to XML). MSDN, abgerufen am 16. März 2013.
  19. Übersicht über LINQ to XML. MSDN, abgerufen am 16. März 2013.
  20. Java Streams Preview vs .Net High-Order Programming with LINQ. Informatech Costa Rica, abgerufen am 3. April 2013 (englisch).