Nyheder i C# 4 – femte del: Valgfri og navngivne parametre

July 6th, 2010

Dette er femte indlæg i min serie om nyheder i C# 4. Efter at vi har set på dynamiske typer, er tiden kommet til valgfri og navngivne parametre.

Kort fortalt er effekten af disse to features, at vi kan undlade og/eller ændre på rækkefølgen af parametre, når vi kalder en metode. I en ideel verden hvor alle metoder har de og kun de parametre, de har brug for, er det måske ikke en særlig nyttig egenskab, men hvis man nogensinde har set Microsoft Offices COM interface, ved man, at det ikke er en ideel verden. Lad mig illustrere:

word.Documents.Open(ref fileName,
   ref missingValue, ref readOnlyValue,
   ref missingValue, ref missingValue,
   ref missingValue, ref missingValue,
   ref missingValue, ref missingValue,
   ref missingValue, ref missingValue,
   ref missingValue, ref missingValue,
   ref missingValue, ref missingValue);

Femten parametre skal der til at åbne et dokument i Word via COM. Femten!

I langt de fleste tilfælde har vi kun lyst til at angive et, nemlig filnavnet, men før C# 4 var vi nødsaget til at kalde metoden med alle femten parametre. At APIet desuden kræver at alt overføres som ref, gør kun problemet endnu mere udtalt (heldigvis er der også forbedringer på dette punkt, som vi skal se på i efterfølgende indlæg).

Med de nye tiltag i C# 4 ser ovenstående kald ud som følger:

word.Documents.Open("demo.docx", ReadOnly: true);

Meget bedre! Første parameter overføres via sin position, mens tredje parameter overføres ved sit navn. Det tillader os at springe parameter nummer to over. Derudover springer vi også alle de efterfølgende valgfri parametre over. Alt i alt giver det os en syntaks, der er langt mere læse- og skrivevenlig.

Lad os se på et par yderligere eksempler. Betragt nedenstående metode:

public static void ListFilm(string title, int releaseYear = 2010, float score = 0f) {
   Console.WriteLine("{0} ({1}): {2}", title, releaseYear, score);
}

Ovenstående erklærer en metode med tre parametre: title er obligatorisk, mens releaseYear og score er valgfri. Vi kan altså kalde metoden på forskellig vis, f.eks.:

ListFilm("Delicatessen", 1991, 7.9f);

// dårlig film, score er 0
ListFilm("Lara Croft: Tomb Raider", 2001); 

// udkommer i 2010 og vi regner med, at det er en god film
ListFilm("Les aventures extraordinaires d'Adèle Blanc-Sec", score: 8.0f);

Som det fremgår, kan vi kalde metoden afhængig af, hvor mange parametre vi kommer med. Hvis alle parametrene er navngivne, kan vi se helt bort fra rækkefølgen, men almindeligvis vil vi nok have et eller få obligatoriske parametre samt et par valgfri, så en blanding vil nok være det hyppigst forekomne.

Fælles for navngivne og valgfri parametre er, at der er tale om en compiler-feature. Al håndtering sker således på oversættelsestidspunktet, og der er ikke spor af hverken navngivne eller valgfri parametre på afviklingstidspunktet.

For valgfri parametre indsætter compileren simpelthen de konkrete standardværdier ved oversættelse af metodekaldet.

For navngivne parametre bruges navnene til at identificere de korrekte placeringer for parametrene, og derefter genereres metodekald på almindeligvis.

Der er selvfølgelig den detalje, at såvel standardværdier som navne bliver en del af metodens signatur på oversættelsestidspunktet, så ændringer af disse slår kun igennem ved genoversættelse. Assemblies, der ikke genoversættes, vil have tidligere standardværdier indlejret i koden. Dette er kun et problem, hvis assemblies oversættes og distribueres særskilt.

Valgfri og navngivne parametre er måske ikke et revolutionerende tiltag, men det er en god, lille forbedring, der i visse situationer kan bidrage væsentlig til læsbarheden af kode. Brug dem hvor det giver mening.

I næste indlæg ser vi mere på forbedringerne omkring integration med COM.

Justering af referencer i ubrugte objekter

July 5th, 2010

Dette indlæg omhandler forholdsvis eksotiske detaljer omkring implementeringen af garbage collection i .NET. Det vil næppe være relevant i ret mange tilfælde, men at kortlægge disse detaljer var en underholdende og lærerig øvelse, så dette indlæg er i realiteten mere et vidnesbyrd om, hvad vi kan lære om .NETs afviklingsmiljø ved hjælp af WinDbg og SOS.

En kollega kom for nogle dage siden forbi og spurgte, om jeg vidste noget om en funktion ved navn gc_heap::relocate_in_large_objects. Det gjorde jeg tilfældigvis, for jeg var selv stødt på denne lidt spøjst navngivne funktion i anden sammenhæng. Navnet kan jo få en til at tro, at objekter på Large Object Heap (LOH) bliver flyttet, hvilket jo som bekendt ikke er tilfældet. Funktionen står derimod for at justere referencer i objekter på LOH.

Betragt nedenstående figur.

LOH før garbage collection og compaction

Her har vi et array på LOH med referencer til objekter på den almindelige heap. Oprydning af sidstnævnte kan føre til at objekterne bliver rykket sammen for at forhindre fragmentering. Hvis dette sker, skal referencerne i vores array justeres, så de igen peger på de faktiske instanser. Efter en oprydning, sammenrykning og justering kan billedet se ud som følger:

