2009-05-27

Konstrukcja obiektów w C#, a ich widzialność

Pytanie brzmi czy w tworząc obiekt w jednym wątku możemy go ujrzeć w stanie częściowo zainicjalizowanym przez konstruktor z poziomu innego wątku. Dla programu jednowątkowego zobaczenie obiektu w stanie częściowo zainicjalizowanym mogło by mieć miejsce gdyby w trakcie jego konstrukcji pojawił się wyjątek. Rozpatrzmy taki przykład:
public partial class Form1 : Form
{
    public TestValue m_v;

    public class TestValue
    {
        protected int x = 5;
        public int y = 6;
        public int z;
        public readonly int p = 4;

        public TestValue(int _y, int _z)
        {
            y = _y;
            z = _z;
            X = 4;
            p = 5;
        }

        public TestValue(int _y, int _z, TestValue _v)
        {
            _v = this;
            y = _y;
            z = _z;
            X = 4;
        }

        public TestValue()
        {
            X = 5;
        }

        public int X 
        {
            get
            {
                return x;
            }

            set
            {
                x = value;
            }
        }
    }

    public Form1()
    {
        InitializeComponent();
        Test1();
        Test2();
        Test3();
        Test4();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Close();
    }

    private void Test1()
    {
        m_v = new TestValue(3, 4);
    }

    private void Test2()
    {
        m_v = new TestValue();
    }

    private void Test3()
    {
        m_v = new TestValue() { X = 4, y = 6 };
    }

    private void Test4()
    {
        m_v = new TestValue(3, 4) { X = 5 };
    }
}
Tutaj możemy dopowiedzieć, że przyspieszony sposób konstrukcji obiektów, zastosowany np. w Test3() nie pozwala na inicjalizację zmiennych nie widocznych na zewnątrz klasy, a także tylko do odczytu. Dopuszczenie takiej możliwości naruszało by zasady enkapsulacji kodu. Zapis typu m_v = new TestValue() { X = 4, y = 6 }; nie tworzy nowego konstruktora. To wywołanie konstruktora plus uzupełnienie pół wartościami. Jak to wygląda z poziomu IL:
.method private hidebysig instance void Test1() cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldc.i4.3 
    L_0003: ldc.i4.4 
    L_0004: newobj instance void TestApp.Form1/TestValue::.ctor(int32, int32)
    L_0009: stfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_000e: ret 
}

.method private hidebysig instance void Test2() cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: newobj instance void TestApp.Form1/TestValue::.ctor()
    L_0007: stfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_000c: ret 
}

.method private hidebysig instance void Test3() cil managed
{
    .maxstack 3
    .locals init (
        [0] class TestApp.Form1/TestValue <>g__initLocal0)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: newobj instance void TestApp.Form1/TestValue::.ctor()
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: ldc.i4.4 
    L_000a: callvirt instance void TestApp.Form1/TestValue::set_X(int32)
    L_000f: nop 
    L_0010: ldloc.0 
    L_0011: ldc.i4.6 
    L_0012: stfld int32 TestApp.Form1/TestValue::y
    L_0017: ldloc.0 
    L_0018: stfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_001d: ret 
}

.method private hidebysig instance void Test4() cil managed
{
    .maxstack 4
    .locals init (
        [0] class TestApp.Form1/TestValue <>g__initLocal1)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldc.i4.3 
    L_0003: ldc.i4.4 
    L_0004: newobj instance void TestApp.Form1/TestValue::.ctor(int32, int32)
    L_0009: stloc.0 
    L_000a: ldloc.0 
    L_000b: ldc.i4.5 
    L_000c: callvirt instance void TestApp.Form1/TestValue::set_X(int32)
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: stfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_0018: ret 
}

.method private hidebysig instance void Test5() cil managed
{
    .maxstack 5
    .locals init (
        [0] class TestApp.Form1/TestValue <>g__initLocal2)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldc.i4.3 
    L_0003: ldc.i4.4 
    L_0004: ldarg.0 
    L_0005: ldfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_000a: newobj instance void TestApp.Form1/TestValue::.ctor(int32, int32, class TestApp.Form1/TestValue)
    L_000f: stloc.0 
    L_0010: ldloc.0 
    L_0011: ldc.i4.5 
    L_0012: callvirt instance void TestApp.Form1/TestValue::set_X(int32)
    L_0017: nop 
    L_0018: ldloc.0 
    L_0019: stfld class TestApp.Form1/TestValue TestApp.Form1::m_v
    L_001e: ret 
}
Najważniejszy wniosek jaki z kodu IL płynie, to taki, że referencja do obiektu jest ustawiana po pełnej konstrukcji obiektu. Dotyczy to zwłaszcza uproszczonej inicjalizacji obiektu, przy której najpierw wywoływany jest konstruktor, później ustawiane są pola obiektu, a na samym końcu ustawiana jest zmienna będąca referencją do obiektu. Czyli konstrukcje takie jak w metodzie Test3() zapewniają nam atomowość konstrukcji obiektu, zarówno jeśli chodzi o możliwość pojawienia się wyjątków w trakcie konstrukcji obiektów, jak i ich widoczności z innych wątków.

Brak komentarzy:

Prześlij komentarz