LINQ (Language Integrated Query)LINQ (Language Integrated Query)

Was ist das?What is it?

LINQ bietet Abfragefunktionen auf Sprachebene und eine API einer Funktion höherer Ordnung für C# und VB, sodass die Möglichkeit besteht, aussagekräftigen, deklarativen Code zu schreiben.LINQ provides language-level querying capabilities and a higher-order function API to C# and VB as a way to write expressive, declarative code.

Abfragesyntax auf Sprachebene:Language-level query syntax:

var linqExperts = from p in programmers
                  where p.IsNewToLINQ
                  select new LINQExpert(p);

Gleiches Beispiel mit der IEnumerable<T>-API:Same example using the IEnumerable<T> API:

var linqExperts = programmers.Where(p => IsNewToLINQ)
                             .Select(p => new LINQExpert(p));

LINQ ist ausdrucksstarkLINQ is Expressive

Nehmen Sie an, Sie haben eine Liste mit Haustieren, möchten diese aber in ein Wörterbuch konvertieren, damit Sie direkt über den RFID-Wert auf ein Haustier zugreifen können.Imagine you have a list of pets, but want to convert it into a dictionary where you can access a pet directly by its RFID value.

Herkömmlicher imperativer Code:Traditional imperative code:

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)
{
    petLookup.Add(pet.RFID, pet);
}

Der Zweck des Codes ist nicht, ein neues Dictionary<int, Pet> zu erstellen und es über eine Schleife zu erweitern, sondern eine vorhandene Liste in ein Wörterbuch zu konvertieren.The intention behind the code is not to create a new Dictionary<int, Pet> and add to it via a loop, it is to convert an existing list into a dictionary! Mit LINQ bleibt dieser Zweck erhalten, mit imperativem Code hingegen nicht.LINQ preserves the intention whereas the imperative code does not.

Entsprechender LINQ-Ausdruck:Equivalent LINQ expression:

var petLookup = pets.ToDictionary(pet => pet.RFID);

Der Code mit LINQ ist hilfreich, da er aus der Perspektive eines Programmierers einen Ausgleich zwischen Zweck und Code schafft.The code using LINQ is valuable because it evens the playing field between intent and code when reasoning as a programmer. Ein weiter Bonus ist die Kürze des Codes.Another bonus is code brevity. Stellen Sie sich vor, Sie könnten große Teile einer Codebasis um 1/3 reduzieren, wie oben dargestellt.Imagine reducing large portions of a codebase by 1/3 as done above. Klingt das nicht überzeugend?Pretty sweet deal, right?

LINQ-Anbieter vereinfachen den DatenzugriffLINQ Providers Simplify Data Access

Für einen erheblichen Teil der verwendeten Software dreht sich alles um den Umgang mit Daten aus unterschiedlichen Quellen (Datenbanken, JSON, XML usw.).For a significant chunk of software out in the wild, everything revolves around dealing with data from some source (Databases, JSON, XML, etc). Häufig gehört dazu das Erlernen einer neuen API für jede Datenquelle, was lästig sein kann.Often this involves learning a new API for each data source, which can be annoying. LINQ vereinfacht dies durch das Abstrahieren gemeinsamer Elemente des Datenzugriffs in eine Abfragesyntax, die immer gleich aussieht, unabhängig davon, welche Datenquelle Sie auswählen.LINQ simplifies this by abstracting common elements of data access into a query syntax which looks the same no matter which data source you pick.

Stellen Sie sich Folgendes vor: Sie möchten alle XML-Elemente mit einem bestimmten Attributwert finden.Consider the following: finding all XML elements with a specific attribute value.

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
                                           string attributeName, string value)
{
    return from el in documentRoot.Elements(elementName)
           where (string)el.Element(attributeName) == value
           select el;
}

Code zu schreiben, um das XML-Dokument zu diesem Zweck manuell zu durchlaufen, wäre eine wesentlich größere Herausforderung.Writing code to manually traverse the XML document to perform this task would be far more challenging.

