Skummelt med virtuelle metoder


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!

CropperCapture[43]


comments powered by Disqus