LINQ-Übersicht
Language-Integrated Query (LINQ) bietet Abfragefunktionen auf Sprachebene und eine höhergeordnete Funktion API für C# und Visual Basic, mit der Sie ausdrucksstarken deklarativen Code schreiben können.
Syntax für sprachspezifische Abfragen
Dies ist die Abfragesyntax auf Sprachebene:
var linqExperts = from p in programmers
where p.IsNewToLINQ
select new LINQExpert(p);
Dim linqExperts = From p in programmers
Where p.IsNewToLINQ
Select New LINQExpert(p)
Dies ist dasselbe Beispiel mit der IEnumerable<T>
-API:
var linqExperts = programmers.Where(p => p.IsNewToLINQ)
.Select(p => new LINQExpert(p));
Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).
Select(Function(p) New LINQExpert(p))
LINQ ist ausdrucksstark
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.
Dies ist herkömmlicher imperativer Code:
var petLookup = new Dictionary<int, Pet>();
foreach (var pet in pets)
{
petLookup.Add(pet.RFID, pet);
}
Dim petLookup = New Dictionary(Of Integer, Pet)()
For Each pet in pets
petLookup.Add(pet.RFID, pet)
Next
Der Zweck des Codes ist nicht, ein neues Dictionary<int, Pet>
-Element zu erstellen und es über eine Schleife zu erweitern, sondern eine vorhandene Liste in ein Wörterbuch zu konvertieren. LINQ bewahrt die Intention, während dies bei imperativem Code nicht der Fall ist.
Dies ist der entsprechende LINQ-Ausdruck:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
Der Code, der LINQ verwendet, ist wertvoll, da er das Spielfeld zwischen Absicht und Code ausgleicht, wenn man als Programmierer argumentiert. Ein weiterer Bonus ist die Kürze des Codes. Stellen Sie sich vor, wie oben gezeigt, große Teile einer Codebasis um 1/3 zu reduzieren. Süßer Deal, richtig?
LINQ-Anbieter vereinfachen den Datenzugriff
Für einen erheblichen Teil der Software in der Natur dreht sich alles um den Umgang mit Daten aus einer Quelle (Datenbanken, JSON, XML usw.). Dies beinhaltet häufig das Erlernen einer neuen API für jede Datenquelle, was lästig sein kann. LINQ vereinfacht dies, indem allgemeine Elemente des Datenzugriffs in eine Abfragesyntax abstrahiert werden, die unabhängig davon, welche Datenquelle Sie auswählen, gleich aussieht.
Dadurch werden alle XML-Elemente mit einem bestimmten Attributwert gefunden:
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;
}
Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,
attributeName As String, value As String) As IEnumerable(Of XElement)
Return From el In documentRoot.Elements(elementName)
Where el.Element(attributeName).ToString() = value
Select el
End Function
Das Schreiben von Code, um das XML-Dokument manuell zu durchlaufen, um diese Aufgabe zu erledigen, wäre viel schwieriger.
Die Interaktion mit XML ist nicht das einzige, was Sie mit LINQ-Anbietern tun können. LINQ to SQL ist ein ziemlich reduzierter objektrelationaler Mapper (ORM) für eine MSSQL Server-Datenbank. Die Json.NET-Bibliothek stellt eine effiziente JSON-Dokumentquerung über LINQ bereit. Wenn es allerdings keine Bibliothek gibt, die Ihre Anforderungen erfüllt, können Sie auch Ihren eigenen LINQ-Anbieter schreiben.
Gründe für die Verwendung der Abfragesyntax
Warum Abfragesyntax verwenden? Dies ist eine Frage, die oft aufkommt. Schließlich ist der folgende Code:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
sehr viel präziser als dies:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
Ist die API-Syntax nicht nur eine präzisere Methode, um die Abfragesyntax auszuführen?
Nein. Die Abfragesyntax ermöglicht die Verwendung der let Klausel, mit der Sie eine Variable innerhalb des Bereichs des Ausdrucks einführen und binden können, um sie in nachfolgenden Teilen des Ausdrucks zu verwenden. Das Reproduzieren desselben Codes mit nur der API-Syntax kann ausgeführt werden, führt aber höchstwahrscheinlich zu Code, der schwer zu lesen ist.
Daher stellt sich die folgende Frage: Sollten Sie einfach die Abfragesyntax verwenden?
Die Antwort auf diese Frage ist ja wenn:
- Ihre vorhandene Codebasis verwendet bereits die Abfragesyntax.
- Sie den Gültigkeitsbereich von Variablen in Ihren Abfragen aufgrund der Komplexität festlegen müssen.
- Sie bevorzugen die Abfragesyntax, und sie ablenkt nicht von Ihrer Codebasis.
Die Antwort auf diese Frage ist nein, wenn...
- Ihre vorhandene Codebasis verwendet bereits die API-Syntax.
- Sie den Bereich von Variablen in Ihren Abfragen nicht festlegen müssen
- Sie bevorzugen die API-Syntax, und sie ablenkt nicht von Ihrer Codebasis.
Essential LINQ
Die folgenden Beispiele sind eine schnelle Demonstration einiger der wesentlichen Teile von LINQ. Dies ist in keiner Weise umfassend, da LINQ mehr Funktionalität bietet als das, was hier präsentiert wird.
Die Grundbestandteile sind Where
, Select
und Aggregate
.
// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);
// Using the query syntax.
var queryGermanShepherds = from dog in dogs
where dog.Breed == DogBreed.GermanShepherd
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 the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (partialSum, nextString) => partialSum + nextString.Length);
' Filtering a list.
Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)
' Using the query syntax.
Dim queryGermanShepherds = From dog In dogs
Where dog.Breed = DogBreed.GermanShepherd
Select dog
' Mapping a list from type A to type B.
Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())
' Using the query syntax.
Dim queryCats = From dog In dogs
Select dog.TurnIntoACat()
' Summing the lengths of a set of strings.
Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(partialSum, nextString) partialSum + nextString.Length)
Reduzieren einer Liste mit Listen
// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)
Vereinigung von zwei Gruppen (mit benutzerdefiniertem Vergleichsoperator)
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 d.GetHashCode();
}
}
...
// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
Public Class DogHairLengthComparer
Inherits IEqualityComparer(Of Dog)
Public Function Equals(a As Dog,b As Dog) As Boolean
If a Is Nothing AndAlso b Is Nothing Then
Return True
ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
Return False
Else
Return a.HairLengthType = b.HairLengthType
End If
End Function
Public Function GetHashCode(d As Dog) As Integer
' Default hashcode is enough here, as these are simple objects.
Return d.GetHashCode()
End Function
End Class
...
' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())
Schnittmenge von zwei Mengen
// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
New VolunteerTimeComparer())
Sortieren
// 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);
' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
OrderBy(Function(direction) direction.HasNoTolls).
ThenBy(Function(direction) direction.EstimatedTime)
Gleichheit von Instanzeigenschaften
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):
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
if (self == null || to == null)
{
return self == to;
}
// Selects the properties which have unequal values into a sequence of those properties.
var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
where !ignore.Contains(property.Name)
let selfValue = property.GetValue(self, null)
let toValue = property.GetValue(to, null)
where !Equals(selfValue, toValue)
select property;
return !unequalProperties.Any();
}
<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As String()) As Boolean
If self Is Nothing OrElse [to] Is Nothing Then
Return self Is [to]
End If
' Selects the properties which have unequal values into a sequence of those properties.
Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
Where Not ignore.Contains([property].Name)
Let selfValue = [property].GetValue(self, Nothing)
Let toValue = [property].GetValue([to], Nothing)
Where Not Equals(selfValue, toValue) Select [property]
Return Not unequalProperties.Any()
End Function
PLINQ
PLINQ oder Parallel LINQ ist ein paralleles Ausführungsmodul für LINQ-Ausdrücke. Reguläre LINQ-Ausdrücke können also im Grunde ganz einfach über eine beliebige Anzahl von Threads parallelisiert werden. Dies erfolgt über einen Aufruf von AsParallel()
vor dem Ausdruck.
Beachten Sie Folgendes:
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);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
Dim seed As UInt64 = 0
Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim resultSelector As Func(Of Uint64, string) = Function(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.
In Diagrammform:
Parallelisierbare CPU-gebundene Aufträge, die einfach über LINQ ausgedrückt werden können (mit anderen Worten, sind reine Funktionen und haben keine Nebenwirkungen) sind ein hervorragender Kandidat für PLINQ. Für Aufträge, die einen Nebeneffekt haben, sollten Sie die Task Parallel Library verwenden.