2.11. Lambda väljendid ja LINQ Lambda väljendid on funktsioonide lihtsustatud kirjapaneku viis. Lambda väljend kirjeldab tehte mustrit, see koosneb ümarsulgudes olevatest parameetritest, lambda operaatorist (=>) ja C# avaldisest selle järel. Lambda väljendi tulemus sõltub vaid väljendis olevatest parameetritest, muud seadistused tulemust ei mõjuta. Lambda operaatorit loetakse "muutub" (goes to). Lambda arvutust (λ arvutus) kasutatakse matemaatilises loogikas ja funktsionaalprogrammeerimises. Lisaks C# keelele saab neid kasutada ka näiteks keeltes C++ v11, F#, Delphi, Java, JavaScript, Perl, PHP, Python, R, Smalltalk Visual Basic.NET 9. Lambda väljendid kompileeritakse samasuguseks CLR koodiks nagu ka tavapärased C# funktsioonid ja programmi töö kiirust nende kasutamine ei muuda, küll aga saab kood lühem ja asjatundjale kiiremini jälgitav. Lambda väljendid võimaldavad lühidalt vormistada päringuid muutujakogusse. Andmekogusse suunatud lambda päringu jaoks on enamasti tarvis nimeruumi Linq vahendeid. Lambda väljendite kasutamine ei muuda C# programmi töö kiirust. Operaator => eraldab parameetreid ja avaldist, see ei tähista otsest tehet. Lambda väljendi parameetrid võivad olla deklareeritud tüübiga või määratlemata tüübist. Viimasel juhul järeldab tüübi kompilaator. Kui parameetreid on vaid üks, ei ole ümarsulud selle ümber vajalikud. Kui väljendi tulemus ei ole muutuja väärtus, st on konstant või toiming, võib parameeter puududa. Lambda väljendi parameetrid võivad olla näiteks järgmisel kujul. Func<int, int> multiplybyfive = x => x * 5; Func<int, int, int> sumtwo = (int a, int b) => a + b; Func<int, int, int> add = (a, b) => a + b; Func<double> pi = () => Math.PI; Action<string> write = s => Console.WriteLine(s); Eeltoodud väljendite kasutuse näited. void LINQ_usage() var result = multiplybyfive(10);// result = 50; result = sumtwo(5, 20);// result = 25; result = add(5, 20);// result = 25; var p = pi(); write("done"); LINQ (Language Integrated Query) on objektorienteeritud päringukeel. Päringuga loetakse andmeid andmeallikast. LINQ andmeallikad võivad olla erinevat tüüpi: XML fail, SQL Server andmebaas, mälus hoitavad andmestikud ja struktuurid C# programmis. LINQ funktsioonide kasutamiseks C# programmis tuleb koodi päisesse lisada viide selle nimeruumile Linq kujul: using System.Linq;. Tüüpilised LINQ päringu tingimused on from, mis määrab andmeallika, where mis määrab filtri ja select, mis täpsustab väljastatavate elementide tüübi. Kusjuures neid märksõnu kasutatakse vastupidises järjekorras võrreldes SQL päringuga. Peamised LINQ päringu meetodid on koos näidetega loetletud tabelis 6. LINQ on objektorienteeritud päringukeel.
Tabel 6. Peamised objektide pärimise meetodid nimeruumis System.Linq. Näidetes kasutatakse siinsamas peatükis allpool loodud klassi Prof ja loetelu Profs. Meetod All<TSource>() Any<TSource>() Average() Concat<TSource>() Contains<TSource>() Count<TSource>() Distinct<TSource>() First<TSource>() GroupBy<TSource, TKey>() Last<TSource>() Max() Min() OrderBy<TSource, TKey>() OrderByDescending <TSource, TKey>() Kirjeldus ja näide Kontrollib, kas kõik elemendid vastavad tingimusele. bool kõikvanad = Profs.All(p => p.ageinyears > 60); Kontrollib, kas mõni element vastab tingimusele. bool m6ninoor = Profs.Any(p => p.ageinyears < 40); Tunnuse keskmine. Tulemus vormindatakse muutuja tüübi järgi. double keskminevanus = Profs.Average(p => p.ageinyears) Ühendab kaks kogumit. IEnumerable<string> mkoos = m1.concat(m2); Kontrollib, kas kogum sisaldab mingit elementi. bool bxon = märgid.contains("bx"); Elementide arv kogumis. Pane tähele, et massiividel on sama tähendusega omadus Array.Length;, mis on kiirem. int n = märgid.count(); Väljastab üksteisest erinevad väärtused. IEnumerable<string> kasutatudmärgid = märgid.distinct(); Väljastab esimese elemendi, mis vastab antud tingimusele, kui tingimus on esitatud. Prof vanim = Profs.First(p => p.ageinyears > 60); Grupeerib objektid antud tunnuse järgi. IEnumerable<IGrouping<string, string> query = Profs.GroupBy(p => p.institution, p => p.surname); Väljastab viimase elemendi, mis vastab antud tingimusele, kui tingimus on esitatud. Prof vvana = Profs.Last(p => p.ageinyears > 60); Väljastab suurima väärtuse. Tulemus vormindatakse muutuja tüübi järgi. int maxage = Profs.Max(p => Väljastab väikseima väärtuse. Tulemus vormindatakse muutuja tüübi järgi. int maxage = Profs.Min(p => Järjestab objektid antud tunnuse järgi. IEnumerable<Prof> orderedprofs = Profs.OrderBy(p => p.dateofbirth); Järjestab objektid antud tunnuse järgi alanevas järjekorras. IEnumerable<Prof> orderedprofs =
Reverse<TSource>() Single<TSource>() Sum() Take<TSource>() TakeWhile<TSource>() ThenBy<TSource, TKey>() ThenByDescending <TSource, TKey>() Union<TSource>() Profs.OrderByDescending(p => p.dateofbirth); Pöörab objektide järjekorra vastupidiseks. List<Prof> ProfsReversed = Profs.Reverse(); Väljastab ühe tingimusele vastava objekti. Kui tingimusele vastab enam kui üks objekt, siis annab veateate. try Prof Mänd = Profs.Single(p => p.surname == "Mänd"); catch (InvalidOperationException) Console.WriteLine("Loetelus on mitu Mändi."); Väljastab summa. double sumage = Profs.Sum(p => Väljastab antud arvu objekte kogumi algusest. IEnumerable<Prof> Profs = Profs.OrderBy(p => p.dateofbirth).take(2); Väljastab objektid kogumi algusest kuni tingimus on tõene. List<Prof> p2 = orderedprofs.takewhile(p => p.ageinyears > 60).ToList(); Järjestamine teise tunnuse järgi. IEnumerable<Prof> Profs1 = Profs.OrderBy(p => p.surname).thenby(p => p.name); Järjestab objektid teise tunnuse järgi alanevas järjekorras. IEnumerable<Prof> orderedprofs = Profs.OrderByDescending(p => p.dateofbirth).thenbydescending (p => p.name); Ühendab kaks kogumit. LINQ päringute kasutusala on laiem kui vaid SQL päringuid toetavad andmebaasid, LINQ objektidega kaudu on keerukaid päringuid koostada lihtsam kui pikkade SQL lausetena; VS annab LINQ päringute kirjutamisel vihjeid ja kompileerimisel kontrollib päringu süntaksi. LINQ päringud on paraku aeglasemad kui SQL laused C# koodis, sest LINQ on vaid SQL lausete vahendaja. Suhtlus SQL andmebaasiga toimub ka LINQ päringute puhul ikkagi SQL keeles. LINQ on SQL vahendaja, mis kasutab objekte. LINQ päringud kirjutatakse kas päringu süntaksiga või meetodi süntaksiga. Päringu süntaks on pikem ja meenutab SQL päringuid (va järjekord from, where, select). Meetodi süntaksi kasutamisel tuleb objekti nime järele punkt liosada ja VS pakub selle objekti meetodeid. Enamasti saab LINQ meetodi kirjutada lambda väljendina. Selle peatüki näidetes kasutatakse struktuuri Prof, mis on deklareeritud järgmises näidiskoodis. Vanuse arvutamise meetod ei ole selles näites päris täpne, sest ei arvesta ajavööndeid, kus isik sündis ja kus on ta praegu, ega liigaastaid ega kella nihutamise võimalikku mõju.
public struct Prof public string Name get; set; public string Surname get; set; public string Institution get; set; public DateTime DateOfBirth get; set; public bool Male get; set; public int AgeInYears get; set; public Prof(string name, string surname, string institution, DateTime dateofbirth, bool male) Name = name; Surname = surname; Institution = institution; DateOfBirth = dateofbirth; Male = male; TimeSpan diff1 = DateTime.Now.Subtract(dateOfBirth); DateTime Age = DateTime.MinValue + diff1; AgeInYears = Age.Year 1; //MinValue is 1/1/1 Järgmises näites on professorite nimekirja lisatud konkreetsed isikud ning seejärel valitakse professorid vanuses üle 60 a. algul LINQ päringuga ja siis lambda väljendi abil. static void Main() CultureInfo ci = new CultureInfo("et EE"); //Funktsioon, mis teisendab teksti kujul sünnikuupäeva DateTime vormingusse Func<string, DateTime> strtodate = (x) => DateTime.ParseExact(x, "dd.mm.yyyy", ci); List<Prof> Profs = new List<Prof>(); Profs.Add(new Prof("Ülo", "Mander", "ÖMI", strtodate("11.01.1954"), true)); Profs.Add(new Prof("Tõnu", "Oja", "ÖMI", strtodate("28.06.1955"), true)); Profs.Add(new Prof("Jaak", "Jaagus", "ÖMI", strtodate("24.12.1956"), true)); Profs.Add(new Prof("Tiit", "Tammaru", "ÖMI", strtodate("04.02.1972"), true)); Profs.Add(new Prof("Toomas", "Tammaru", "ÖMI", strtodate("03.05.1968"), true)); Profs.Add(new Prof("Raivo", "Mänd", "ÖMI", strtodate("02.12.1954"), true)); Profs.Add(new Prof("Marika", "Mänd", "EMÜ", strtodate("11.12.1954"), false)); Console.WriteLine("Professorid vanuses üle 60 a LINQ süntaksi abil."); var selectedprofs = from p in Profs where p.ageinyears > 60 select new Prof(p.Name, p.surname, p.institution, p.dateofbirth, p.male); foreach (Prof p in selectedprofs)
Console.WriteLine("Professorid vanuses üle 60 Lambda väljendi abil (meetodi süntaks)."); foreach (Prof p in Profs.FindAll(e => (e.ageinyears > 60))) Järgmises näites moodustatakse teatmetabel professorite loetelust. professorite nimed sünniaasta kaupa. Seejärel kuvatakse ILookup<int, string> lookuptable = Profs.ToLookup(e => e.dateofbirth.year, e => e.surname + ", " + e.name); Console.WriteLine("Professorid sünniaastate kaupa"); foreach (var groupitem in lookuptable) Console.WriteLine(groupItem.Key); foreach (var obj in groupitem) Console.WriteLine(obj); Järgnevas näites moodustatakse veel mõned väljavõtteid ja kokkuvõtteid professorite loetelust kasutades lambda väljendeid. Pane tähele, et pole oluline, mis tähte kasutatakse lambda väljendis objekti tähistamiseks. See täht ei tohi vaid olla kasutusel mõne muutuja tähistamiseks. Järgnevates näidetes tähistab muutuja p üksikut professorit muutujakogus, täht e aga sama objekti lambda väljendis. Need muutujad võiksid olla ka sama tähistusega. Console.WriteLine("Professorid vanuse järjekorras"); foreach (Prof p in Profs.OrderBy(e => e.dateofbirth)) Console.WriteLine("Keskmine vanus = " + Profs.Average(e => e.ageinyears) + "\n"); Console.WriteLine("Nendest professoritest noorim on " + Profs.Min(e => e.ageinyears) + " aastane.\n"); Console.WriteLine("Kaks noorimat on:"); foreach (Prof p in Profs.OrderByDescending(e => e.dateofbirth).take(2)) Console.WriteLine(p.Name + " " + p.surname); Console.WriteLine("\nProfessorid vanuses üle 60 a"); foreach (Prof p in Profs.FindAll(e => (e.ageinyears > 60))) Console.WriteLine("\nNendest professoritest töötavad ÖMIs:"); foreach (Prof p in Profs.FindAll(e => e.institution == "ÖMI")) Console.WriteLine("Nimi : " + p.name + " " + p.surname);
Console.WriteLine("\nKas professorite loetelus on naisi?"); if (Profs.Exists(e => e.male == false)) Console.WriteLine("Jah, nimestikus on naissoost professoreid."); else Console.WriteLine("Ei, nimestikus ei ole naissoost professoreid."); Console.WriteLine("\nNimestikus on " + Profs.Count(e => e.surname == "Mänd") + " professorit perenimega Mänd.\n"); LINQ päringuga valitud üksikobjektide sirvimiseks ja käsitsemiseks kasutatakse foreach kordust. Seejuures rakendub LINQ päring alles foreach tsükli käivitamisel. Kui moodustatud kogumi liikmeid kasutatakse foreach korduses, siis ei ole vajadust kogumi tüüpi täpsustada ja saab kasutada kogumite üldist liidest IEnumerable<>. Kui soovitakse saada kindlat tüüpi kogumit, siis tuleb uue kogumi tüüp deklareerida. Järgnevas näites on kaks alternatiivset võimalust uue kogumi moodustamiseks LINQ meetodiga. Esimesel real moodustatakse kordumatute string tüüpi märkide komplekt üldise kogumina, teisel real teisendatakse kogum tüübiks List<string>. IEnumerable<string> kasutatudmärgid1 = märgid.distinct(); List<string> kasutatudmärgid2 = märgid.distinct().tolist(); Lisaks siin näites esitatud ToList() meetodile sisaldab nimeruum Linq veel kogumi tüübi teisendamise meetodeid: ToArray(), ToDictionary() ja ToLookup().