Spójrz na poniższy kod i zastanów się jaka wartość zostanie wyświetlona na konsoli (pamiętaj, że string to typ referencyjny)?
using System;
class Program
{
static void Test(string y)
{
y = "bbb";
}
static void Main()
{
string x = "aaa";
Test(x);
Console.WriteLine(x);
}
}
Prawidłowa odpowiedź (aaa) nie jest wcale taka oczywista. Użytkownik zobaczy napis aaa, dlatego, że bez użycia modyfikatora ref, program napisany w C# przekazuje do metody kopię wartości parametru (dla typów wartościowych) lub kopię referencji (dla typów referencyjnych).
Gdy do parametru y w metodzie Test przypisywany jest nowy tekst, CLR nie modyfkuje tablicy znaków. Zamiast tego tworzy nowy string (więcej info tutaj) i przypisuje wskazanie do niego do zmiennej y. Zmienna y znajdująca się w metodzie Test jest jednak tylko kopią referencji do napisu wskazywanego przez zmienną x z metody Main. Skoro zmodyfikowana została jedynie kopia, to po wyjściu z metody, na konsole trafia pierwotny napis aaa.
By rzeczywiście zmienić tekst kryjący się pod zmienną x, użyj modyfikatora ref (musisz dodać go zarówno w deklaracji metody jak i jej wywołaniu – C# wymusza takie zachowanie by zwiększyć czytelność kodu):
using System;
class Program
{
static void Test(ref string y)
{
y = "bbb";
}
static void Main()
{
string x = "aaa";
Test(ref x);
Console.WriteLine(x);
}
}
Po takiej zmianie na konsole trafi napis bbb.
SOS
Sposób przekazywania parametrów do metody można zbadać za pomocą narzędzia SOS (Son of Strike). Posłużymy się poleceniem CLRStack -a, które wyświetli informacje o parametrach i zmiennych lokalnych na stosie kodu zarządzanego (jeśli nie wiesz jak używać SOS patrz tutaj i tutaj, jeśli dziwisz się skąd nazwa "Son of Strike" kliknij tu)...
Poniżej znajdują się rezultaty polecania CLRStack -a, wykonanego w momencie wejścia do metody Test.
Dla kodu bez modyfikatora ref:
!CLRStack -a
OS Thread Id: 0x176c (5996)
Child SP IP Call Site
0031f114 00390104 Program.Test(System.String)
PARAMETERS:
y (0x0031f114) = 0x025cb948
0031f158 003900af Program.Main()
LOCALS:
0x0031f158 = 0x025cb948
0031f3c0 656721bb [GCFrame: 0031f3c0]
Dla kodu z modyfikatorem ref:
!CLRStack -a
OS Thread Id: 0x934 (2356)
Child SP IP Call Site
001dee34 002f00f4 Program.Test(System.String ByRef)
PARAMETERS:
y (0x001dee34) = 0x001dee78
001dee78 002f00aa Program.Main()
LOCALS:
0x001dee78 = 0x027fb948
001df0ec 656721bb [GCFrame: 001df0ec]
Istotną różnicą widoczną na powyższych zrzutach jest wartość parametru y. W przypadku kodu bez modyfikatora ref jest to adres stringa aaa (0x025cb948), natomiast dla kodu z modyfikatorem ref, wartością parametru y jest adres zmiennej x z metody Main (0x001dee78), która wskazuje na string aaa.