Vue d’ensemble de LINQ
Language-Integrated Query (LINQ) fournit des capacités d'interrogation au niveau du langage et une API de fonction d'ordre supérieur pour C# et Visual Basic, qui vous permettent d'écrire un code déclaratif expressif.
Syntaxe de requête au niveau du langage
Il s’agit de la syntaxe de requête au niveau du langage :
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)
Il s’agit du même exemple à l’aide de l’API IEnumerable<T>
:
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 est expressif
Imaginez que vous disposez d’une liste d’animaux de compagnie, mais que vous souhaitez le convertir en dictionnaire où vous pouvez accéder directement à un animal de compagnie par sa valeur RFID
.
Il s’agit du code impératif traditionnel :
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
L’intention derrière le code n’est pas de créer une nouvelle Dictionary<int, Pet>
et de l’ajouter via une boucle, il est de convertir une liste existante en dictionnaire ! LINQ conserve l’intention alors que le code impératif ne le fait pas.
Il s’agit de l’expression LINQ équivalente :
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
Le code utilisant LINQ est précieux, car il permet de mettre à égalité l'intention et le code lorsqu'on raisonne en tant que programmeur. Un autre bonus est la concision du code. Imaginez que vous réduisez les grandes parties d’une base de code de 1/3 comme indiqué ci-dessus. Bon marché, non ?
Les fournisseurs LINQ simplifient l’accès aux données
Pour un segment important de logiciels dans la nature, tout tourne autour de la gestion des données provenant de certaines sources (Bases de données, JSON, XML, etc.). Cela implique souvent l’apprentissage d’une nouvelle API pour chaque source de données, ce qui peut être ennuyeux. LINQ simplifie cette opération en abstraitant les éléments communs de l’accès aux données dans une syntaxe de requête qui ressemble à la même source de données que vous choisissez.
Cela recherche tous les éléments XML avec une valeur d’attribut spécifique :
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
L’écriture de code pour parcourir manuellement le document XML pour effectuer cette tâche serait beaucoup plus difficile.
L’interaction avec XML n’est pas la seule chose que vous pouvez faire avec les fournisseurs LINQ. Linq to SQL est un Object-Relational Mapper (ORM) assez basique pour une base de données serveur MSSQL. La bibliothèque Json.NET fournit une traversée de document JSON efficace via LINQ. En outre, s’il n’existe pas de bibliothèque qui fait ce dont vous avez besoin, vous pouvez également écrire votre propre fournisseur LINQ!
Raisons d’utiliser la syntaxe de requête
Pourquoi utiliser la syntaxe de requête ? C’est une question qui se pose souvent. Après tout, le code suivant :
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
est beaucoup plus concis que ceci :
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
La syntaxe de l’API n’est-elle pas un moyen plus concis d’effectuer la syntaxe de requête ?
Non. La syntaxe de requête permet l'utilisation de la clause let, qui vous permet d'introduire et de lier une variable dans la portée de l'expression, et de l'utiliser dans les parties suivantes. La reproduction du même code avec uniquement la syntaxe de l’API peut être effectuée, mais cela entraînera probablement un code difficile à lire.
Cela pose donc la question, devez-vous simplement utiliser la syntaxe de requête ?
La réponse à cette question est oui si :
- Votre base de code existante utilise déjà la syntaxe de requête.
- Vous devez étendre les variables au sein de vos requêtes en raison de la complexité.
- Vous préférez la syntaxe d'interrogation et elle ne perturbe pas votre base de code.
La réponse à cette question est no si...
- Votre codebase existant utilise déjà la syntaxe de l’API
- Vous n’avez pas besoin de définir des variables d’étendue dans vos requêtes
- Vous préférez la syntaxe de l’API car elle ne va pas distraire votre code source.
LINQ essentiel
Les exemples suivants sont une démonstration rapide de certaines des pièces essentielles de LINQ. Cela n’est pas complet, car LINQ fournit plus de fonctionnalités que ce qui est présenté ici.
Le pain et le beurre - Where
, Select
et 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)
Aplatir une liste de listes
// 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)
Union entre deux ensembles (avec comparateur personnalisé)
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())
Intersection entre deux ensembles
// 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())
Classement
// 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)
Égalité des propriétés d’instance
Enfin, un exemple plus avancé : déterminer si les valeurs des propriétés de deux instances du même type sont égales (empruntées et modifiées à partir de ce billet StackOverflow) :
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 ou Parallel LINQ est un moteur d’exécution parallèle pour les expressions LINQ. En d’autres termes, une expression LINQ régulière peut être parallélisée de manière triviale sur n’importe quel nombre de threads. Cela s’effectue via un appel à AsParallel()
précédant l’expression.
Tenez compte des éléments suivants :
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)
}
Ce code partitionnera facebookUsers
entre les threads système selon les besoins, totalisera en parallèle les likes sur tous les threads, additionnera les résultats calculés par chaque thread et projettera ce résultat en une belle chaîne.
Sous forme de diagramme :
Les travaux liés au processeur parallélisables qui peuvent être facilement exprimés via LINQ (en d’autres termes, sont des fonctions pures et n’ont aucun effet secondaire) sont un excellent candidat pour PLINQ. Pour les tâches qui ont un effet secondaire, envisagez d'utiliser la bibliothèque Task Parallel Library.