LOH efter garbage collection og compaction

Det er der næppe noget nyt eller overraskende i, men min kollega synes at have observeret, at justering af referencer også skete, hvis den omtalte array ikke længere var i brug.

Kunne det være tilfældet?

Tænk lidt over det: Bliver referencer virkelig justeret for objekter, der ikke længere er i brug af applikationen?

Mit første svar var forkert. Jeg var overbevist om, at der ikke var nogen grund til at justere disse referencer i fald vores array ikke længere var i brug, men i stedet for at lade det komme an på, hvad jeg troede, satte jeg mig for at undersøge det.

Til formålet lavede jeg en lille testapplikation som angivet nedenfor:

sealed class SomeType {
   public int X { get; set; }
   public int Y { get; set; }

   public SomeType(int x, int y) {
      X = x;
      Y = y;
   }
}

sealed class Program {
   static void GenerateRandomObjects() {
      string s = null;
      for (int i = 0; i < 1000; i++) {
         s += i.ToString();
      }
      Console.WriteLine(s.Length);
   }

   static void Main() {
      Console.WriteLine("Press Enter to begin");
      Console.ReadLine();

      GenerateRandomObjects();
      var smallbuffer = new SomeType[100];
      var largebuffer = new SomeType[85000];

      for (int i = 0; i < smallbuffer.Length; i++) {
         var st = new SomeType(1, 2);
         smallbuffer[i] = st;
         largebuffer[i] = st;
      }

      Trace.WriteLine("Buffers filled");
      Debugger.Break();

      Trace.WriteLine(string.Format("smallbuffer is in gen{0}", GC.GetGeneration(smallbuffer)));

      Trace.WriteLine("Nulling root to largebuffer");
      largebuffer = new SomeType[200];
      Debugger.Break();

      Trace.WriteLine(string.Format("smallbuffer is in gen{0}", GC.GetGeneration(smallbuffer)));

      Trace.WriteLine("Collecting ...");
      GC.Collect(1);
      Trace.WriteLine(string.Format("smallbuffer is in gen{0}", GC.GetGeneration(smallbuffer)));
      GenerateRandomObjects();

      Trace.WriteLine("After GC");
      Debugger.Break();

      Console.WriteLine(smallbuffer.Length);
      Console.WriteLine(largebuffer.Length);
   }
}

Applikationen gør som følger:

  • Først opretter den et array, der er stort nok til, at det ryger på LOH.
  • Dette array sættes til at pege på et antal objekter allokeret på den almindelige heap. For at gøre det lettere at spore disse objekter, lader jeg også et mindre array referere disse.
  • Det næste skridt er, at sørge for at det store array ikke længere er refereret. Det er ikke helt så let, som det lyder. Det er ofte utilstrækkeligt at sætte den lokale reference til null. I mange tilfælde vil vi nemlig stadig have en gyldig reference på stakken eller i et register. Derfor er den bedste fremgangsmåde, at lade referencen pege på en ny instans. Det sikrer, at vi få overskrevet eventuelle gemte referencer.
  • Dernæst skal jeg iværksætte en garbage collection, der også laver compaction, så objekterne bliver flyttet på heapen. Dette er også mere tricky end som så, da vi ikke har mulighed for at angive, at der skal foretages compaction ved kald til GC.Collect(). Ergo, var jeg nødt til at prøve mig frem. Ved at lave en del tilfældige objekter rundt om mit lille array (og de refererede objekter) og derefter lave et kald til GC.Collect(1), fik jeg sat gang i den nødvendige oprydning. Så langt så godt.

For at undersøge om referencerne faktisk blev justeret i det store array, gjorde jeg følgende:

Ved første break brugte jeg !dumpheap til at finde mine arrays. Det mindste array har plads til 100 referencer, så jeg filtrerede listen, så jeg kun fik instanser på mere end 400 bytes.

0:004> g
Buffers filled
(1bdc.1918): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0000a020 edx=0030f230 esi=00687048 edi=00000064
eip=761d22a1 esp=0030f1a0 ebp=0030f23c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNELBASE!DebugBreak+0x2:
761d22a1 cc              int     3
0:000> !dumpheap -type Object[] -min 400
 Address       MT     Size
02427060 64cf6c28     8208
026dfc7c 64cf6c28      416
026f9758 64cf6c28      528
03421010 64cf6c28     4096
03422020 64cf6c28      528
03422240 64cf6c28     4096
03423250 64cf6c28     8176
03425250 64cf6c28   340016
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
64cf6c28        8       366064 System.Object[]
Total 8 objects

De to interessante instanser var henholdsvis 026dfc7c (smallbuffer) og 03425250 (largebuffer).

For at verificere, at de begge pegede på samme elementer, udskrev jeg første element i hver array.

Først smallbuffer:

0:000> !da -details -length 1 026dfc7c
Name:        LOHRoots.SomeType[]
MethodTable: 64cf6c28
EEClass:     64a79698
Size:        416(0x1a0) bytes
Array:       Rank 1, Number of elements 100, Type CLASS
Element Methodtable: 001638cc
[0] 026dfe1c
    Name:        LOHRoots.SomeType
    MethodTable: 001638cc
    EEClass:     001614e4
    Size:        16(0x10) bytes
    File:        C:\dev2010\LOHRoots\LOHRoots\bin\Release\LOHRoots.exe
    Fields:
              MT    Field   Offset                 Type VT     Attr    Value Name
        64d42978  4000001        4             System.Int32      1     instance            1     <X>k__BackingField
        64d42978  4000002        8             System.Int32      1     instance            2     <Y>k__BackingField

