Přehled LINQ
Language-Integrated Query (LINQ) poskytuje možnosti dotazování na úrovni jazyka a funkce vyššího řádu rozhraní API pro C# a Visual Basic, které umožňují psát výrazný deklarativní kód.
Syntaxe dotazů na úrovni jazyka
Toto je syntaxe dotazů na úrovni jazyka:
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)
Toto je stejný příklad použití rozhraní 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 je výstižná
Představte si, že máte seznam domácích mazlíčků, ale chcete ho převést na slovník, kde můžete přistupovat k domácímu mazlíčku přímo podle jeho RFID
hodnoty.
Toto je tradiční imperativní kód:
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
Záměrem kódu není vytvořit nový Dictionary<int, Pet>
a přidávat do něj pomocí smyčky, ale převést stávající seznam na slovník. LINQ zachovává záměr, zatímco imperativní kód ne.
Toto je ekvivalentní výraz LINQ:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
Kód využívající LINQ je cenný, protože při programátorském uvažování vyrovnává podmínky mezi záměrem a kódem. Dalším bonusem je stručnost kódu. Představte si, že snížíte velké části základu kódu o 1/3 podle výše uvedeného postupu. Sladká dohoda, že?
Zprostředkovatelé LINQ zjednodušují přístup k datům
U značné části softwaru dostupného v běžném prostředí se všechno točí kolem zpracování dat z nějakého zdroje (databáze, JSON, XML atd.). Často se to týká učení nového rozhraní API pro každý zdroj dat, což může být nepříjemné. LINQ to zjednodušuje abstrakcí běžných prvků přístupu k datům do syntaxe dotazu, která vypadá stejně bez ohledu na to, který zdroj dat vyberete.
Vyhledá všechny elementy XML s konkrétní hodnotou atributu:
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
Psaní kódu pro ruční procházení dokumentu XML k tomuto úkolu by bylo mnohem náročnější.
Interakce s XML není jediná věc, kterou můžete dělat s zprostředkovateli LINQ. Linq to SQL je poměrně holý mapovač Object-Relational mapper (ORM) pro databázi serveru MSSQL. Knihovna Json.NET poskytuje efektivní procházení dokumentů JSON prostřednictvím LINQ. Kromě toho, pokud neexistuje knihovna, která dělá to, co potřebujete, můžete také napsat vlastního poskytovatele LINQ!
Důvody použití syntaxe dotazu
Proč používat syntaxi dotazu? To je otázka, která se často objevuje. Koneckonců, následující kód:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
je mnohem stručnější než toto:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
Není syntaxe rozhraní API jen stručnější způsob, jak syntaxi dotazu provést?
Ne. Syntaxe dotazu umožňuje použití klauzule let, která umožňuje zavést a svázat proměnnou v rámci výrazu a použít ji v dalších částech výrazu. Reprodukování stejného kódu pouze se syntaxí rozhraní API je možné provést, ale s největší pravděpodobností povede ke čtení kódu, který je obtížně čitelný.
Takže to vyvolává otázku, měli byste použít jenom syntaxi dotazu?
Odpověď na tuto otázku je ano, pokud:
- Váš existující základ kódu už používá syntaxi dotazu.
- Proměnné v rámci dotazů je potřeba vymezit kvůli složitosti.
- Preferujete syntaxi dotazů a nebude vás rozptylovat od vaší kódové základny.
Odpověď na tuto otázku je bez pokud...
- Váš stávající základ kódu už používá syntaxi rozhraní API.
- V dotazech není potřeba definovat rozsah proměnných.
- Dáváte přednost syntaxi rozhraní API a neodvádí pozornost od vašeho kódu.
Základní LINQ
Následující příklady představují rychlou ukázku některých základních částí LINQ. To není nijak komplexní, protože LINQ poskytuje více funkcí, než je zde prezentováno.
Chléb a máslo - Where
, Select
a 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)
Zploštění seznamu seznamů
// 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)
Sjednocení dvou množin (s vlastním komparátorem)
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())
Průnik dvou sad
// 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())
Objednávání
// 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)
Rovnost vlastností instance
A konečně pokročilejší příklad: určení, zda hodnoty vlastností dvou instancí stejného typu jsou stejné (převzato a upraveno z tohoto StackOverflow příspěvku):
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 nebo Parallel LINQ je paralelní prováděcí modul pro výrazy LINQ. Jinými slovy, regulární výraz LINQ lze triviálně paralelizovat napříč libovolným počtem vláken. Toho se dosahuje voláním AsParallel()
před výrazem.
Vezměte v úvahu následující skutečnosti:
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)
}
Tento kód podle potřeby rozdělí facebookUsers
napříč systémovými vlákny, paralelně sečte celkový počet lajků na každém vlákně, sečte výsledky vypočítané jednotlivými vlákny a vyjádří tento výsledek do přehledného řetězce.
V podobě diagramu:
Paralelizovatelné úlohy vázané na procesor, které lze snadno vyjádřit prostřednictvím LINQ (jinými slovy, jsou čisté funkce a nemají žádné vedlejší účinky) jsou skvělým kandidátem pro PLINQ. U úloh, které