Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

Modyfikator ref dla typów referencyjnych i odrobina SOSu

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.

Dodaj komentarz

Loading