Og derefter largebuffer:

0:000> !da -details -length 1 03425250
Name:        LOHRoots.SomeType[]
MethodTable: 64cf6c28
EEClass:     64a79698
Size:        340016(0x53030) bytes
Array:       Rank 1, Number of elements 85000, Type CLASS
Element Methodtable: 001638cc
[0] 026dfe1c
    Name:        LOHRoots.SomeType
    MethodTable: 001638cc
    EEClass:     001614e4
    Size:        16(0x10) bytes
    File:        C:\dev2010\LOHRoots\LOHRoots\bin\Release\LOHRoots.exe
    Fields:
              MT    Field   Offset                 Type VT     Attr    Value Name
        64d42978  4000001        4             System.Int32      1     instance            1     <X>k__BackingField
        64d42978  4000002        8             System.Int32      1     instance            2     <Y>k__BackingField

Læg mærke til, at begge elementer peger på 026dfe1c. Så langt så godt.

Herefter kørte jeg videre til det punkt, hvor referencen til largebuffer overskrives:

0:000> g
smallbuffer is in gen0
Nulling root to largebuffer
(1bdc.1918): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0000a020 edx=0030f230 esi=00687048 edi=0242b3f8
eip=761d22a1 esp=0030f1a0 ebp=0030f23c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNELBASE!DebugBreak+0x2:
761d22a1 cc              int     3

Herefter verificerede jeg, at applikationen ikke længere havde roots til largebuffer.

0:000> !gcroot 03425250
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1918
Scan Thread 2 OSTHread 1d34

Ingen roots, så den instans largebuffer peger på, kan ryddes op på dette tidspunkt. Videre til næste stop.

0:000> g
smallbuffer is in gen0
Collecting ...
smallbuffer is in gen1
After GC
(1bdc.1918): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0000a020 edx=0030f230 esi=00687048 edi=0242865c
eip=761d22a1 esp=0030f1a0 ebp=0030f23c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNELBASE!DebugBreak+0x2:
761d22a1 cc              int     3

På dette tidspunkt har der netop været en garbage collection. Vi ved endnu ikke, om der også har været compaction. For at undersøge dette kan vi se, om vores arrays er blevet flyttet.

0:000> !dumpheap -type Object[] -min 400
 Address       MT     Size
0242566c 64cf6c28     8208
0242a3d0 64cf6c28      416
0243fb64 64cf6c28      816
03421010 64cf6c28     4096
03422020 64cf6c28      528
03422240 64cf6c28     4096
03423250 64cf6c28     8176
03425250 64cf6c28   340016
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
64cf6c28        8       366352 System.Object[]
Total 8 objects

Læg mærke til, at vores lille array er flyttet fra 026dfc7c til 0242a3d0, mens vores store array stadig befinder sig på 03425250. Der har altså været compaction i forbindelse med denne garbage collection.

Det interessante er således at finde ud af, om referencerne i vores store (og ubrugte) array er blevet justeret. Lad os se på det lille array:

0:000> !da -details -length 1 0242a3d0
Name:        LOHRoots.SomeType[]
MethodTable: 64cf6c28
EEClass:     64a79698
Size:        416(0x1a0) bytes
Array:       Rank 1, Number of elements 100, Type CLASS
Element Methodtable: 001638cc
[0] 0242a570
    Name:        LOHRoots.SomeType
    MethodTable: 001638cc
    EEClass:     001614e4
    Size:        16(0x10) bytes
    File:        C:\dev2010\LOHRoots\LOHRoots\bin\Release\LOHRoots.exe
    Fields:
              MT    Field   Offset                 Type VT     Attr    Value Name
        64d42978  4000001        4             System.Int32      1     instance            1     <X>k__BackingField
        64d42978  4000002        8             System.Int32      1     instance            2     <Y>k__BackingField

Og derefter det store:

0:000> !da -details -length 1 03425250
Name:        LOHRoots.SomeType[]
MethodTable: 64cf6c28
EEClass:     64a79698
Size:        340016(0x53030) bytes
Array:       Rank 1, Number of elements 85000, Type CLASS
Element Methodtable: 001638cc
[0] 0242a570
    Name:        LOHRoots.SomeType
    MethodTable: 001638cc
    EEClass:     001614e4
    Size:        16(0x10) bytes
    File:        C:\dev2010\LOHRoots\LOHRoots\bin\Release\LOHRoots.exe
    Fields:
              MT    Field   Offset                 Type VT     Attr    Value Name
        64d42978  4000001        4             System.Int32      1     instance            1     <X>k__BackingField
        64d42978  4000002        8             System.Int32      1     instance            2     <Y>k__BackingField

Læg mærke til at begge arrays peger på samme element. Referencen i det store array er således rettet fra 026dfe1c til 0242a570, og dermed kan vi konkludere, at det store array er blevet opdateret på trods af, at applikationen ikke længere har nogen gældende reference til dette array.

Hvordan kan det være?

Tænker vi lidt over, hvad der sker under en garbage collection, giver det faktisk god mening, men det er måske ikke så intuitivt ved første øjekast.

En garbage collection lægger ud med at lave en analyse af hvilke objekter på den aktuelle del af heapen, der stadig er i brug. Disse markeres som værende i brug. Alle andre objekter kan ryddes op. Nøgleordet her er den aktuelle del af heapen. I og med at testprogrammet kører en GC.Collect(1) foretages denne analyse således kun for objekter der ligger i generation 0 og 1. Vores store array ligger på LOH, og er således ikke berørt af denne analyse.

