A LINQ áttekintése
A Language-Integrated Query (LINQ) nyelvszintű lekérdezési képességeket, valamint egy magasabb rendű függvény API-t biztosít a C# és a Visual Basic számára, amelyekkel kifejező deklaratív kódot írhat.
Nyelvszintű lekérdezési szintaxis
Ez a nyelvszintű lekérdezési szintaxis:
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)
Ez ugyanaz a példa az IEnumerable<T>
API használatával:
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))
A LINQ kifejező
Imagine van egy háziállatok listája, de szeretné átalakítani egy szótárba, ahol közvetlenül elérheti a kisállatot az RFID
értéke alapján.
Ez a hagyományos imperatív 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
A kód mögött nem az a szándék, hogy újat Dictionary<int, Pet>
hozzon létre, és hurkon keresztül adja hozzá, hanem egy meglévő listát konvertál szótárba! A LINQ megőrzi a szándékot, míg az imperatív kód nem.
Ez az egyenértékű LINQ-kifejezés:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
A LINQ-t használó kód azért hasznos, mert a programozóként végzett érvelés során kiegyenlíti a játékteret a szándék és a kód között. Egy másik bónusz a kód rövidsége. Imagine a kódbázis nagy részeinek 1/3-tal való csökkentése a fentieknek megfelelően. Édes üzlet, ugye?
A LINQ-szolgáltatók leegyszerűsítik az adathozzáférést
A vadonban jelentős mennyiségű szoftver esetében minden az egyes forrásból (adatbázisokból, JSON-ból, XML-ből stb.) származó adatok kezelésével foglalkozik. Ez gyakran egy új API elsajátítása minden adatforráshoz, ami bosszantó lehet. A LINQ leegyszerűsíti ezt, ha az adathozzáférés gyakori elemeit egy olyan lekérdezési szintaxisba absztraháljuk, amely ugyanúgy néz ki, függetlenül attól, hogy melyik adatforrást választja.
Ez megkeresi az adott attribútumértékkel rendelkező összes XML-elemet:
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
A feladat elvégzéséhez az XML-dokumentum manuális bejárására vonatkozó kód írása sokkal nagyobb kihívást jelent.
A LINQ-szolgáltatókkal nem csak az XML-műveletek használhatók. A Linq to SQL meglehetősen csupasz Object-Relational Mapper (ORM) egy MSSQL Server-adatbázishoz. A Json.NET kódtár hatékony JSON-dokumentumbejárást biztosít a LINQ-n keresztül. Továbbá, ha nincs olyan kódtár, amely azt teszi, amire szüksége van, saját LINQ-szolgáltatót is írhat!
A lekérdezési szintaxis használatának indokai
Miért érdemes lekérdezési szintaxist használni? Ez egy olyan kérdés, amely gyakran felmerül. Végül is a következő kód:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
sokkal tömörebb, mint ez:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
Az API szintaxisa nem csak tömörebb módja a lekérdezési szintaxisnak?
Nem. A lekérdezési szintaxis lehetővé teszi a let záradék használatát, amely lehetővé teszi egy változó bevezetését és kötését a kifejezés hatókörén belül, a kifejezés későbbi részeiben használva. Ugyanazt a kódot csak az API-szintaxissal reprodukálni lehet, de valószínűleg nehezen olvasható kódhoz vezet.
Tehát ez felveti a kérdést, csak a lekérdezési szintaxist kell használnia?
A válasz erre a kérdésre igen , ha:
- A meglévő kódbázis már használja a lekérdezési szintaxist.
- A változókat összetettség miatt a lekérdezéseken belül kell hatókörbe helyeznie.
- A lekérdezési szintaxist részesíti előnyben, és ez nem vonja el a figyelmet a kódbázistól.
A válasz erre a kérdésre nem , ha...
- A meglévő kódbázis már használja az API-szintaxist
- A lekérdezéseken belül nincs szükség változók hatókörének meghatározására
- Az API-szintaxist részesíti előnyben, és ez nem vonja el a figyelmet a kódbázistól
Alapvető LINQ
A LINQ-minták igazán átfogó listájáért látogasson el a 101 LINQ-mintára.
Az alábbi példák a LINQ néhány alapvető elemének gyors bemutatását szemléltetik. Ez semmiképpen sem átfogó, mivel a LINQ több funkciót biztosít, mint amit itt bemutatunk.
A kenyér és a vaj - Where
, Select
, és 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, (s1, s2) => s1.Length + s2.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(s1, s2) s1.Length + s2.Length)
Listalista egybesimítása
// 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)
Egyesítés két halmaz között (egyéni összehasonlítóval)
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())
Metszet két halmaz között
// 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())
Rendezés
// 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)
Példánytulajdonságok egyenlősége
Végül egy fejlettebb minta: annak meghatározása, hogy két azonos típusú példány tulajdonságainak értékei egyenlőek-e ( ebből a StackOverflow-bejegyzésből kölcsönzött és módosított):
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
A PLINQ vagy parallel LINQ a LINQ-kifejezések párhuzamos végrehajtási motorja. Más szóval egy reguláris LINQ-kifejezés triviálisan párhuzamosítható tetszőleges számú szálon. Ez a kifejezés előtti hívással AsParallel()
érhető el.
A következőket ajánljuk figyelmébe:
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)
}
Ez a kód szükség szerint particionálja facebookUsers
a rendszerszálakat, párhuzamosan összegzi az egyes szálak összes tetszését, összegzi az egyes szálak által kiszámított eredményeket, és egy szép sztringet eredményező projektet hoz létre.
Diagram formájában:
A LINQ-n keresztül könnyen kifejezhető párhuzamos cpu-kötésű feladatok (más szóval tiszta funkciók, és nincsenek mellékhatásaik) kiválóan alkalmasak a PLINQ-ra. A mellékhatással rendelkező feladatok esetében fontolja meg a párhuzamos feladattár használatát.
További erőforrások
- 101 LINQ-minta
- Linqpad, egy játszótéri környezet és adatbázis-lekérdezési motor C#/F#/Visual Basic
- EduLinq, e-könyv a LINQ-objektumok implementálásának megismeréséhez