Eddig a LINQ esetén két szintaxisról beszéltünk és a példák is mindkét szintaxissal be lettek mutatva. Azonban a most következő műveletek csak lambda szintaxissal alkalmazhatóak.
Az egyszerűség kedvéért maradjunk az eddigi LINQ példák többségében alkalmazott kollekciónál:
struct Pelda
{
public int X { get; set; }
public int Y { get; set; }
}
var elemek = new List<Pelda>
{
new Pelda { X = 10, Y = 20 },
new Pelda { X = 10, Y = 12 },
new Pelda { X = 11, Y = 23 },
new Pelda { X = 44, Y = 42 },
new Pelda { X = 7, Y = 1 },
new Pelda { X = 9, Y = 12 },
};
Kihagyás és megadott számú elem kiválasztása
Ha elemeket szeretnénk kihagyni egy kollekcióból, akkor a Skip() metódust alkalmazhatjuk. Ennek paramétereként a kihagyandó elemek számát kell megadnunk. Ha pedig csak egy adott mennyiségű elemet szeretnénk kiválasztani, akkor a Take() metódust alkalmazhatjuk. Ennek paramétere szintén egész szám, ami a kiválasztandó elemek számát adja meg. Mindkét metódus esetén, ha kifutna az eredeti kollekció méretéből, akkor egy üres kollekciót kapunk.
Példaként vegyük ki az elemek közül az első kettő kihagyása után a következő három elemet:
var eredmeny = elemek.Skip(2).Take(3);
Elem kiválasztások
Az eddig ismertetett LINQ műveletek mind IEnumerable<T> típusú elemet adnak vissza. Sok esetben azonban szükségünk lehet egy megadott elem kiválasztására.
Az első elemet a First() metódussal tudjuk kiválasztani. Azonban, ha a kollekció nem tartalmaz elemet, akkor ez a metódus kivételt fog dobni. Párja a FirstOrDefault(), ami sikertelenség esetén a típus alapértelmezett értékét adja vissza (Érték típus esetén a típus alapértelmezett értéke, amit a paraméter nélküli konstruktor beállít. Referencia típusok, osztályok esetén azonban a visszatérési érték ilyenkor null lesz.).
Ha az utolsó elemre vagyunk kíváncsiak, akkor a Last() és a LastOrDefault() metódusokat alkalmazhatjuk, amelyek teljesen hasonlóan működnek, mint a First() és a FirstOrDefault() metódusok.
Ha tudjuk, hogy az eredmény kollekcióból például a negyedik elemre vagyunk kíváncsiak, akkor az ElementAt() metódus alkalmazható, aminek a paramétere az elem sorszáma, amire kíváncsiak vagyunk. Ez kivételt fog dobni, ha a megadott sorszámú elem nem található. Az ez ellen védett párja az ElementAtOrDefault() metódus.
A First(), FirstOrDefault(), Last(), LastOrDefault() metódusoknak paramétert is tudunk adni, ami egy where záradékként viselkedve csak az első, vagy utolsó olyan elemet adja vissza, amire a feltétel igaz.
//egy olyan Pelda objektum lesz, amiben X = 0, Y = 0
//mivel nincs ilyen a listában
var eredmeny = elemek.FirstOrDefault(i => i.Y == 99);
//Pelda { X = 7, Y = 1 } objektum lesz
var eredmeny2 = elemek.Last(i => i.Y == 1);
//Pelda { X = 44, Y = 42 }, objektum lesz, mivel ő a 2. indexel rendelkező
var eredmeny3 = elemek.ElementAt(2);
.NET 6 óta, a FirstOrDefault és LastOrDefault esetén megadhatjuk második paraméternek, hogy mi legyen az alapértelmezett érték, amivel visszatérjen, ha nem találta meg a keresett elemet:
var alap = new Pelda { X = int.MaxValue, Y = int.MaxValue };
//alap változó értékét adja vissza, mivel nincs ilyen a listában
var eredmeny4 = elemek.FirstOrDefault(i => i.Y == 99, alap);
//szintén alap változó értékét adja vissza, mivel nincs ilyen a listában
var eredmeny5 = elemek.LastOrDefault(i => i.Y == 99, alap);
Ismétlődések kiszűrése
Ismétlődés kiszűrésére a Distinct metódus alkalmazható. Alkalmazásának feltétele, hogy ha a típusunk egy összetett típus, ami több elemet tartalmaz, akkor rendelkezzen egy megfelelően felülírt Equals metódussal. Alternatívaként adhatunk neki paraméternek egy IEqualityComparer<T> interface-t megvalósító típus példányt.
Az IEqualityComparer<T> lehetővé teszi, hogy a típus alapértelmezett Equals() és GetHashCode() metódusától eltérő működésű implementációkat készítsünk. Ennek segítségével megoldható, hogy egy komplex típus esetén a típus alapértelmezett Equals metódusától eltérően csak egy adott tulajdonság alapján hasonlítsunk össze két objektumot.
.NET 6 óta lehetőségünk van egy objektum bizonyos tulajdonsága alapján az ismétlődések kiszűrésére a DistinctBy művelettel. Ennek egy lambda kifejezést kell megadnunk, ami alapján egyedi értékekre vagyunk kíváncsiak.
//olyan IEnumerable<Pelda> lesz, amiben minden Pelda
//egyedi X értékkel rendelkezik ismétlődés nélkül
var egyediX = elemek.DistinctBy(i => i.X);
Aggregáció műveletei
A LINQ támogat néhány aggregációs1 műveletet. Ha kollekció eredményének számosságára vagyunk kíváncsiak, akkor a Count() metódust alkalmazhatjuk. Itt megjegyezném, hogy feltételek esetén, ha azt szeretnénk kiértékelni, hogy egyáltalán van-e eredménye a keresésnek, akkor a Count() > 0 kifejezés erősen kerülendő, mert ez végig megy a kollekción és végig számolja az elemeket.
Helyette az Any() metódust ajánlott alkalmazni, ami eleve erre lett kitalálva. Az Any() igaz értéket ad vissza, ha az IEnumerable<T> típusunk tartalmaz elemeket. Paraméterrel is kiegészíthetjük. Ebben az esetben azt mondja meg, hogy a kollekció tartalmaz-e a feltételnek megfelelő elemet.
Ha mindenképpen szükségünk van egy IEnumerable<T> elemszámára és .NET 6 vagy újabb keretrendszerre fejlesztünk, akkor használhatjuk a TryGetNonEnumeratedCount metódust. Ez a paraméternek megadott IEnumerable<T> típus alatt található kollekciót megpróbálja megállapítani és ha lehetséges bejárás nélkül elérni a kollekció darabszámát, akkor visszaadja azt.
A metódus bool visszatérési értékkel rendelkezik, ami igaz értékű, ha bejárás nélkül megállapítható a kollekció hossza, ellenkező esetben pedig hamis értéket ad vissza. A kollekció hosszát egy out paraméterben adja vissza:
int darab = 0;
//true lesz, darab értéke pedig 6 lesz.
bool siker = elemek.TryGetNonEnumeratedCount(out darab);
Ha egy kollekció esetén a minimum, vagy maximum értékre vagyunk kíváncsiak, akkor a Min() és Max() metódusokat alkalmazhatjuk. Ha a kollekció szám típusokból áll, akkor paramétert nem kell adnunk. Ellenkező esetben kell egy kifejezés, ami megmondja, hogy mi alapján számolódjon a minimum és maxumum érték. Átlagolásra hasonló működéssel az Avg() metódus alkalmazható.
//Összes elem számának meghatározása
var osszes = elemek.Count();
//Összes olyan elem számának meghatározása, ahol x páros
var parosx = elemek.Count(i => i.X%2 ==0);
//X maximumának meghatározása, értéke 44
var MaxX = elemek.Max(i => i.X);
//Y minimumának meghatározása, értéke 1
var MinY = elemek.Min(i => i.Y);
//Átlag terület meghatározása
var avg = elemek.Avg(i => i.Y * i.X);
Idetartozóan a .NET 6 újdonsága a MaxBy és MinBy műveletek. Ezek szintén maximum és minimum keresésre szolgálnak, de nem a megadott lambda kifejezés típusának megfelelő változóval térnek vissza, hanem az eredeti típussal. Ez azt jelenti, hogy az alábbi példában a Max és Min változó típusa Pelda lesz, és nem int:
//Pelda { X = 44, Y = 42 } objektum
var Max = elemek.MaxBy(i => i.X);
//Pelda { X = 7, Y = 1 } objektum
var Min = elemek.MinBy(i => i.Y);
A .NET 9 bevezeti a CountBy metódust is, amivel feltételesen tudunk megszámolni dolgokat:
elemek.CountBy(i => i.X == 10);
A metódus visszatérési értéke egy kulcs-érték párokból álló IEnumerable, ami jelen esetben IEnumerable<KeyValuePair<bool, int>> lesz, ami két elemet tartalmaz. Egy olyan KeyValuePair<bool, int> példányt, ahol a kulcs true és a hozzátartozó érték pedig azon elemek száma, ahol x értéke 10 volt és egy olyan KeyValuePair<bool, int> példányt, ahol a kulcs false és a hozzátartozó érték pedig azon elemek száma, ahol x értéke nem 10.
Egyedi aggregáció
Az Aggregate metódussal bármilyen tetszőleges IEnumerable<T> kollekción végezhetünk aggregációt, még akkor is, ha a típusok nem numerikusak. A metódus első paramétere a kezdő érték. A második paramétere egy lambda kifejezés, ami az összegzést végzi, a harmadik pedig egy projekciót ellátó függvény. Tételezzük fel, hogy adott egy tömbünk, ami gyümölcsök neveit tárolja:
string[] gyumolcsok = { "alma", "avokado", "mango", "narancs", "korte", "citrom", "csipkebogyo" };
Ha ebből a tömbből szeretnénk kinyerni a leghosszabb nevű gyümölcsöt, csupa nagybetűvel, akkor az alábbi Aggregate hívással tudnánk megtenni:
string nev = gyumolcsok.Aggregate(
"",
(eredmeny, aktualis) => aktualis.Length > eredmeny.Length ? aktualis : eredmeny,
gyumolcs => gyumolcs.ToUpper());
Az agregációs lambda kifejezésben az első paraméter az aggregáció aktuális állapota, a második paraméter pedig az éppen aktuálisan feldolgozott elem.
A .NET 9 bevezeti az AggregateBy metódust:
var nevek = gyumolcsok.AggregateBy(
gyumolcs => gyumolcs.StartsWith('a'),
"",
(eredmeny, aktualis) => aktualis.Length > eredmeny.Length ? aktualis : eredmeny);
A visszatérési típusunk itt is egy kulcs-érték párokból álló IEnumerable lesz. A metódus első paramétere a szűrésre szolgál. A második paraméter itt is a kezdő érték, a harmadik pedig az aggregációs függvény. A fenti példában a nevek változó típusa IEnumerable<KeyValuePair<bool, string>> lesz és két elemmel fog rendelkezni:
{ true, "avokado" }
{ false, "csipkebogyo" }
Konvertáló metódusok
A LINQ metódusok késleltetett végrehajtásúak. A több elemet visszaadó LINQ műveletek csak akkor kerülnek kiértékelésre, ha elkezdjük őket bejárni, vagy elemet választuk ki belőlük, vagy aggregálunk rajtuk, illetve akkor is, ha az eredményeket konvertáljuk Listára, Tömbre vagy Dictionary típusra. Ezekre a konverziókra a LINQ által biztosított metódusok a ToArray(), ToList() és ToDictionary().
A ToDictionary() metódus használatához két lambda kifejezést kell megadnunk paraméternek. Az első a kulcsok kiválogatására szolgál, a második metódus pedig az értékek kiválasztására szolgál.
Nézzünk erre egy példát! Tételezzük fel, hogy van egy osztályunk, ami neveket és telefonszámokat tartalmaz:
class Kontakt
{
public string Nev { get; set;}
public int Telefonszam { get; set; }
}
Ebben az esetben, ha van egy kontaktok nevű listánk, ami Kontakt példányokat tartalmaz és ezt Dictionary típussá szeretnénk alakítani, méghozzá úgy, hogy a kulcs a Név legyen, a hozzátartozó érték pedig a Telefonszam, akkor a ToDictionary() metódusnak az alábbi lambda paramétereket kell megadnunk:
var dictionary = kontaktok.ToDictionary(x => x.Nev, x => x.Telefonszam);
-
Adatok összegzése, összesítése és tárolása.↩