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