Da oprydningen også iværksætter en flytning af objekterne i dette tilfælde, er garbage collection-rutinen nødt til at opdatere referencerne til de flyttede objekter, men da den på dette tidspunkt ikke ved, at vores store array ikke længere er i brug, er den nødt til at opdatere disse referencer.

Disponeringen giver god mening, hvis analysen af aktive rødder er mere omfattende end justering af referencerne. Skønt det optimale ville være at springe opdateringen over, kan det sagtens give mening at udføre denne – i teorien – overflødige handling, hvis disse forhold er til stede. Den analyse har jeg svært ved at lave, men jeg har været i kontakt med en af de ansvarlige udviklere, og Microsoft kender til problematikken, og derfor har jeg tillid til, at de har lavet de nødvendige analyser og undersøgelser.

Ændrer vi testapplikationen så den kalder GC.Collect(2) i stedet (eller blot GC.Collect(), der ligeledes laver en komplet oprydning), laver garbage collection-rutinen en fuld analyse af heapen og finder således ud af, at det store array ikke længere er i brug. I det tilfælde opdateres referencerne naturligvis ikke, da den nødvendige viden er tilgængelig i dette tilfælde.

Som nævnt tror jeg ikke, at dette er et reelt problem i nogen væsentlig udstrækning. For at denne situation skal have indflydelse på afviklingen, skal applikationen have mange midlertidige objekter på LOH, disse skal i stor udstrækning referere objekter på den almindelige heap, og compaction skal foretages jævnligt. I det tilfælde vil man muligvis kunne spore en effekt, men det er i realiteten et afledt problem af at have mange midlertidige objekter på LOH. Det er af flere grunde en dårlig ide, og dette er således kun en af flere uheldige konsekvenser af den situation.

Microsoft MVP Award 2010

July 1st, 2010

Microsoft MVPFor 3. gang i træk kan jeg sole mig lidt i indforstået, nørdet verdensberømmelse, idet jeg netop har fået besked om, at jeg har modtaget Microsofts MVP Award for 2010. Men selvom jeg har prøvet det før, er jeg alstå ikke mindre stolt af den grund.

Nyheder i C# 4 – fjerde del: ExpandoObject og DynamicObject

June 28th, 2010

Efter en længere pause som følge af en svær lungebetændelse, fortsætter jeg her med fjerde indlæg i min serie om nyhederne i C# 4.

I de foregående indlæg har vi set på motivationen for dynamiske typer, eksempler på brug af dynamiske typer og den underliggende infrastruktur for dynamiske typer. I dette indlæg er vi kommet til to nye, dynamiske typer i .NET 4, ExpandoObject og DynamicObject.

ExpandoObject

ExpandoObject er en type, der tillader, at vi kan modificere egenskaberne for konkrete instanser. Opretter vi en dynamisk instans af ExpandoObject, kan vi således tilføje properties, metoder og så videre til denne. Her er et eksempel, hvor vi tilføjer en property ved navn Text:

dynamic expando = new ExpandoObject();
expando.Text = "hello world";

Herefter kan vi tilgå denne property på instansen på almindelig vis, og ganske som forventet udskriver nedenstående teksten hello world.

Console.WriteLine(expando.Text);

Denne egenskab gør, at vi kan skrive kode, der i nogle tilfælde bliver betydelig mindre omfattende, end vi er vant til. Lad os se på et eksempel.

Når vi arbejder med XML, er konstruktioner som nedenstående hyppige:

var movie = new XElement("Movie",
   new XElement("Title", "Blade Runner"),
   new XElement("Release", "1982"),
   new XElement("Cast",
      new XElement("Actor", "Harrison Ford"),
      new XElement("SupportingActor", "Rugter Hauer"),
      new XElement("Actress", "Sean Young"),
      new XElement("SupportingActress", "Daryl Hannah")
   )
);

Console.WriteLine((string)movie.Element("Cast").Element("Actor"));

Ved hjælp af Element(), kan vi udvælge specifikke dele af vores XElement-struktur. Dette er blot en af flere varianter over samme tema, men de fleste af dem fungerer ved, at vi bruger tekst til at navigere rundt i strukturen. I de tilfælde er brugen af dynamiske typer oplagt.

Ved hjælp af ExpandoObject, kan vi erklære en tilsvarende struktur således:

dynamic movie = new ExpandoObject();
movie.Title = "Blade Runner";
movie.Release = "1982";
movie.Cast = new ExpandoObject();
movie.Cast.Actor = "Harrison Ford";
movie.Cast.SupportingActor = "Rutger Hauer";
movie.Cast.Actres = "Sean Young";
movie.Cast.SupportingActres = "Daryl Hannah";

Fordi vores instans af ExpandoObject tilpasser sig det konkrete behov, kan vi blot tilføje egenskaber til objektet efter behov. Brugen ligner til forveksling den kode, vi ville skrive, hvis vi havde en eksplicit type til håndtering af vores film, men vi har ikke skrevet kode til erklæring af denne type.

Tilgang til dele af objektet ligner ligeledes den kode, vi ville skrive, hvis vi havde en eksplicit type:

Console.WriteLine(movie.Cast.Actor);

