Inicjalizacja właściwości obiektu

W C# 3.0 wprowadzono nowy typ inicjalizacji pól klas, przykład:

class SampleClass
{
    public int X { get; set; }
    public int Y { get; set; }
}
SampleClass sampleClass = null;
try
{
 sampleClass = new SampleClass {X = 5, Y = 10};
}
catch (Exception)
{
}
int y = sampleClass.Y;

Przed c# 3.0 programiści zwykle używali następującej konstrukcji:

SampleClass sampleClass = null;
try
{
 sampleClass=new SampleClass();
 sampleClass.X = 5;
 sampleClass.Y = 10;
}
catch (Exception)
{
}
int y = sampleClass.Y;

Czy obydwie konstrukcje pełnią taką samą rolę? W końcu zarówno pierwszy jak i drugi sposób służy do inicjalizacji pól. Jeśli zajrzymy do zdekompilowanewgo kodu (np. za pomocą .NET Reflector) to zobaczymy, że w pewnych przypadkach konstrukcje znaczącą będą różnić się  zachowaniem. Przyjrzyjmy się najpierw klasycznemu podejściu (druga konstrukcja, kod po dekompilacji):

// kod po dekompilacji, zwykle przypisanie wlasciwosci
SampleClass sampleClass;
int y;
sampleClass = null;
Label_0018:
try
{
   sampleClass = new SampleClass();
   sampleClass.X = 5;
   sampleClass.Y = 10;
   goto Label_0038;
}
catch (Exception)
{
Label_0033:
   goto Label_0038;
}
Label_0038:
y = sampleClass.Y;

Nie ma chyba tutaj zaskoczenia. Wywołujemy konstruktor a następnie ustawiamy właściwości X oraz Y (za pomocą wygenerowanych setterów). Sprawdźmy co zostanie wygenerowane jeśli użyjemy inicjalizatora obiektu (pierwsza konstrukcja):

// kod po dekompilacji, inicjalizator obiektow
    SampleClass sampleClass;
    SampleClass <>g__initLocal0;
    int y;
    sampleClass = null;
Label_0018:
    try
    {
        <>g__initLocal0 = new SampleClass();
        <>g__initLocal0.X = 5;
        <>g__initLocal0.Y = 10;
        sampleClass = <>g__initLocal0;
        goto Label_003A;
    }
    catch (Exception)
    {
    Label_0035:
        goto Label_003A;
    }
Label_003A:
    y = sampleClass.Y;

Jak widać po dekompilacji zwykłe przypisania wygenerowały inny kod niż inicjalizator obiektu. Jaki to ma skutek w praktyce? Przypuśćmy, że przypisanie wartości do Y, spowoduje wyjątek. W przypadku inicjalizatora obiektu sampleClass będzie miał wartość NULL ponieważ nigdy nie dojdzie do przypisania <>g_initLocal0!

Dla konstrukcji ze zwykłym przypisaniem właściwości, sampleClass będzie różny od NULL, a właściwość X będzie zainicjalizowana (Y będzie pusty, ponieważ wywołał wyjątek).

Decydując się na któreś z rozwiązań warto zwrócić na to uwagę, ponieważ wyjątki typu NULL Reference, są zwykle trudne w znalezieniu. Aby się przekonać, ,że sampleClass naprawdę będzie NULL’em  wystarczy zasymulować wyrzucenie wyjątku:

class SampleClass
{
   public int X { get; set; }
   
   public int Y
   {
       get { return 0; }
       set{throw new NotImplementedException();}
   }
}

Leave a Reply

Your email address will not be published.