LINQ-Anbieter ermöglichen nicht nur die Interaktion mit XML.Interacting with XML isn’t the only thing you can do with LINQ Providers. LINQ to SQL ist ein ziemlich reduzierter objektrelationaler Mapper (ORM) für eine MSSQL Server-Datenbank.Linq to SQL is a fairly bare-bones Object-Relational Mapper (ORM) for an MSSQL Server Database. Die JSON.NET-Bibliothek bietet einen effizienten Durchlauf von JSON-Dokumenten über LINQ.The JSON.NET library provides efficient JSON Document traversal via LINQ. Wenn es allerdings keine Bibliothek gibt, die Ihre Anforderungen erfüllt, können Sie auch Ihren eigenen LINQ-Anbieter schreiben.Furthermore, if there isn’t a library which does what you need, you can also write your own LINQ Provider!

Warum sollte die Abfragesyntax verwendet werden?Why Use the Query Syntax?

Dies ist eine Frage, die häufig gestellt wird.This is a question which often comes up. Im Grunde ist dies:After all, this,

var filteredItems = myItems.Where(item => item.Foo);

sehr viel präziser als dies:is a lot more concise than this:

var filteredItems = from item in myItems
                    where item.Foo
                    select item;

Ist die API-Syntax nicht nur eine präzisere Methode für die Abfragesyntax?Isn’t the API syntax just a more concise way to do the query syntax?

Nein.No. Die Abfragesyntax ermöglicht die Verwendung der let-Klausel, mit der Sie eine Variable im Bereich des Ausdrucks einführen und binden können, um sie in nachfolgenden Teilen des Ausdrucks zu verwenden.The query syntax allows for the use the let clause, which allows you to introduce and bind a variable within the scope of the expression, using it in subsequent pieces of the expression. Das Reproduzieren des gleichen Codes nur mit der API-Syntax ist möglich, führt jedoch wahrscheinlich zu Code, der schwer lesbar ist.Reproducing the same code with only the API syntax can be done, but will most likely lead to code which is hard to read.

Daher stellt sich die folgende Frage: Sollten Sie einfach die Abfragesyntax verwenden?So this begs the question, should you just use the query syntax?

Die Antwort auf diese Frage ist Ja, wenn...The answer to this question is yes if...

  • Ihre vorhandene Codebasis bereits die Abfragesyntax verwendetYour existing codebase already uses the query syntax
  • Sie den Bereich von Variablen in Ihren Abfragen aufgrund der Komplexität festlegen müssenYou need to scope variables within your queries due to complexity
  • Sie die Abfragesyntax bevorzugen und Sie dadurch nicht von Ihrer Codebasis abgelenkt werdenYou prefer the query syntax and it won’t distract from your codebase

Die Antwort auf diese Frage ist Nein, wenn...The answer to this question is no if...

  • Ihre vorhandene Codebasis die API-Syntax bereits verwendetYour existing codebase already uses the API syntax
  • Sie den Bereich von Variablen in Ihren Abfragen nicht festlegen müssenYou have no need to scope variables within your queries
  • Sie die API-Syntax bevorzugen und Sie dadurch nicht von Ihrer Codebasis abgelenkt werdenYou prefer the API syntax and it won’t distract from your codebase

Grundlegende BeispieleEssential Samples

Eine umfassende Liste der LINQ-Beispiele finden Sie unter 101 LINQ Samples (101 LINQ-Beispiele).For a truly comprehensive list of LINQ samples, visit 101 LINQ Samples.

Im Folgenden finden eine kurze Darstellung einiger der grundlegenden Bestandteile von LINQ.The following is a quick demonstration of some of the essential pieces of LINQ. Sie ist bei weitem nicht vollständig, da LINQ erheblich mehr Funktionen bietet, als hier präsentiert werden.This is in no way comprehensive, as LINQ provides significantly more functionality than what is showcased here.

  • Die Grundbestandteile sind Where, Select und Aggregate:The bread and butter - Where, Select, and Aggregate:
// Filtering a list
var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard);

// Using the query syntax
var queryGermanShepards = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepard
                          select dog;

// Mapping a list from type A to type B
var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax
var queryCats = from dog in dogs
                select dog.TurnIntoACat();

