onsdag 2. desember 2009 C#
Ta en titt på følgende program, og se om du kan utlede hva output blir når det kjøres.
1 class SomeClass
2 {
3 protected SomeClass()
4 {
5 SomeFunction();
6 }
7 protected virtual void SomeFunction()
8 {
9 Console.WriteLine("SomeFunction in SomeClass");
10 }
11 }
12
13 class SomeDerivedClass : SomeClass
14 {
15 private readonly string msg;
16
17 public SomeDerivedClass(string msg)
18 {
19 this.msg = msg;
20 }
21 protected override void SomeFunction()
22 {
23 Console.WriteLine(msg);
24 }
25 }
26
27 class Program
28 {
29 static void Main(string[] args)
30 {
31 var d = new SomeDerivedClass("Constructed in main");
32 Console.ReadLine();
33 }
34 }
Ikke juks! Hva tror du det blir? Jeg skal vedde på at i alle fall forfatteren av SomeDerivedClass håper output blir "Constructed in main". Det blir det derimot ikke - Dette programmet har ikke noe annet output enn en newline!
Det som skjer når man oppretter objektet i linje 31 er at konstruktøren i linje 17 kalles. Men før den kan eksekvere innholdet i linje 19 kaller den implisit konstruktøren til baseklassen – linje 3. Denne konstruktøren kaller SomeFunction, men siden dette er et objekt av type SomeDerivedClass vil den ikke kalle SomeFunction i SomeClass, den vil kalle SomeFunction i SomeDerivedClass. Problemet er at den metoden bruker en instansvariabel som settes i konstruktøren, men linje 19 har ikke blitt kjørt enda. this.msg er derfor null i det øyeblikket den skrives ut.
Om msg hadde hatt en "default-verdi", ved at linje 15 for eksempel så ut som nedenfor, ville det vært den verdien som ble output.
15 private readonly string msg = "Set by initializer";
Denne oppførselen gir logisk sett mening, men er veldig skummel fordi de som sub-klasser SomeClass ikke forventer at det skal fungere slik. I instans-metoder forventer man at konstruktøren har blitt kjørt, slik at man kan benytte seg av tilstand som settes der.
Man bør derfor aldri kalle virtuelle (inkludert abstrakte) metoder i konstruktører, fordi dette fører til en eksekveringsrekkefølge det er vanskelig å holde oversikt over. Don't go down the rabbit hole! Generelt sett bør konstruktører være reservert til å sette den initielle tilstanden til objektet, som normalt sett betyr å lagre referanser til kontruktørens argumenter. Konstruktører skal ikke ha oppførsel.
Bruker du ting som FxCop eller Code Analysis i Visual Studio vil du også bli advart om slike situasjoner. Ta advarselen seriøst!