Bemærk at som jeg også var inde på i forrige indlæg, er det vigtigt, at instansen erklæres med typen dynamic, da det er denne angivelse, der tillader ExpandoObject at redefinere sig selv efter behov. Havde vi erklæret movie af typen ExpandoObject, havde vi fået en instans med denne statiske type, og dermed afskåret os selv mulighederne for at definere opførslen ved hjælp af det dynamiske metaobjekt.

Lad os udbygge eksemplet lidt. Hvordan ville det se ud, hvis vi havde brug for en samling af film-objekter baseret på ExpandoObject? Her er et bud:

var movies = new List<dynamic>();

movies.Add(new ExpandoObject());
movies[0].Title = "Blade Runner";
movies[0].Release = 1982;

movies.Add(new ExpandoObject());
movies[1].Title = "Alien";
movies[1].Release = 1979;

dynamic movie = new ExpandoObject();
movie.Title =  "Delicatessen";
movie.Release = 1991;
movies.Add(movie);

Ikke mange overraskelser her og ganske som forventet, kan vi bruge LINQ på vores samling af film:

var movies80 = from m in movies
               where m.Release >= 1980 &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; m.Release < 1990
               select m.Title;

foreach(var m in movies80) {
    Console.WriteLine(m);
}

Som det fremgår, tilbyder ExpandoObject en simplere syntaks for eksemplerne i dette tilfælde. Performance er i begge tilfælde begrænset af de dynamiske opslag, så her adskiller brugen af ExpandoObject sig hverken i positiv eller negativ retning. Det er selvfølgelig værd at understrege, at syntaks ikke er alt, og hvis vi har en rig datamodel i koden, er vi bedre tjent med statiske typer, men ExpandoObject tilbyder et interessant alternativ i specifikke situationer.

DynamicObject

DynamicObject tilbyder langt hen ad vejen den funktionalitet, vi så implementeret via IDynamicMetaObjectProvider i forrige indlæg. Det interessante ved DynamicObject er, at den fritager os for en del af arbejdet med implementering af dette interface ved at udstille et sæt virtuelle metoder, vi kan overstyre efter behov for at få lignende funktionalitet.

Har vi behov for en type, hvor vi kan omdefinere hele eller en del af den gængse opførsel, kan vi således med fordel gøre dette ved at lave en specialisering af DynamicObject.

Den åbenlyse begrænsning ved DynamicObject er, at at vi kun kan bruge den, hvis vi ikke har brug for yderligere specialiseringer. Skal vores type ligeledes arve fra en anden type, er vi nødt til at gå den slagne vej og implementere IDynamicMetaObjectProvider selv. I de resterende tilfælde kan vi med fordel gøre brug af DynamicObjects implementering.

Lad os se på et eksempel.

public sealed class DynamicType : DynamicObject {
   public override bool TryInvokeMember(
      InvokeMemberBinder binder,
      object[] args, out object result) {

      result = null;

      Console.WriteLine("Calling the method {0}", binder.Name);
      return true;
   }
}

Ovenstående type overstyrer TryInvokeMember(), hvilket vil sige, at hver gang, vi kalder en metode på en instans af denne type, vil vi få kaldt ovenstående. Vi kan derfor erklære en dynamisk instans og kalde et antal ikke-eksisterende metoder således:

dynamic d = new DynamicType();

d.Boing();
d.Boom();
d.Tschak();

Resultatet er udskriften Calling the method Boing, Calling the method Boom og Calling the method Tschak. Det er selvfølgelig ikke særlig brugbart i sig selv, men det illustrerer, hvordan vi kan redefinere, hvad det vil sige, at kalde metoder på en instans af denne type.

For et øjeblik siden påstod jeg, at TryInvokeMember() bliver kaldt hver gang, vi kalder en metode på en instans af vores type. Det er ikke helt korrekt. Havde det været korrekt, ville vi kunne implementere eksemplet fra forrige indlæg via DynamicObject, men det kan vi ikke. Kalder vi en metode, der findes på typen, bliver denne kaldt. Så hvis vi eksempelvis gør følgende:

Console.WriteLine(d.GetType());

Får vi udskriften NewsInCS4.DynamicType og ikke Calling the method GetType, som man ellers kunne tro.

Det skyldes, at DynamicObject først undersøger, om en given metode er tilgængelig via objektets statiske type. Så hvis vi kalder en eksisterende metode, kommer TryInvokeMember() ikke ind i billedet, og derfor kan vi ikke bruge DynamicObject til at implementere en type, hvor der er byttet om på to metoder.

DynamicObject tilbyder således stort set de samme muligheder, som hvis vi selv implementerer IDynamicMetaObjectProvider, men da den fritager os for en del af implementeringsarbejdet, vil det i mange tilfælde være lettere at lave en specialisering af DynamicObject end at skulle implementere IDynamicMetaObjectProvider.

Nyheder i C# 4 – tredje del: The Object is King

June 17th, 2010

Dette er tredje indlæg i en serie om nyhederne i C#. Foregående indlæg er her og her.

I forrige indlæg antydede jeg, at dynamic tilbyder større fleksibilitet end Reflection, og det skal vi se på i dette indlæg.

Sammen med C#s nye dynamic nøgleord er der ligeledes kommet nogle udvidelser til klassebiblioteket og afviklingsmiljøet for at understøtte de dynamiske egenskaber. Formålet er at understøtte et koncept, Microsoft har kaldt ”The Object is King”. Ideen er at introducere en abstraktion for, hvad det vil sige, at gøre noget på et objekt. Centralt i denne sammenhæng er det nye interface IDynamicMetaObjectProvider.