// Summing then lengths of a set of strings
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);
  • Reduzieren einer Liste mit Listen:Flattening a list of lists:
// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
  • Vereinigung von zwei Gruppen (mit benutzerdefiniertem Vergleichsoperator):Union between two sets (with custom comparator):
public class DogHairLengthComparer : IEqualityComparer<Dog>
{
    public bool Equals(Dog a, Dog b)
    {
        if (a == null && b == null)
        {
            return true;
        }
        else if ((a == null && b != null) ||
                 (a != null && b == null))
        {
            return false;
        }
        else
        {
            return a.HairLengthType == b.HairLengthType;
        }
    }

    public int GetHashCode(Dog d)
    {
        // default hashcode is enough here, as these are simple objects.
        return b.GetHashCode();
    }
}

...

// Gets all the short-haired dogs between two different kennels
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
  • Schnittmenge zwischen zwei Gruppen:Intersection between two sets:
// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     new VolunteerTimeComparer());
  • Sortierung:Ordering:
// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
              .OrderBy(direction => direction.HasNoTolls)
              .ThenBy(direction => direction.EstimatedTime);
  • Schließlich ein erweitertes Beispiel: Ermitteln, ob die Werte der Eigenschaften von zwei Instanzen desselben Typs gleich sind (aus diesem StackOverflow-Beitrag übernommen und geändert):Finally, a more advanced sample: determining if the values of the properties of two instances of the same type are equal (Borrowed and modified from this StackOverflow post):
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);

        // Selects the properties which have unequal values into a sequence of those properties.
        var unequalProperties = from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                where !ignoreList.Contains(pi.Name)
                                let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
                                let toValue = type.GetProperty(pi.Name).GetValue(to, null)
                                where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
                                select new { Prop = pi.Name, selfValue, toValue };
        return !unequalProperties.Any();
    }

    return self == to;
}

PLINQPLINQ

PLINQ (Parallel LINQ) ist ein Modul für die parallele Ausführung für LINQ-Ausdrücke.PLINQ, or Parallel LINQ, is a parallel execution engine for LINQ expressions. Reguläre LINQ-Ausdrücke können also im Grunde über eine beliebige Anzahl von Threads parallelisiert werden.In other words, a regular LINQ expressions can be trivially parallelized across any number of threads. Dies erfolgt über einen Aufruf von AsParallel() vor dem Ausdruck.This is accomplished via a call to AsParallel() preceding the expression.

Nehmen wir einmal die folgende Situation:Consider the following:

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
    var seed = default(UInt64);

    Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
    Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
    Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

    return facebookUsers.AsParallel()
                        .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}

Dieser Code partitioniert bei Bedarf facebookUsers über Systemthreads, summiert die Gesamtanzahl der „Likes“ parallel für jeden Thread, summiert die von allen Threads berechneten Ergebnisse und bringt das Ergebnis in eine nützliche Zeichenfolge.This code will partition facebookUsers across system threads as necessary, sum up the total likes on each thread in parallel, sum the results computed by each thread, and project that result into a nice string.

In Diagrammform:In diagram form:

PLINQ-Diagramm

Parallelisierbare CPU-gebundene Aufträge, die problemlos über LINQ ausgedrückt werden können (die also reine Funktionen ohne Nebeneffekte darstellen) sind gute Kandidaten für PLINQ.Parallelizable CPU-bound jobs which can be easily expressed via LINQ (in other words, are pure functions and have no side effects) are a great candidate for PLINQ. Für Aufträge, die einen Nebeneffekt haben, sollten Sie die Task Parallel Library verwenden.For jobs which do have a side effect, consider using the Task Parallel Library.

Weitere Ressourcen:Further Resources:

  • 101 LINQ-Beispiele101 LINQ Samples
  • Linqpad, eine Umgebung und ein Datenbankabfragemodul für C#/F#/VBLinqpad, a playground environment and Database querying engine for C#/F#/VB
  • EduLinq, ein E-Book, in dem die Implementierung von LINQ to Objects erläutert wirdEduLinq, an e-book for learning how LINQ-to-objects is implemented