2009-05-29

Słowa kluczowe ref i out

Słowa kluczowe ref i out wykorzystywane są jako dodatkowe parametry parametrów metod. Przykład zastosowania:
public class TestClass
{
    public int X = 1;
}

public void Test1(ref int x, ref TestClass tc1, ref TestClass tc2)
{
    x++;
    tc1 = new TestClass() { X = 5 };
    tc2.X = 6;
}

public void Test2(out int x, out TestClass tc)
{
    x = 8;
    tc = new TestClass() { X = 6 };
}

public void Test()
{
    int x1 = 5;

    TestClass tc1 = new TestClass() { X = 5 };
    TestClass tc2 = new TestClass() { X = 5 };

    TestClass tc1c = tc1;
    TestClass tc2c = tc2;

    System.Console.WriteLine(x1); // 5
    System.Console.WriteLine(tc1.X); // 5
    System.Console.WriteLine(tc2.X); // 5

    Test1(ref x1, ref tc1, ref tc2);

    System.Console.WriteLine(x1); // 6
    System.Console.WriteLine(tc1.X); // 5
    System.Console.WriteLine(tc2.X); // 6

    System.Console.WriteLine(Object.ReferenceEquals(tc1, tc1c)); // False
    System.Console.WriteLine(Object.ReferenceEquals(tc2, tc2c)); // True

    int x2;
    TestClass tc3;

    Test2(out x2, out tc3);

    System.Console.WriteLine(x2); // 8
    System.Console.WriteLine(tc3.X); // 6
}
Czyli słowo kluczowe ref jak sama nazwa wskazuje, służy do przekazywania metodzie referencji do zmiennej. Każda zmiana zmiennej w metodzie oznaczonej jako ref powoduje zmiany w zmiennej referencyjnej, jeśli możemy ją tak nazwać. Warto tutaj zwrócić uwagę na różnicę pomiędzy referencją do zmiennej, a referencją do obiektu. Przed przekazaniem zmiennej do metody jako referencji zmienna ta musi być zainicjalizowana. Nie dotyczy to out, który jest szczególnym przypadkiem ref, tutaj możemy przekazać do metody zmienną jako referencje bez jej inicjalizacji. W drugą stronę parametr metody oznaczony przez ref może być przez tą metodę nietknięty, zaś w przypadku out inicjalizacja takiego parametru musi nastąpić (tutaj mamy duże podobieństwo do return). Czy moglibyśmy się bez tych słów kluczowych obejść ? Tak, ale wiele rzeczy byłoby wtedy bardziej skomplikowanych. Słowo kluczowe ref pozwala na szybkie przekazywanie do metod typów wartościowych (w tym struktur) bez ich kopiowania. Pozwala metodzie na modyfikowania wielu zmiennych wartościowych przekazywanych przez referencję. Normalnie można modyfikować tylko jedną: int r = 5; r = Test5(r);. No i pozwala na modyfikowanie referencji do obiektów. Słowo kluczowe out pozwala na zwracanie przez metodę wielowartościowego rezultatu. I jako efekt uboczny typy wartościowe nie podlegają kopiowaniu. Zauważmy, że wywołując metodę której parametry są oznaczone jako ref i out musimy te parametry oznaczyć także tymi samymi słowami kluczowymi. Dzięki temu wiemy jakiego zachowania po metodzie możemy się spodziewać. Dla obu słów kluczowych do metod nie możemy przekazywać właściwości obiektów. Jak to wygląda od strony IL:
public void Test1(ref int x1, ref TestClass tc1, out int x2, out TestClass tc2)
{
    x2 = 5;
    tc2 = new TestClass();
}

public void Test()
{
    int x1 = 5;
    TestClass tc1 = new TestClass();
    int x2;
    TestClass tc2;

    Test1(ref x1, ref tc1, out x2, out tc2);
}
Tutaj jedno uzupełnienie. Takie wywołanie jest niemożliwe Test1(ref 5, ref new TestClass(), out 5, out new TestClass());. Poprzez ref i out możemy tylko przekazać konkretne zmienne. Wracając do wyglądu kodu w IL:
.method public hidebysig instance void Test1(int32& x1, class TestApp.Form1/TestClass& tc1, [out] int32& x2, [out] class TestApp.Form1/TestClass& tc2) cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.3 
    L_0002: ldc.i4.5 
    L_0003: stind.i4 
    L_0004: ldarg.s tc2
    L_0006: newobj instance void TestApp.Form1/TestClass::.ctor()
    L_000b: stind.ref 
    L_000c: ret 
}

.method public hidebysig instance void Test() cil managed
{
    .maxstack 5
    .locals init (
        [0] int32 x1,
        [1] class TestApp.Form1/TestClass tc1,
        [2] int32 x2,
        [3] class TestApp.Form1/TestClass tc2)
    L_0000: nop 
    L_0001: ldc.i4.5 
    L_0002: stloc.0 
    L_0003: newobj instance void TestApp.Form1/TestClass::.ctor()
    L_0008: stloc.1 
    L_0009: ldarg.0 
    L_000a: ldloca.s x1
    L_000c: ldloca.s tc1
    L_000e: ldloca.s x2
    L_0010: ldloca.s tc2
    L_0012: call instance void TestApp.Form1::Test1(int32&, class TestApp.Form1/TestClass&, int32&, class TestApp.Form1/TestClass&)
    L_0017: nop 
    L_0018: ret 
}
Jak widzimy informacją czy dany parametr jest ref, czy out jest tam widoczna. Widać też tam wyraźnie, że out jest szczególnym przypadkiem ref. Pozostała nam jeszcze ostatnia rzecz do ustalenia co się dzieje przy przeciążaniu metod. Ponieważ przy wywołaniu metod trzeba jawnie podać ref, czy out, takie metody są w pełni rozróżnialne:
public void Test1(ref int x1)
{
}

public void Test1(int x1)
{
}

public void Test()
{
    int x = 6;
    Test1(x);
    Test1(x);
}
Ale tego typu metody uznawane są jako podwójne, pomimo tego, że jak widać w IL, można je bez problemu rozróżnić:
public void Test1(ref int x1)
{
}

public void Test1(out int x1)
{
    x1 = 4;
}

public void Test()
{
    int x = 6;
    Test1(x);
    Test1(x);
}

Brak komentarzy:

Prześlij komentarz