Przyjrzyjmy się następującemu fragmentowi kodu:
public class SampleClass { private int _value = 10; }
W rzeczywistości zostanie wygenerowany konstruktor, ustawiający pole _value na 10. Kod IL:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldc.i4.s 10 L_0003: stfld int32 SampleClass::_value L_0008: ldarg.0 L_0009: call instance void [mscorlib]System.Object::.ctor() L_000e: nop L_000f: ret }
Na razie wszystko wygląda świetnie… Mamy konstruktor który ustawia zmienną na daną wartość a sami nie musimy pisać zbyt dużo kodu (inicjalizacja inline). Z kodu wynika, że najpierw wartość ustawiona jest na 10 a potem dopiero jest wywołany konstruktor klasy bazowej.
Po jakimś czasie jednak okazało się, że należy dodać kilka innych pól oraz dodatkowe konstruktory tzn.:
public class SampleClass { private int _value1 = 10; private int _value2 = 10; private int _value3 = 10; private int _value4 = 10; public SampleClass() { } public SampleClass(int arg1) { } public SampleClass(int arg1, int arg2) { } public SampleClass(int arg1, int arg2, int arg3) { } }
Kod IL jednak już nie wygląda tak prosto:
// Konstruktor bezparametrowy .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldc.i4.s 10 L_0003: stfld int32 SampleClass::_value1 L_0008: ldarg.0 L_0009: ldc.i4.s 10 L_000b: stfld int32 SampleClass::_value2 L_0010: ldarg.0 L_0011: ldc.i4.s 10 L_0013: stfld int32 SampleClass::_value3 L_0018: ldarg.0 L_0019: ldc.i4.s 10 L_001b: stfld int32 SampleClass::_value4 L_0020: ldarg.0 L_0021: call instance void [mscorlib]System.Object::.ctor() L_0026: nop L_0027: nop L_0028: nop L_0029: ret } // ctor 2 .method public hidebysig specialname rtspecialname instance void .ctor(int32 arg1) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldc.i4.s 10 L_0003: stfld int32 SampleClass::_value1 L_0008: ldarg.0 L_0009: ldc.i4.s 10 L_000b: stfld int32 SampleClass::_value2 L_0010: ldarg.0 L_0011: ldc.i4.s 10 L_0013: stfld int32 SampleClass::_value3 L_0018: ldarg.0 L_0019: ldc.i4.s 10 L_001b: stfld int32 SampleClass::_value4 L_0020: ldarg.0 L_0021: call instance void [mscorlib]System.Object::.ctor() L_0026: nop L_0027: nop L_0028: nop L_0029: ret }
Niestety nie została dokonana żadna optymalizacja. Dla każdego konstruktora dodano inicjalizacje pól. Zamiast w jednym konstruktorze inicjalizować pola a potem wywoływać go z pozostałych, kod jest duplikowany w każdym z nich. Przy wielu polach i konstruktorach nie jest to optymalne rozwiązanie. Każdy wygenerowany konstruktor składa się z 3 części: inicjalizacja pól, wywołanie konstruktora bazowego a na końcu wykonanie ciała właściwego konstruktora.
Warto się zatem zastanowić nad następującym rozwiązaniem problemu:
public class SampleClass { private int _value1; private int _value2; private int _value3; private int _value4; public SampleClass() { _value1 = 10; _value2 = 10; _value3 = 10; _value4 = 10; } public SampleClass(int arg1):this() { } public SampleClass(int arg1, int arg2):this() { } public SampleClass(int arg1, int arg2, int arg3):this() { } }
Oczywiście nie są to jakieś rzeczywiste problemy, które spowolnią aplikację. Traktować to należy jako ciekawostkę ale uważam, że lepiej znać takie anomalia i pisać zawsze lepszy kod.
Osobiście jestem zdania, że inicjalizacja powinna nastąpić w konstruktorze (lub osobnej metodzie, która to zrobi). Zwłaszcza, jeśli klasa z jakiegoś powodu jest duuuża.
Jeśli ktoś już bardzo chce inicjalizację inline jego sprawa. Ale przestrzegam przed konstrukcjami, gdzie jedno pole jest inicjalizowane wartością innego pola, które jest inicjalizowane trzecią metodą itd. Stracisz panowanie nad tym, które pole kiedy się policzy najpierw, co będzie powodować błędy trudne do wychwycenia. Odradzam, bo widziałem takie coś i w produkcji…