Hvis en type implementerer dette interface, betyder det, at typen selv håndterer, hvad det vil sige, at udføre forskellige operationer på instanser af typen. Mere specifikt betyder det, at typen kan returnere en instans af DynamicMetaObject, og det er denne instans, der står for nævnte funktionalitet. Ved at overstyre metoder i DynamicMetaObject, kan vi således definere objektets eksakte grundopførsel.

Lad os se på et eksempel, men lad os først få et par aspekter omkring statiske typer på plads. Betragt nedenstående metode:

public void SomeMethod(SomeType instance) {
   instance.Method();
}

Hvad sker der her? Som udgangspunkt får vi kaldt Method på en instans af typen SomeType. Hvis SomeType er en referencetype, Method er virtual, og det aktuelle objekt, instance peger på, er af en specialisering af SomeType, kan vi få kaldt en variant af Method. I begge tilfælde kan vi dog på oversættelsestidspunktet generere den specifikke kode, fordi C#s typebinding dikterer, hvad der skal ske her. (1)

Lad os nu forestille os, at vi flytter den handling der afgør, at vi kalder Method fra SomeType fra oversættelsestidspunktet til afviklingstidspunktet. Hvis metoden var erklæret som void SomeMethod(dynamic instance) ville det være præcis det, der ville ske, men lad os gå et skridt videre.

Lad os ydermere forestille os, at selve afgørelsen omkring, hvad det vil sige, at kalde en metode er blevet uddelegeret. I så fald er det ikke længere C#s statiske typesystem, der afgør, at effekten af instance.Method() er at kalde koden på SomeType associeret med Method(). I stedet kan vi implementere præcis den opførsel, vi har brug for. Det er, hvad IDynamicMetaObjectProvider tillader os.

Lad os se på et eksempel. Betragt nedenstående klasse:

public sealed class WeirdType : IDynamicMetaObjectProvider {
   public void Foo() {
      Console.WriteLine("Foo");
   }

   public void Bar() {
      Console.WriteLine("Bar");
   }

   public DynamicMetaObject GetMetaObject(Expression parameter) {
      return new WeirdTypeMetaObject(parameter, this);
   }
}

WeirdType har to simple metoder, Foo og Bar, der henholdsvis udskriver teksterne ”Foo” og ”Bar”. Kalder vi dem som følger, får vi helt som forventet udskriften: Foo efterfulgt af Bar.

var wt = new WeirdType();
wt.Foo();
wt.Bar();

(Husk var blot betyder, at compileren sætter typen af wt til den type, den kan udlede af tildelingen – altså WeirdType. Der er ikke noget magi, og det er ikke farligt.)

Derudover implementerer WeirdType IDynamicMetaObjectProvider og har derfor metoden GetMetaObject, som returnerer en instans af WeirdTypeMetaObject. Det betyder, at hvis vi kalder metoder på en dynamisk erklæret instans, vil det være opførelsen i det returnerede metaobjekt, der definerer den tilhørende funktionalitet. Det er lidt af en mundfuld, så lad mig prøve at konkretisere.

Som et illustrativt eksempel kunne vi få metaobjektet til at bytte om på metoderne, så når vi kalder Foo(), afvikles koden for Bar() og omvendt. For at gøre dette, skal vi implementere den nødvendige logik i WeirdTypeMetaObject.

Ændrer vi således ovenstående eksempel fra var til dynamic, vil compileren vide, at den er nødt til at overlade håndteringen af wt til afviklingsmiljøet, og eftersom typen implementerer IDynamicMetaObjectProvider, ved afviklingsmiljøet, at den aktuelle opførsel for instansen er defineret af vores metaobjekt.

For at gøre en lang historie kort: Kører vi nedenstående, får vi udskriften Bar efterfulgt af Foo, fordi vi har redefineret, hvad det vil sige, at kalde metoder på objektet.

dynamic wt = new WeirdType();
wt.Foo();
wt.Bar();

Eksemplet er selvsagt ikke det mest brugbare, men det illustrerer to vigtige pointer:

For det første viser det, at der kan gælde helt andre regler for, hvad det vil sige, at udføre operationer på en instans, når referencen til denne er erklæret som dynamic.

For det andet illustrerer det, at vi kan implementere regler, der ændrer de grundlæggende spilleregler fundamentalt. Det er en stærk feature, der kan åbne nogle døre i specielle situationer, men det er selvfølgelig også noget, der bør bruges med omtanke.

Definitionen af WeirdTypeMetaObject ser ud som følger:

public class WeirdTypeMetaObject : DynamicMetaObject {
   public WeirdTypeMetaObject(Expression expression, WeirdType value)
      : base(expression, BindingRestrictions.Empty, value) {}

   public override DynamicMetaObject BindInvokeMember(
      InvokeMemberBinder binder, DynamicMetaObject[] args) {

      Action action;

      if (binder.Name == "Bar") {
         action = new Action(((WeirdType) Value).Foo);
      } else if (binder.Name == "Foo") {
         action = new Action(((WeirdType) Value).Bar);
      } else {
         return base.BindInvokeMember(binder, args);
      }

      return new DynamicMetaObject(
         Expression.Block(
            Expression.Call(Expression.Constant(Value), action.Method),
            Expression.Default(typeof(object))
         ),
         BindingRestrictions.GetInstanceRestriction(Expression, Value),
         Value
      );
   }
}

Jeg vil ikke komme ind på detaljerne i ovenstående i dette indlæg, men jeg skal nok vende tilbage til det senere, da der er mindst en overraskelse, der kan drille lidt.

