C# 3.0 - Language INtegrated Query (LINQ)

C# 3.0 bringer mange nye spændende sprogkonstruktioner med sig. Den mest spændende sprogudvikling er dog Language INtegrated Query (LINQ). Med LINQ bliver det muligt at lave forespørgelser direkte i C# 3.0.

Moderne lagdelte softwarearkitekturer og objektorienterede programmeringssprog har medført, at selve programmeringen af systemer er blevet sværere. Der kræves ofte dybdegående kendskab til flere teknologier og typesystemer for at få hul hele vejen igennem den lagdelte arkitektur. C# 3.0 gør op med meget af kompleksiteten ved at implementere forespørgselssproget LINQ. Med LINQ er det transparent, hvad det er for datakilde der laves forespørgelser mod. Det betyder, at du som udvikler kan skrive forespørgsler mod objektstrukturer, XML og databaser, helt uden at forlade C# 3.0 og uden at bekymre dig synderligt om datakilden, du arbejder op mod.

LINQ i aktion
Lad os kaste os ud i det. I følgende eksempel bruges der flere af de nye sprogkonstruktioner: Extension Methods, Local Type Inference, Object Initialization Expressions og Query Expressions. Konstruktionerne behandles i det efterfølgende.

Hello world!
Hvad kan være mere passende end at lave "Hello World" i C# 3.0 for at have et udgangspunkt for resten af artiklen?
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace LINQDemo
7 {
8 class Program
9 {
10 static void Main(string[] args)
11 {
12 Programmer p1 = new Programmer {
13 Favoritsprog = "C#",
14 Fornavn="Hans"};
15 Programmer p2 = new Programmer {
16 Favoritsprog = "VB.NET",
17 Fornavn = "Per"};
18 List<Programmer> liste = new List<Programmer>();
19 liste.Add(p1); liste.Add(p2);
20 var query = from p in liste
21 where p.Favoritsprog == "C#"
22 select new { p.Fornavn,
23 p.Favoritsprog };
24 foreach (var x in query)
25 {
26 Console.WriteLine(x);
27 Console.WriteLine("Fornavn: {0}",
28 x.Fornavn);
29 }
30 Console.ReadKey();
31 }
32 public class Programmer : Person
33 {
34 public string Favoritsprog;
35 }
36 public abstract class Person
37 {
38 public string Fornavn;
39 public string Efternavn;
40 public int ID;
41 }
42 }
43 }

I det efterfølgende gennemgås, hvorledes dette resultat opnås.

Object Initialization Expressions
For den, der er bekendt med C# 2.0, springer linje 12-18 som det første i øjnene. Der er nu en lettere måde at instantiere objekter på kaldet Object Initialization Expressions. Der er sparet nogle linjer kode, enten i overdefinerede konstruktorer og/eller kald til properties.

Local Type Inference
I linje 20 begynder C# 3.0 rigtigt at vise sig, hvor det nye keyword 'var' introduceres. 'var' keywordet gør det muligt at undlade at specificere typen, for derefter at udlede typen fra det udtryk som instantierer objektet. Det betyder, at følgende udtryk er identiske:

int a = 1;
int b = a;
og
int a = 1;
var b = a;

I sidste linje kan det udledes, at b er af typen int, da a er af typen int. Man kan frygte, at den dovne programmør formentlig allerede her har set fidusen - pas på læsevenligheden af koden, når du anvender type inferens. Det er værd at bemærke, at typer kun kan udledes inden for samme scope, deraf lokal type inferens.

Anonymous Types
I linje 22 bruges new keywordet for eksplicit at deklarere, at der kommer en ny type retur - en anonym type som består af to attributter: Fornavn og Favoritsprog. Hvis man kigger i debugger visualizeren fra Visual Studio, så fremgår det, at typestærkheden bibeholdes, eftersom typen kan udledes.

Query Expression
Det næste der springer i øjnene i linje 20 og frem, er noget der kan ligne et omvendt SQL udtryk - det er et Query Expression. Der er hældt syntaktisk sukker i rå mænger ud over Query Expressions, for at gøre dem mere forståelige! For at forstå Query Expression er vi nødsaget til at træde et skridt tilbage og kigge på Extension Methods og Lambda Expressions som udtrykket i linje 20 gør brug af:

