Ugrás a tartalomhoz

NET-es programozási technológiák

Kovásznai Gergely, Biró Csaba

EKF TTK

15. fejezet - LINQ (írta: Kovásznai Gergely)

15. fejezet - LINQ (írta: Kovásznai Gergely)

Számítógépes alkalmazásokban igen gyakori feladat, hogy adatoknak egy gyűjteményét kell kezelnünk, megjelenítenünk. Mindehhez kapcsolódva a gyűjteményeken sokszor kell műveleteket végeznünk, például rendeznünk kell a gyűjtemény elemeit, szűréseket kell a gyűjteményen végeznünk, vagy esetleg kettő (vagy több) gyűjteményt össze kell kapcsolnunk egy lekérdezésen belül (join).

Az előző fejezetekben formos, közelebbről WPF-es, alkalmazások fejlesztésével foglalkoztunk. Képzeljünk el egy formot, melyen dolgozók adatait jelenítjük meg egy ListBox-ban! Jó lenne, ha a dolgozókat nevük szerinti ábécé sorrendbe tudnánk rendezni, akár egy kattintással, vagy esetleg csak a fejlesztési részlegen dolgozókat megjeleníteni, illetve az éppen szabadságon levőket kiszűrni.

Mindezen feladatok adatbázis-kezelésből ismerősek lehetnek. Az ebben a fejezetben bemutatandó .NET-es technológia, a LINQ (Language Integrated Query) azonban lehetővé teszi gyűjtemények sokkal általánosabb célú felhasználását. Az továbbiakban átvesszük a LINQ alapokat, majd a LINQ to Objects technológiával ismerkedünk meg, mellyel a objektumaink gyűjteményein tudunk lekérdezéseket végezni. A . fejezetben a LINQ to XML, a . fejezetben pedig a LINQ to Entities technológiával ismerkedünk majd meg.

De miről is van szó tulajdonképpen? Legyen a programunkban például egy lista, melyben városok neveit tároljuk:

List<string> cities = new List<string> {

        "Zalaegerszeg", "Miskolc", "Székesfehérvár",

        "Debrecen", "Eger", "Kőszeg"

};

Szeretnénk rendezni a városneveket hosszuk szerint. Ezt a következőképpen tehetjük meg nagyon egyszerűen:

       IEnumerable<string> citiesToDisplay = cities.OrderBy(c => c.Length);

Hogy még halmozzuk az élvezeteket, a rendezés előtt szeretnénk kiválogatni azokat a városneveket, melyek legalább 8 betűből állnak:

IEnumerable<string> citiesToDisplay = cities

                .Where(c => c.Length >= 8)

                .OrderBy(c => c.Length);

A fenti kódrészletekben több érdekes (és esetleg új) dolgot is láthatunk. Valószínűleg a legszembetűnőbbek a műveleteket végző metódusok (Where, OrderBy); ezeket és társaikat bővítő metódusoknak nevezzük. A legfurcsábbak ezen bővítő metódusok paraméterei, melyek ...=>... formájúaknak tűnnek; ezek az ún. lambda kifejezések. A lambda kifejezésekről a . fejezetben, a bővítő metódusokról pedig a . fejezetben lesz részletesebben szó.

Egy villanásnyi példa erejéig szeretnénk megmutatni a fenti lekérdezésnek egy másik (C# 3.0-tól kezdve teljesen hivatalos) szintaxisát is:

IEnumerable<string> citiesToDisplay = from c in cities

                                                                                where c.Length >= 8

                                                                                orderby c.Length

                                                                                select c;

Ezt az előzővel teljesen ekvivalens (esetlegesen az SQL-re emlékeztető) kifejezést a C#-ban lekérdező kifejezésnek nevezik, és a . fejezetben fogunk vele részletesebben foglalkozni.

Lambda kifejezések

A lambda kifejezések nagyon intuitív elemei a C#-nak, annak 3.0-ás verzióját kezdve. Mielőtt azonban belemerülnénk a használatukba, próbáljuk megérteni, miféle elemei is ezek a nyelvnek!

Legelőször érdemes feleleveníteni a delegate-ekkel kapcsolatos ismereteinket (Reiter, 2009). Mint az közismert, ezek egyfajta metódus-típusokként működnek, és a segítségükkel tudunk olyan metódusokat írni, mely „metódusmutatókat” fogadnak paraméterül. Vegyünk egy példát: szeretnénk egy FilterDates metódust írni, mely dátumoknak egy listájából elemeket szűr ki. Nem fixáljuk a metódusban, hogy a szűrés milyen szempontok alapján történjen, hanem ezt a „szempontot” paraméterként szeretnénk majd a metódusnak átadni. Erre szolgál a FilterDates egy speciális paramétere, melyben a szűrő metódusunkat (illetve arra egy „mutatót”) tudjuk átadni. A delegate szolgál arra, hogy a szűrő metódus szignatúráját (értsd: paraméterei és visszatérési típusát) előre meghatározzuk. A lenti példában egy saját delegate-t készítünk, mely egy DateTime paramétert ír elő és bool visszatérési típust.

delegate bool DateFilter(DateTime date);

List<DateTime> FilterDates(List<DateTime> dates, DateFilter filter)

{

        List<DateTime> filteredDates = new List<DateTime>();

        foreach (DateTime d in dates)

                if (filter(d))

                        filteredDates.Add(d);

        return filteredDates;

}

Nagyon fontos, hogy később bármilyen metódust is fogunk a FilterDates-nek átadni, annak szignatúrájának pontosan meg kell majd egyeznie a delegate által megadottal. Például:

bool Filter21stCentury(DateTime date)

{

        return date.Year > 2000;

}

...

List<DateTime> dates = ...;

List<DateTime> datesToDisplay = FilterDates(dates, Filter21stCentury);

Tegyük fel, hogy a Filter21stCentury metódust sehonnan máshonnan nem fogjuk használni. Az ilyen „egyszer használatos” metódusok esetén igen kényelmetlen, hogy szépen, akkurátusan, nevesítve kell őket definiálnunk. C# 2.0-tól kezdve azonban névtelen metódusokat is átadhatunk paramétereként. Például a fenti legalsó metódushívást a következőképpen is megírhatjuk (anélkül, hogy külön, nevesítve definiálnánk a szűrő metódust):

List<DateTime> datesToDisplay = FilterDates(dates,

        delegate(DateTime d) { return d.Year > 2000; }

);

A lambda kifejezések tulajdonképpen a névtelen metódusokra egy másfajta, intuitívabb szintaktika, amit a C# 3.0-ban vezettek be. A fenti kódrészlet lambda kifejezéssel a következőképpen írható:

List<DateTime> datesToDisplay = FilterDates(dates, d => d.Year > 2000);

A lambda kifejezések fontos kelléke a => (nyíl) operátor. Érdekesség, hogy a paraméter (d) típusát a fordító kikövetkezteti a környezetéből. Több paramétert is használhatunk, illetve paraméter nélküli lambda kifejezést is írhatunk, sőt a lambda kifejezés jobb oldala akár egy teljes blokk is lehet (hiszen ez tulajdonképpen egy névtelen metódus törzse):

(a, b) => (a + b) / 2

() => 42

(x, y) => {

        if (x > y) return x;

        else return y;

}

Egyetlen paraméter esetén nem kötelező kitenni a zárójeleket, illetve egyszerű (csak „return …;”) jobb oldal esetén nem kötelező sem a return-t, sem a kapcsos zárójeleket szerepeltetni.