Ved at implementere IDynamicMetaObjectProvider får vi komplet kontrol over en types basale semantik. Desværre er det en lidt omstændelig affære at implementere dette interface, og hvis vi ikke har brug for at kunne kontrollere alt, tilbyder .NET to typer, der laver meget af arbejdet for os. I næste indlæg skal vi således se nærmere på DynamicObject og ExpandoObject.


  1. Faktisk ”snyder” Microsoft lidt her, for hvis SomeType er en referencetype genererer compileren et kald via callvirt, uanset om der er tale om en virtuel metode eller ej. Fordi vi kalder en metode via et referenceargument, kan referencen være null og derfor udnytter compileren, at callvirt laver et implicit null-tjek- Af den grund ser vi callvirt her i stedet for call.

Nyheder i C# 4 – andel del

June 11th, 2010

Dette er andet indlæg i en lille serie om nyhederne i C# 4. I forrige indlæg så vi på udviklingen af C#, og specifikt på motivationen for at udvide sproget med adgang til dynamiske typer. I dette indlæg skal vi se på et konkret eksempel på brug af dynamiske typer.

Så lad os kaste os ud i det. Betragt nedenstående stump kode:

Adder adder = GetAdder();
int result = adder.Add(40, 2);
Console.WriteLine(result);

Det er, som vi kender det. Ingen dynamiske typer. Det er plain vanilla C#, men lad os alligevel bruge et øjeblik på at slå fast, hvad der sker her.

Forudsat at vi har en type kaldet Adder med en metode kaldet Add, der tager to ints og returnerer en int samt en lokal metode ved navn GetAdder, der returnerer en instans af Adder, oversætter dette og fungerer som forventet.

Vi kan udføre det samme på dynamisk vis ved hjælp af Reflection. I så fald ser kode ud som følger:

object adder = GetAdder();
Type adderType = adder.GetType();
object returnObject = adderType.InvokeMember("Add",
   BindingFlags.InvokeMethod, null, adder, new object[] { 40, 2 });
int result = Convert.ToInt32(returnObject);
Console.WriteLine(result);

Ikke alene fylder dette en del mere, det er også sværere at læse, men den grundlæggende funktionalitet er som ovenfor.

Vi har stadig en GetAdder(), men da vi ikke vil lægge os fast på en bestemt type til returværdien, er vi nødt til at betragte den som en instans af object.

Vi vil stadig gerne kalde Add(), men for at kunne gøre det via Reflection, er vi nødt til at have fat i den aktuelle type på returværdien fra GetAdder() først. Via typeobjektet kan vi kalde metoden, men vi er nødt til at beskrive signaturen, hvordan typebinding skal foretages, hvilken instans metoden skal kaldes for samt de relevante argumenter til metoden. Derfor bliver kaldet til InvokeMethod() ret omfattende.

Returværdien er ligeledes repræsenteret via en instans af object, så for at få pillet værdien ud af denne, er vi nødt til at kalde ToInt32(), og herefter er vi i mål.

Ulemperne ved ovenstående er indlysende. Det er tungt at skrive og tungt at læse. Der er adskillige muligheder for fejl på afviklingstidspunktet. Måske har typen ikke en Add(), måske er argumenterne ikke korrekte, måske får vi noget tilbage, der ikke kan konverteres til int.

Dertil kommer, at hver gang vi kalder ovenstående, skal alle disse elementer verificeres, og det tager naturligvis lidt tid.

Hvad er fordelen så? Fordelen er, at ovenstående virker, uanset hvad GetAdder() end returnerer, så længe der er tale om en instans, der har en Add() med den korrekte signatur. Koden er således ikke afhængig af specifikke typer, men derimod specifikke egenskaber ved vilkårlige typer.

Med dynamiske typer får vi større fleksibilitet samt bedre ydelse med en syntaks, der ligger meget tæt op ad den vi kender fra statiske typer. Med dynamiske typer ser koden således ud:

dynamic adder = GetAdder();
dynamic result = adder.Add(40, 2);
Console.WriteLine(result);

Nu ligner koden til forveksling, det vi kender med den ene forskel, at vi har udskiftet de statiske typer med det nye nøgleord dynamic. Ligesom Reflection-eksemplet fungerer dette, uanset hvad GetAdder() end returnerer, så længe den returnerede instans kan honorere kaldet til Add(). Det er fleksibelt og letlæseligt.

Vi har naturligvis stadig samme faldgruber som ved Reflection. Hvis metoden ikke findes, får vi en fejl på kørselstidspunktet. Hvis argumenterne til metoden ikke passer, får vi ligeledes en afviklingsfejl. I teorien kunne vi også få en fejl, hvis WriteLine ikke kunne kaldes med den konkrete instans af result, men eftersom WriteLine() ender med at kalde ToString(), som instansen får fra object, er der ingen ko på isen her.

Den tilgængelige syntaks er ikke den eneste fordel. I vores Reflection-eksempel betaler vi prisen for dynamikken ved hvert kald, så hvis vi kalder ovenstående i en løkke, kommer programmet til at lave det samme arbejde mange gange. Vi kan selvfølgelig gemme resultatet af forarbejdet og genbruge det i løkken, men det kræver mere kode.

Med DLRen får vi dette forærende af selve afviklingsmiljøet i form af call site caching, så når den specifikke Add() metode er blevet identificeret for vores aktuelle instans, gemmes denne oplysning og genbruges automatisk, hvis vi f.eks. kalder Add() flere gange i en løkke.