var query = liste.
Where(p => p.Favoritsprog == "C#").
Select(p => new { p.Fornavn,p.Favoritsprog });

Extension Methods
Hvis vi starter med Where og Select metoderne, så er de det, hvad der kaldes Extension Methods. I linje 18 er liste objektet defineret med den generiske type List<Programmer>, som normalt ikke har disse metoder. List<Programmer> klassen "udvides" ganske enkelt ved at inkludere et namespace. I denne situation System.Linq, der indeholder metoder som "vedhæftes" klassen. I følgende eksempel udvider jeg Programmer klassen ved at lave min egen extension method.

static class MyExtensions
{
public static bool IsAGreatProgrammer(this Programmer p)
{
return (p.Erfaring >= 3 && p.Favoritsprog
== "C#" ? true:false);
}
}

Extension methods skal have statisk signatur og være public. Desuden skal typen, der extendes, præfikses med keywordet this. Hvis extension method er defineret i et andet namespace, skal det namespace selvfølgelig inkluderes med using direktivet. Hvis man leger lidt med Reflector (kan hentes på www.aisto.com/roeder/dotnet/) og kigger nærmere på klassen Enumerable i System.Linq namespace'et, ses det, at der er mange Extension Methods med parametertypen (this) IEnumerable

<T>. Det betyder, at alle objekter, der implementerer IEnumerable<T> og har System.Linq namespace'et inkluderet, vil have blandt andet Where og Select metoder tilgængelig.

Lambda Expression
Mere interessant bliver det (for nogen) når vi kigger på parameteren til f.eks. Where metoden:
p => p.Favoritsprog == "C#"
Ovenstående er et såkaldt lambda expression , som er en mere præcis måde at udtrykke anonyme metoder på. Lambda'er skal læses på følgende måde: parametre => udtryk. Kompileren omsætter Query Expressions til lambda expressions, hvilket betyder, at kendskab til lambda'er ikke er strengt nødvendigt. Omskrevet til C# 2.0 og med anvendelse af anonyme metoder, så ser samme funktionalitet således ud:

IEnumerable<Programmer> coolProgrammmers = liste.Where
(delegate(Programmer p)
{
return p.Favoritsprog == "C#";
}
);
Der er mange fordele ved at bruge lambda udtryk i stedet for anonyme metoder. Det første, som springer i øjnene, er den mere kompakte måde at skrive koden på. Det er også værd at bemærke, at man i lambda udtrykket har lokal type inferens. I den anonyme metode kræves parametertypen specificeret eksplicit. I lambda'en ved kompileren (og Visual Studio) godt, at p er af typen Programmer. Det elegante ved lambda udtryk er, at de kan omsættes til delegates som eksekveres samtidig med, at de kan omsættes til expression trees. Expression trees er en repræsentation af lambda'en, som der efterfølgende kan arbejdes og optimeres på, før den kompileres og eksekveres.

Sådan kommer du videre

Du har nu fået en introduktion til, hvordan der laves forespørgsler med LINQ i objektstrukturer. Næste skridt vil være at kigge på forespørgsler mod databaser og XML-filer ved brug af LINQ. For at lege med C# 3.0 og LINQ kan du downloade næste generation af Visual Studio, kodenavn Orcas, her: msdn2.microsoft.com/en-us/vstudio/aa700831.aspx. God fornøjelse!



Her finder du mere
Online

MSDN Guide til C# 3.0: msdn.microsoft.dk/guide


MSDN C# website: msdn.microsoft.com/vcsharp


Blogs

Blogs fra C# teamet: msdn2.microsoft.com/en-us/vcsharp/aa336719.aspx


Mads Torgersen: blogs.msdn.com/madst/


Henrik Vestergaard: bloks.msdn.com/henrikwh


Bøger
Introducing Microsoft® LINQ, Pialorsi & Russo, MS Press

Gratis foredrag om LINQ
Få demonstreret de nye muligheder, hør om hvordan de nye sprogfaciliteter fungerer og se de mange interessante perspektiver, LINQ vil få for softwareudvikling fremover.
Tid: Onsdag den 12. september 2007 kl. 16.45-19
(dørene lukkes præcist!).
Sted: Artpeople, Ørstedhus, Vester Farimagsgade 41, 7. sal,
1606 København V
Tilmelding: www.prosa.dk/kursus