A LINQ áttekintése
Language-Integrated Lekérdezés (LINQ) nyelvi szintű lekérdezési képességeket, valamint egy magasabb rendű függvény API-t a C#-hoz és a Visual Basichoz, amely lehetővé teszi kifejező deklaratív kód írását.
Nyelvszintű lekérdezési szintaxis
Ez a nyelvszintű lekérdezés szintaxisa:
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 a 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ő jellegű
Tegyük fel, hogy rendelkezik a háziállatok listájával, de szeretné átalakítani egy szótárba, ahol közvetlenül elérheti a kisállatot a 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 a szándék nem az, hogy létrehozzon egy új Dictionary<int, Pet>
-t és ezen keresztül hozzáadja, hanem hogy egy meglévő listát szótárrá alakítsa! 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 értékes, mert a programozóként való érveléskor kiegyenlíti a szándék és a kód közötti játékteret. Egy másik bónusz a kód tömörsége. Képzelje el, hogy a kódbázis nagy részét a fenti módon 1/3-tal csökkenti. Édes üzlet, igaz?
A LINQ-szolgáltatók leegyszerűsítik az adathozzáférést
A szoftver jelentős részéhez a vadonban minden egy forrásból származó adatok (adatbázisok, JSON, XML stb.) kezelése körül forog. Ez gyakran magában foglalja az új API-k tanulását minden adatforráshoz, ami bosszantó lehet. A LINQ leegyszerűsíti ezt úgy, hogy az adathozzáférés gyakori elemeit egy lekérdezési szintaxisba absztrakcióval absztraktálja, amely ugyanúgy néz ki, függetlenül attól, hogy melyik adatforrást választja.
Ez megkeresi az összes xml-elemet egy adott attribútumértékkel:
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.
Az XML használata nem az egyetlen dolog, amit a LINQ-szolgáltatókkal végezhet. Linq to SQL egy viszonylag alapszintű Object-Relational leképező (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és szintaxisának használatának okai
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-szintaxis nem csupán tömörebb módszer a lekérdezési szintaxis használatára?
Nem. A lekérdezés szintaxisa lehetővé teszi a záradék használatát, amely által bevezethetünk és összekapcsolhatunk egy változót a kifejezés hatókörén belül, és azt a kifejezés későbbi részeiben használhatjuk. Ugyanazt a kódot csak az API szintaxisával lehet reprodukálni, de nagy valószínűséggel nehezen olvasható kódhoz vezet.
Ez felveti a kérdést, vajon csak a lekérdezés szintaxisát kellene 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és szintaxisát.
- Összetettség miatt változókat kell hatókörbe helyeznie a lekérdezésekben.
- Előnyben részesíti a lekérdezés szintaxisát, és 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 nem kell változókat külön hatókörbe helyeznie
- Az API-szintaxist részesíti előnyben, és ez nem vonja el a figyelmet a kódbázistól
Alapvető LINQ
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, (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)
Listalista összesimí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())
Rendelé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ány objektum tulajdonságainak egyenlősége
Végül egy fejlettebb minta: annak meghatározása, hogy két azonos típusú példány tulajdonságai azonosak-e (a StackOverflow után kölcsönözve és módosítva):
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árhuzamba állítható tetszőleges számú szálon. Ez a kifejezés előtti AsParallel()
hívásával történik.
Vegye figyelembe a következőket:
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 a facebookUsers
a rendszerszálak között, párhuzamosan összegzi az egyes szálak összes lájkját, majd az összes szál által kiszámított eredményeket összeadja, és az így kapott eredményt egy szép karakterlánccá alakítja.
Diagram formájában:
A linq használatával könnyen kifejezhető párhuzamos processzorhoz kötött feladatok (más szóval tiszta függvények, és nincsenek mellékhatásaik) kiválóan alkalmasak a PLINQ-ra. Az által végzett feladatoknál érdemes lehet a feladat párhuzamos kódtáráthasználni.