Okay, er humlen så, at vi blot har fået pænere og hurtigere Reflection? Ikke helt for dynamic kan mere end Reflection, men det ser vi på i næste indlæg.

Nyheder i C# 4 – første del

June 4th, 2010

Dette er første indlæg i en lille serie omkring nyhederne i C# 4.

Med frigivelsen af Visual Studio 2010, har vi også fået adgang til den nyeste version af C#, men der er meget mere end det på tapetet. Ikke alene har Visual Studio gennemgået en større om- og udbygning. .NET afviklingsmiljøet, klassebiblioteket og C# er alle ligeledes blevet opdateret med nye features.

Jeg vil i første omgang begrænse mig til nyhederne i selve C# sproget, men i senere indlæg vil jeg også komme ind på nogle af nyhederne i klassebiblioteket.

Nyhederne i C# 4 er:

Men først …

Lidt historie

C# har gennemgået en omfattende udvikling gennem årene. Den første version kom i 2002 og blev af mange betragtet som en Redmond-version af Java. Sprogene lignede da også hinanden til forveksling, men de begyndte allerede at divergere i 2005, da version 2 tilføjede generics til ikke bare sproget men også til selve afviklingsmiljøet. Dermed adskilte C# sig fra både Java og C++ på dette punkt.

C#s udvikling

I 2007 introducerede Anders Hejlsberg og Co. Language INtegrated Query, LINQ, og dermed fik C# et sæt ret unikke features, der var med til at lægge afstand til Java.

Seneste skud på stammen er muligheden for at arbejde med dynamiske typer. Så til alle dem, der råbte vagt i gevær omkring brugen af var: Det er nu, I skal være bange.

Sammenlignet med de to forrige versioner af C#, er denne nyhed muligvis ikke så revolutionerende. Har man brug for at arbejde med dynamiske typer, vil man værdsætte de nye tiltag, men hvis alle ens udfordringer kan løses med statiske typer, vil de nye muligheder muligvis kun bidrage som ammunition i den verserende skyttegravskrig mellem tilhængere af henholdsvis dynamiske og statiske sprog.

I modsætning til generics og LINQ vil dynamiske typer ikke ændre den gængse brug af sproget, men det er et værktøj, der er særdeles anvendelig i visse situationer.

Dynamiske typer

Hvad er dynamiske typer, og hvad er der sket med .NET for at imødekomme brugen af disse?

Svaret på det første spørgsmål er, at dynamiske typer er typer, hvor undersøgelser og beslutninger omkring typernes egenskaber foretages på afviklingstidspunktet i stedet for på oversættelsestidspunktet.

Så de tjek, compileren normalt laver som en del af oversættelsen, laves i stedet på afviklingstidspunktet. Det vil selvfølgelig også sige, at de fejl, der under normale omstændigheder ville give fejl under oversættelsen, giver nu fejl under kørslen i stedet.

Det er der ikke noget nyt i. Konceptet findes allerede i mange andre sprog som f.eks. Javascript, Perl, Python, Ruby og mange andre. Det nye består i, at C# nu tillader, at typer kan opføre sig på denne måde, og vi kan således have kode, der benytter såvel statiske som dynamiske typer.

Svaret på det andet spørgsmål er lidt mere omfattende. CLRen har nemlig fået et sæt af udvidelser, der tilsammen betegnes Dynamic Language Runtime eller blot DLR.

Dynamic Language Runtime

Overordnet set tilbyder DLRen i skrivende stund tre komponenter:

Dynamic dispatch er den mekanisme, der gør den dynamiske håndtering mulig. I C# er det repræsenteret ved dels et nyt reserveret ord, dynamic samt et interface, IDynamicMetaObjectProvider, der angiver, hvordan det dynamiske skal opføre sig. Hvis en type implementerer dette interface, er det et signal til afviklingsmiljøet om, at der gælder specielle regler for, hvad det vil sige at udføre operationer på instanser af denne.

Expression trees er som sådan ikke nye, men de er blevet udvidet til at kunne håndtere de nødvendige sprogkonstruktioner.

Call site caching er en optimering, der gør, at omkostningerne ved dynamiske opslag på afviklingstidspunktet reduceres. Første gang en metode kaldes på et dynamisk objekt, er afviklingsmiljøet nødt til at finde ud af, hvad det vil sige, at kalde denne metode. Denne operation tager tid, så derfor gemmer afviklingsmiljøet resultatet, så det kan genbruges for lignende operationer.

Med de faciliteter DLRen stiller til rådighed, er det let at implementere nye dynamiske sprog i .NET-verden. De to mest populære for tiden er nok IronPython og IronRuby.

Det essentielle er dog ikke, at vi har fået et par nye, hippe sprog. Den store nyhed er, at afviklingsmiljøet nu tilbyder en platform til implementering af disse eller dele deraf. Da de dynamiske faciliteter er en del af infrastrukturen, får vi en masse af den grundlæggende forærende, og det er ret interessant. På samme måde som CLRen giver os en fælles platform på tværs af sprog, giver DLRen os nu et fundament til at bygge dynamiske systemer.

Vi kan afvikle kode skrevet i dynamiske sprog som IronPython eller IronRuby, vi kan interagere med dynamiske miljøer som COM, DOM og så videre, og vi kan lade dele af vores system implementere en dynamisk adfærd, som vi definerer. Alt dette vil jeg se nærmere på i de efterfølgende indlæg.