C#: ref vs. out

W C# typy proste przekazywane są przez wartość. Oznacza to, że za każdym razem wszystkie bity są kopiowane. Ponadto jakiekolwiek operacje dokonywane na takim polu, nie są widoczne na zewnątrz, na przykład:

internal class Program
{   
    private static void Increment( int value)
    {
        value++;
    }
    private static void Main(string[] args)
    {
        int value = 5;
        Increment(value);
        Console.WriteLine(value);
    }
}

Czasami zachodzi potrzeba przekazania wyniku z powrotem albo ze względu na dużą strukturę danych, nie chcemy kopiować wszystkiego za każdym razem. Służą do tego dwa operatory: ref oraz out. Wewnętrznie (w CLR) wyglądają praktycznie tak samo. Różnica jednak jest dla kompilatora i dla programisty. W skrócie Ref:

  1. Przekazuje zmienną przez adres, a nie przez wartość .
  2. Zmienna przekazana przez REF musi być już zainicjalizowana.

Z kolei out:

  1. Tak samo jak ref, również przekazuje zmienną przez adres.
  2. Zmienna przekazana przez OUT nie musi być zainicjalizowana (aczkolwiek może).
  3. Zmienna przekazana przez OUT musi za to być zainicjalizowana w ciele metody.

Punkt pierwszy jest chyba oczywisty ale spróbujmy napisać trochę kodu, który to udowodni:

// OUT
internal class Program
{   
    private static void SetToFive(out  int value)
    {
        value = 5;
    }
    private static void Main(string[] args)
    {
        int value = 0;
        SetToFive(out value);
        Console.WriteLine(value);
    }
}
// REF
internal class Program
{   
    private static void SetToFive(ref  int value)
    {
        value = 5;
    }
    private static void Main(string[] args)
    {
        int value = 0;
        SetToFive(ref value);
        Console.WriteLine(value);
    }
}

Obie powyższe funkcje wydrukują 5 – pierwszy punkt jest zatem prawdziwy. Następnie przekonajmy się o punkcie 2. Dla ref musimy zawsze zainicjalizować wartość przed przekazaniem jej do metody. Poniższy kod nie zadziała:

internal class Program
{   
    private static void SetToFive(ref  int value)
    {
        value = 5;
    }
    private static void Main(string[] args)
    {
        int value;
        SetToFive(ref value);
        Console.WriteLine(value);
    }
}

Przed wywołaniem SetToFive, value musi mieć już jakąś wartość. Poprawna wersja to:

private static void Main(string[] args)
{
   int value = 10;
   SetToFive(ref value);
   Console.WriteLine(value);
}

Z kolei dla out, nie trzeba inicjalizować value:

internal class Program
{
    private static void SetToFive(out  int value)
    {
        value = 5;
    }
    private static void Main(string[] args)
    {
        int value ;
        SetToFive(out value);
        Console.WriteLine(value);
    }
}

Pozostał punkt trzeci. Jeśli oznaczymy parametr out, wtedy w ciele metody musimy dokonać inicjalizacji a nie tylko modyfikacji. Na przykład poniższy kod nie zadziała:

internal class Program
{
    private static void Increment(out  int value)
    {
        value = value + 1;
    }
    private static void Main(string[] args)
    {
        int value = 10;
        Increment(out value);
        Console.WriteLine(value);
    }
}

OUT przyjmuje zawsze, że parametr nie jest zainicjalizowany a praca na takim obiekcie jest oczywiście niemożliwa.W powyższy przykładzie należy zatem użyć REF, który wymaga aby wartość przekazana do funkcji była już zainicjalizowana. W przypadku OUT, to metoda odpowiada za tą operację. Nie możemy nic zrobić z parametrem OUT jeśli najpierw nie przypiszemy do niej jakieś wartości. Jeśli zatem piszemy jakąś metodę, która zwraca kilka wyników, wtedy lepiej oznaczyć je jako OUT, który wymusi na nas wypełnienie ich. Z Kolei REF lepiej nadaję się do przypadków gdy po prostu nie chcemy przekazywać przez wartość (wydajność) lub chcemy jakąś zmienną jedynie zmodyfikować.

