OVERZICHT VAN LINQ
Language-Integrated Query (LINQ) biedt mogelijkheden voor query's op taalniveau en een functie met hogere volgorde API naar C# en Visual Basic, waarmee u expressieve declaratieve code kunt schrijven.
Querysyntaxis op taalniveau
Dit is de querysyntaxis op taalniveau:
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)
Dit is hetzelfde voorbeeld met behulp van de 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 is expressief
Stel dat u een lijst met huisdieren hebt, maar deze wilt omzetten in een woordenlijst waar u rechtstreeks toegang hebt tot een huisdier met de RFID
waarde.
Dit is traditionele imperatieve 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
De bedoeling achter de code is niet om een nieuwe Dictionary<int, Pet>
te maken en daarop via een lus toe te voegen. Het doel is om een bestaande lijst om te zetten in een woordenboek. LINQ behoudt de bedoeling, terwijl de imperatieve code dat niet doet.
Dit is de equivalente LINQ-expressie:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
De code die gebruikmaakt van LINQ is waardevol omdat het het speelveld tussen intentie en code gelijkmaakt wanneer u redeneert als programmeur. Een andere bonus is code-breviteit. Stel dat u grote delen van een codebasis met 1/3 vermindert zoals hierboven is gedaan. Lieve deal, toch?
LINQ-providers vereenvoudigen de toegang tot gegevens
Voor een aanzienlijk deel van de software in het wild draait alles om het omgaan met gegevens uit een bepaalde bron (Databases, JSON, XML, enzovoort). Dit omvat vaak het leren van een nieuwe API voor elke gegevensbron, wat vervelend kan zijn. LINQ vereenvoudigt dit door algemene elementen van gegevenstoegang te abstraheren in een querysyntaxis die er hetzelfde uitziet, ongeacht welke gegevensbron u kiest.
Hiermee worden alle XML-elementen met een specifieke kenmerkwaarde gevonden:
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
Het schrijven van code om het XML-document handmatig te doorlopen om deze taak uit te voeren, zou veel lastiger zijn.
Interactie met XML is niet het enige wat u kunt doen met LINQ-providers. Linq naar SQL is een redelijk eenvoudige Object-Relational mapper (ORM) voor een MSSQL Server-database. De Json.NET-bibliotheek biedt efficiënte JSON-documentkruising via LINQ. Als er bovendien geen bibliotheek is die doet wat u nodig hebt, kunt u ook uw eigen LINQ Providerschrijven!
Redenen om de querysyntaxis te gebruiken
Waarom querysyntaxis gebruiken? Dit is een vraag die vaak voorkomt. Immers, de volgende code:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
is veel beknopter dan dit:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
Is de API-syntaxis niet alleen een beknoptere manier om de querysyntaxis uit te voeren?
Nee. Met de querysyntaxis kunt u het gebruik van de clausule mogelijk maken, waarmee u een variabele binnen de scope van de expressie kunt introduceren en binden, waardoor u deze in volgende delen van de expressie kunt gebruiken. Het reproduceren van dezelfde code met alleen de API-syntaxis kan worden uitgevoerd, maar leidt waarschijnlijk tot code die moeilijk te lezen is.
Dus dit roept de vraag op, moet u alleen de query syntax gebruiken?
Het antwoord op deze vraag is ja als:
- Uw bestaande codebase maakt al gebruik van de querysyntaxis.
- U moet variabelen binnen uw query's afbakenen vanwege de complexiteit.
- U geeft de voorkeur aan de querysyntaxis en het leidt niet af van uw codebasis.
Het antwoord op deze vraag is geen als...
- Uw bestaande codebase maakt al gebruik van de API-syntaxis
- U hoeft geen variabelen binnen uw query's te definiëren
- U geeft de voorkeur aan de API-syntaxis en het leidt niet af van uw codebase.
Essentiële LINQ
De volgende voorbeelden zijn een snelle demonstratie van enkele van de essentiële onderdelen van LINQ. Dit is op geen enkele manier uitgebreid, omdat LINQ meer functionaliteit biedt dan wat hier wordt weergegeven.
Het brood en boter - Where
, Select
en 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)
Het platmaken van een lijst van lijsten
// 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)
Samenvoeging tussen twee sets (met aangepaste 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 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())
Snijpunt tussen twee sets
// 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())
Bestellen
// 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)
Gelijkheid van exemplaareigenschappen
Ten slotte een geavanceerder voorbeeld: bepalen of de waarden van de eigenschappen van twee exemplaren van hetzelfde type gelijk zijn (Geleend en aangepast van dit StackOverflow-bericht):
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 of Parallel LINQ is een parallelle uitvoeringsengine voor LINQ-expressies. Met andere woorden, een reguliere LINQ-expressie kan triviaal worden geparallelliseerd over een willekeurig aantal threads. Dit wordt bereikt via een aanroep van AsParallel()
voorafgaand aan de expressie.
Houd rekening met het volgende:
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)
}
Met deze code wordt facebookUsers
, indien nodig, over systeemthreads gepartitioneerd. Het totale aantal likes wordt in elke thread parallel opgeteld, vervolgens worden de resultaten van iedere thread samengevoegd, en dat resultaat wordt omgezet naar een mooie tekenreeks.
In diagramvorm:
Parallelliseerbare CPU-gebonden taken die eenvoudig kunnen worden uitgedrukt via LINQ (met andere woorden, zijn pure functies en hebben geen neveneffecten) zijn een uitstekende kandidaat voor PLINQ. Voor taken die een neveneffect hebben, kunt u overwegen de taakparallelbibliotheekte gebruiken.