Dennis' Blog

Avonturen in .NET
posts - 37, comments - 633, trackbacks - 0, articles - 0

Thursday, November 10, 2005

Het is alweer enige tijd geleden dat ik iets over C# 3.0 geschreven heb, maar dat ga ik nu (min of meer) goedmaken. Ik vervolg deze serie nu met een aantal artikelen over een van de toepassingen van de nieuwe mogelijkheden: Linq.

Linq staat voor Language Integrated Query. Zoals de naam al aangeeft geeft ons dat de mogelijkheid om in de taal (in dit geval C# maar VB.Net kan er ook mee overweg) queries te definieren. Het verhaal achter het ontstaan van Linq zal ik hier even kort schetsen, zodat je een beeld hebt wat de geschiedenis is.

Neem eens een stuk code dat je recentelijk geschreven hebt. Kijk er eens goed naar en ga dan kijken hoe vaak je in die code "zoekt" naar zaken. Denk bijvoorbeeld aan het doorlopen van een ArrayList instance met daarin Customer objecten. Regelmatig schrijf je code die in die lijst op zoek gaat naar alle klanten die nog een betaling open hebben staan. Of je wilt een overzicht van alle klanten in je lijst die aangegeven hebben dat ze informatie per email willen ontvangen. Of je wilt simpelweg in die lijst op zoek gaan naar die ene klant met klantnummer 'xyz123' zodat je de gegevens daarvan op het scherm kunt tonen. Als je goed kijkt, zul je zien dat het zoeken naar gegevens in lijsten (in wat voor vorm dan ook) best vaak voorkomt.

Echter, een goede methode om te zoeken is er niet. Neem de volgende code eens:

// Initializeer de boel
Customer displayCustomer = null;
bool bFound = false;
int counter = 0;
 
// Zoek de juiste klant
while( (!bFound) && (counter < customerList.Count) ) {
    displayCustomer = (Customer)customerList[counter];
    if( displayCustomer.CustomerID == "xyz123" ) {
        bFound = true;
    }
    counter++;
}
 
if( bFound ) {
    // Laat zien
} else {
    // Geef foutmelding
}

// Enzovoorts...

Ik weet het, deze code kan beter. Je kunt natuurlijk een specifieke CustomerList type definieren, met daarin de juiste zoekcode. Maar die zal er niet echt anders uitzien dan bovenstaande code.

Zoeken is behoorlijk belangrijk de meeste, serieuze applicaties. Er zijn vele voorbeelden te verzinnen: zoeken naar de inlognaam van de gebruiker om zijn of haar wachtwoord te controleren, zoeken naar de bestanden in de plugin-directory, zoeken naar de geinstalleerde printers op het systeem, zoeken naar.. affijn, je snapt het wel.

Zoeken gebeurdt uiteraard ook in databases. Da's mooi, zul je denken, want daar hoef ik niet zo veel voor te doen. Alles wat ik moet doen is de juiste SQL code schrijven en de database regelt het zelf wel. Maar heb je je wel eens gerealiseerd dat dat eigenlijk een hele vreemde constructie oplevert? Je schrijft hele mooie, goed gedocumenteerde, door de compiler op alle details gecontroleerde C# of VB.Net code, vervolgens plak je daar een string (!!!) in met daarin een stuk code in een hele andere taal, namelijk SQL. Denk daar eens over na: in onze code staat opeens een niet te controleren, niet te compileren en dus niet te verifieren string met een andere programmeertaal daarin. Vreemd, vind je niet?

Als je nu veel met XML werkt (en wie doet dat tegenwoordig niet?) dan krijg je te maken met weer een hele andere manier van zoeken in data. Nu kun je met XQuery of XPath aan de gang gaan, of je itereert zelf over de elementen in je XML document heen en goet alles zelf.

Zoeken, zoeken en zoeken. We doen het nogal eens in onze applicatie, op heel veel verschillende en elkaar min of meer tegensprekende methodes. Om daar nu wat aan te doen, is het Project Linq geboren. Het idee hierachter is: laten we alle mogelijke manieren van zoeken (querien in slecht nederlands) nu eens onder de loep nemen en daar een uniforme, strongly typed, verifieerbare en compileerbare methode voor verzinnen. En dat hebben ze gedaan.

Al gauw bleek dat dat niet zo eenvoudig was en om dat op een goede manier te doen moest de taal C# en VB.Net aangepast worden. Welke aanpassingen dat zijn, heb je in mijn vorige postings kunnen lezen (ja ja, er zat een lijn in het verhaal!). Met die uitbreidingen is Linq mogelijk geworden. Ik zal nog even op een rijtje zetten welke nieuwe mogelijkheden er komen:

  • Type inferrence (var q = ....)

  • Extension methods

  • Initializers

  • Anonymous types

  • Lambda expressions

Met dit alles kunnen we bovenstaande code anders gaan schrijven, op een manier die ons behoorlijk bekend voor zal komen:
 

var displayCustomer = from customer in customerList
                      where customer.CustomerID == "xyz123"
                      select customer;
 
if (displayCustomer != null)
{
    // Laat zien
}
else
{
    // Geef foutmelding
}

En dit doet dus precies hetzelfde...

Laat je niet verwarren door de rare plaatsing van 'select', dit leg ik nog wel uit. Maar afgezien daarvan is dit 'gewoon' SQL in C# syntax. De enige aanpassing die ik gedaan heb is dat customerList nu geen ArrayList meer is maar een List<Customer>. Dat maakt echter voor mijn verhaal niets uit.

Laten we eens kijken hoe dit nou kan.

Je moet weten dat de namespace System.Query (waar Linq in is gedefnieerd) voornamelijk bestaat uit een aantal extension methods op het type IEnumerable<T>. Met andere woorden: alles wat de interface IEnumerable<T> implementeert heeft een aantal extra methods gekregen. Een van die methods is bijvoorbeeld Where(); Die zit er als volgt uit:

IEnumerable<T> IEnumerable.Where<T>((T) => bool : predicate)

Met andere woorden: de method Where heeft als resultaat een IEnumerable<T> en als argument de lambda expression (T) => bool: predicate, oftewel er wordt een anonymous method gemaakt die een bool terug geeft. Dus als we dat even toepassen op onze customerList gaat dat er als volgt uitzien:

var resultList = customerList.Where( (Customer c) => c.CustomerID == "xyz123" );

Vertaling: neem onze List<Customer> customerList (welke IEnumerable<Customer> implementeert). Voer daar de 'Where' method op uit. In die Where method geven we mee de lambda expressie (nogmaals, lees mijn vorige postings over dit onderwerp om te begrijpen wat dat precies is) (Customer c) => c.CustomerID == "xyz123" . Er wordt nu dus een anonymous method gedefnieerd die voor iedere Customer instance in de lijst kijkt naar de CustomerID en die vergelijkt met "xyz123". Daar komt een bool uit (true of false) en als hij true is, dan wordt die Customer instance aan de nieuwe resultList toegevoegd. Overigens is resultList ook een class die IEnumerable<T> implementeert....

Kijk hier nog eens goed naar, laat het even bezinken. Ik wacht wel even.

Goed. Het resultaat is dus een IEnumerable<T>. Daarop kunnen we dan weer andere extension methods toepassen, welke bijna allemaal een IEnumerable<T> terug geven. We kunnen dus een hele rij van dit soort methods aanroepen: Lijst.Where().Where().GroupBy().Select(); Anders weergegeven:

Lijst.Where().
 Where().
 GroupBy().
 Select();
 

Nou hebben we net gezien in mijn voorbeeld dat we helemaal geen IEnumerable<T>.Where() method gebruiken. Nee, we roepen gewoon from type x in lijst where x.iets == "bla" select x.ID; of zo iets dergelijks. De reden dat dat werkt is door het gebruik van Expression Trees. Dat is wellicht iets voor andere posting, maar neem nu maar even van me aan dat dat er voor zorgt dat onze mooie, SQL-achtige syntax verandert in die reeks van method calls en dat alle code achter bijvoorbeeld de where vertaalt wordt in een lambda expressie. Moeilijk is dat niet, het is een kwestie van haakjes en dergelijke toevoegen.

Dat betekent dat onze eerste voorbeeld code er eigenlijk zo uit gaat zien:

var displayCustomer = customerList
    .Where((Customer customer) => customer.CustomerID == "xyz123")
    .Select(customer => customer);

En deze code moet nu te begrijpen zijn.

Je ziet het: als je er even naar kijkt, is het op zich vrij logisch. Maar je moet er even aan wennen. Persoonlijk vind ik mijn eerste Linq voorbeeld qua code duidelijker en overzichtelijker dan mijn eerst code-voorbeeld in deze posting.

Volgende keer ga ik kijken naar DLinq, oftewel Linq'en op een database. Het zal je niet verbazen dat dat vrijwel hetzelfde werkt als wat ik je nu heb laten zien.

Tot dan!

PS Mocht je opmerkingen, vragen of iets anders te melden hebben, aarzel dan niet om te reageren, hetzij via het commentaarvakje, hetzij via de mail. Op die manier weet ik of er uberhaupt mensen geinteresseerd zijn in wat ik te melden heb over dit onderwerp.

posted @ 10:10 PM | Feedback (56)