Na zakończenie jeszcze pokaże, że można również korzystać z named parameters i powyższych słów kluczowych:

internal class Program
{
    private static void SetToFive(out  int value)
    {
        value = 5;
    }
    private static void Main(string[] args)
    {
        int x = 5;
        SetToFive(value:out x);        
    }
}

Jak widać, składnia trochę może dziwna, ale jest to jak najbardziej możliwe.

16 thoughts on “C#: ref vs. out”

  1. “1. Przekazuje wartość przez adres a nie przez wartość (tak jak out). (…) 1. Przekazuje wartość przez adres a nie przez wartość (tak jak ref).” Ja czegoś nie rozumiem, czy pomyłka?

  2. Teraz tym bardziej jest źle 🙂
    “Ref:
    1. Przekazuje zmienną przez adres a nie przez wartość (tak jak out).
    Z kolei out:
    Przekazuje zmienną przez adres a nie przez wartość (tak jak ref). ”
    Punkt w out do zmiany pozdrawiam 🙂

  3. To co pisze @Marcin Sosnicki:
    Chodzi pewnie o to, że w punkcie 1 przy out nie powinno być”Przekazuje zmienną przez adres a nie przez wartość”
    Out chyba własnie przekazuje przez wartość.

    Pozdrawiam
    Marcin

  4. Artykuł jest bardzo pomocny, ale autor nieświadomie wprowadził zamieszanie stąd ogólne niezrozumienie przez czytelników (co jest słuszne):

    W skrócie Ref:

    1. Przekazuje zmienną przez adres a nie przez wartość (tak jak out).

    Osobiście usunąłbym ten nawias, ponieważ on sugeruje, że out w przeciwieństwie do ref przekazuje zmienną przez wartość. Z kolei w przypadku out napisałbym, że:

    1. Analogicznie jak w ref, przekazuje zmienną przez adres, a nie przez wartość.

    Wydaje mi się, że w ten sposób od razu sugeruje się, że out jak i ref przekazują zmienne przez adres, a nie przez wartość. W obecnej formie przez te nawiasy ma się wrażenie, że ref i out inaczej przekazuja zmienne. Dopiero po ostatnim komentarzu autora wątpliwości zostały rozwiane.

    Pozdrawiam
    Rafał Cypcer

  5. A ja się pytam po prostu – po cholerę mi coś takiego? Nie rozumiem w c# elementów, które niby mają uprościć, a tak naprawdę komplikują sprawę… Bo można to obejść w inny sposób, który po prostu nie pozostawi żadnych wątpliwości dla każdego programisty w zespole.

  6. Też w pierwszym momencie się zdziwiłem na zdanie:
    1.Przekazuje zmienną przez adres a nie przez wartość (tak jak ref).
    Ładniej byłoby
    1.Przekazuje zmienną przez adres, a nie przez wartość (podobnie jak czyni to ref).

    No ale wiadomo o co chodzi po krótkim namyśle. 🙂

  7. @aaaa co ty gadasz za głupoty, czasami się nie da normalnie tego obejść, myślisz że globalnymi ziemnymi załatwisz sprawę, jak tak to się grubo mylisz.

    Artykuł bardzo fajny.

    Co do waszych problemów ze zrozumieniem pojęcia referencja:

    out i ref działają na tej samej zasadzie przekazanie parametru w formie odwołania się do pamięci.

    reszta to różnica kiedy ma być zdefiniowane i kiedy.

    Proste jak cholera, ciężej by było to zrozumieć w C++;

  8. @Rafał Cypcer:
    Dobra sugestia, dzieki. Wiele osob narzekalo, ze to nieczytelne wiec zmineilem dzisiaj:)

Leave a Reply

Your email address will not be published.