CodeGym /Kursy /C# SELF /Modyfikatory parametrów ( ref...

Modyfikatory parametrów ( ref, out, in)

C# SELF
Poziom 12 , Lekcja 4
Dostępny

1. Wprowadzenie

W C# domyślnie wszystkie parametry metod przekazywane są przez wartość, czyli do metody trafia kopia, a nie oryginał.

A co jeśli chcesz zmienić zewnętrzną zmienną z wnętrza metody? Albo zwrócić z metody kilka wartości naraz? Albo może musisz przekazać do środka dużą strukturę, ale nie chcesz kopiować całego tego "walizkowego potwora", tylko wystarczy ci referencja, żeby oszczędzić RAM?

To wszystko — to właśnie modyfikatory parametrów. Dają ci kontrolę nad "trasą przesyłu" danych między kodem wywołującym a metodą.

Rodzaje modyfikatorów

Modyfikator Przekaz danych Czy trzeba zainicjalizować przed wywołaniem? Czy można czytać w środku? Czy można pisać w środku? Typowe scenariusze użycia
ref W obie strony Tak Tak Tak Przekazanie zmiennej do czytania i/lub zmiany
out Tylko na zewnątrz Nie Nie (przed przypisaniem) Tak (obowiązkowo!) Zwracanie kilku wartości z metody
in Tylko do środka Tak Tak Nie Przekazanie dużych struktur do "tylko odczytu" z oszczędnością

Spokojnie, zaraz przejdziemy przez każdy z nich. To serio prostsze niż wygląda.

2. Modyfikator ref: Przekazujemy przez referencję: i czytamy, i piszemy

Wyobraź sobie: przynosisz kumplowi kubek kawy i mówisz — "Masz, możesz wypić, podgrzać, a nawet coś tam dolać". Kumpel z tą kawą może zrobić co chce, a co zostanie (albo nie zostanie) — wraca do ciebie. Tak działa ref.


void DoSomething(ref int x)
{
    x = x + 10; // Zmieniamy parametr wejściowy
}
        
Metoda z parametrem ref

Żeby przekazać parametr jako ref, trzeba go zadeklarować z tym samym modyfikatorem i przy wywołaniu metody:

int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // wypisze 15

To musi być zmienna, nie możesz przekazać wyrażenia przez referencję. Taki kod nie zadziała:

DoSomething(ref 10);		// tu musi być zmienna!
  • Przed wywołaniem: zmienna musi być zainicjalizowana (musi mieć przypisaną wartość).
  • W środku metody: można czytać i zmieniać wartość.
  • Po wywołaniu: zmiany zostają.

Przykład: Zamieniamy wartości zmiennych miejscami

Czasem trzeba zamienić miejscami wartości dwóch zmiennych. Ale jest haczyk: w C# typy wartościowe (np. int) domyślnie przekazywane są przez wartość. To znaczy, że przy przekazaniu do metody kopiowane są same wartości, a nie referencje do zmiennych. Żeby metoda mogła zmienić oryginalne zmienne wywołującego kodu, trzeba użyć słowa kluczowego ref.

Przykład bez ref — nie działa:

// Próba zamiany wartości — nie zadziała
void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(x, y); // Przekazywane są kopie wartości
Console.WriteLine($"{x}, {y}"); // → 10, 20 — nic się nie zmieniło!

W środku funkcji Swap zmienne a i b — to kopie x i y. Zmieniamy tylko te kopie, a oryginały zostają nietknięte.

Przykład z ref — zmienne zamieniają się miejscami


// Zamieniamy wartości zmiennych używając ref
void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(ref x, ref y); // Przekazywane są referencje do zmiennych
Console.WriteLine($"{x}, {y}"); // → 20, 10 — wszystko się zgadza
        

ref pozwala przekazywać referencje do samych zmiennych, a nie ich wartości.
Dlatego funkcja Swap działa bezpośrednio na x i y, zmieniając ich zawartość.

Kiedy używać ref?

  • Musisz zmienić przekazaną zmienną (np. zwiększyć ją, podmienić).
  • Nie chcesz zwracać nowej wartości (jak przez return), bo chcesz zmienić argument bezpośrednio.
  • Nie używaj do "wypluwania" wielu różnych wartości — do tego jest inny modyfikator.

3. Modyfikator out: Przekazujemy na zewnątrz

A teraz wyobraź sobie: przychodzisz do kumpla, ale masz pusty kubek i prosisz: "Napełnij kubek — czym chcesz, kawą, herbatą, co tam masz!" Po tym twój kumpel musi go czymś napełnić, bo inaczej kompilator zrobi awanturę.

out powstał po to, żeby metoda mogła zwrócić kilka różnych wartości, omijając return. Takie parametry muszą być obowiązkowo zainicjalizowane w środku metody.


void GetCoordinates(out int x, out int y)
{
    x = 5;
    y = 10; // Bez tego - błąd!
}
        
Metoda z parametrami out
  • Przed wywołaniem: zmiennej nie trzeba inicjalizować, można nawet napisać po prostu out int x w wywołaniu.
  • W środku metody: obowiązkowo przypisz wartość przed wyjściem z metody, bo inaczej kompilator zrobi strajk.
  • Po wywołaniu: zmienna ma nową wartość.

Przykład: Zwracamy dwie wartości z metody naraz

void ParseNameAndAge(string input, out string name, out int age)
{
    string[] parts = input.Split(',');	// zamieniamy string na tablicę stringów - dzielimy po ','
    name = parts[0];
    age = int.Parse(parts[1]);
}

string userInput = "Ivan,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} lat");

Kiedy używać out?

  • Kiedy musisz zwrócić z metody kilka wartości (np. rozbić string na części, jak wyżej).
  • Kiedy typ zwracanej wartości nie jest z góry znany (np. próba konwersji stringa na liczbę).

Przykłady z .NET: Metoda int.TryParse(string, out int) — klasyka!

string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
    Console.WriteLine($"Przekonwertowano: {parsedNumber}");
}
else
{
    Console.WriteLine("Błąd konwersji!");
}

Metoda int.TryParse zwraca true, jeśli udało się przekonwertować podany string na liczbę i false, jeśli nie. Samą wartość liczbową zwraca przez out.

Typowe wtopy i śmieszne sytuacje przy użyciu ref i out

  • Zapomniałeś zainicjalizować zmienną dla ref — wtedy kompilator się wkurza.
  • Nie przypisałeś wartości dla out — kompilator jeszcze bardziej się wkurza.
  • Zapomniałeś jawnie podać modyfikatora przy wywołaniu: napisałeś DoSomething(x) zamiast DoSomething(ref x).
  • Używanie ref lub out z konstami lub literałami nie działa! To mogą być tylko zmienne. Tylko one mają adres w pamięci i można do nich zrobić referencję.

4. Modyfikator in: Tylko do odczytu, i tylko przez referencję

Czasem są sytuacje, że do metody trzeba przekazać jakiś duży i skomplikowany obiekt. Zwykle taki obiekt przekazuje się przez referencję, ale wtedy może być przypadkowo zmieniony przez funkcję, która ma do niego pełny dostęp — bo daliśmy jej referencję.

Teoretycznie można by zrobić pełną kopię naszego obiektu i przekazać ją. Wtedy oryginał nikt nie zmieni. Ale jeśli obiekt jest duży, to niepotrzebnie kopiujemy dużo danych. Dużo lepiej przekazać obiekt do funkcji i poprosić kompilator, żeby pilnował, żeby nikt go nie zmienił.

in — nowy modyfikator, który pozwala przekazywać struktury przez referencję, ale tylko do odczytu. To jak "wystawa miecza pod szkłem": możesz popatrzeć, ale dotknąć — niestety nie.


void PrintPoint(in Point pt)
{
    pt.X = 5; // Błąd! Tylko do odczytu.
    Console.WriteLine($"Punkt: {pt.X}, {pt.Y}");  //To można
}
        
Metoda z parametrem in (tylko do odczytu)
  • Przed wywołaniem: zmienna musi być zainicjalizowana.
  • W środku metody: można tylko czytać, nie można zmieniać.
  • Oszczędność pamięci: struktura nie jest kopiowana, tylko przekazywana przez referencję.

Stosuje się np. dla tablic dużych struktur, żeby nie kopiować niepotrzebnie danych. Ale z klasami (typami referencyjnymi) in nie daje specjalnych korzyści.

Przykład: Przekazujemy dużą strukturę przez referencję, żeby nie kopiować

struct BigData
{
    public int A, B, C, D, E;
}

void PrintBigData(in BigData data)
{
    data.A = 10; // nie można - tylko do odczytu!
    Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}

BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);

5. Pytania, które często się pojawiają:

Czy można używać ref/out/in z tablicami i typami referencyjnymi?
Tak, można! Ale pamiętaj: same tablice to już typy referencyjne. Przekazując tablicę przez ref, możesz zmienić samą referencję. Najczęściej przydaje się to dla struktur.

Jaka jest różnica między ref a out, skoro w obu mogę dostać wartość?
W ref zmienna musi być zainicjalizowana zanim jej użyjesz. W out nie musi, ale w środku metody MUSI być przypisana przynajmniej raz.

Co jeśli spróbuję użyć literału (np. liczby 5) z ref/out/in?
Kompilator od razu wywali błąd. Tylko zmienne!

Ile parametrów mogę zrobić ref/out/in?
Ile chcesz, nie ma limitów! Ale nie przesadzaj dla czytelności kodu: jeśli masz ich więcej niż dwa-trzy, pomyśl o zwracaniu krotki, struktury albo klasy.

1
Ankieta/quiz
Klasy i obiekty, poziom 12, lekcja 4
Niedostępny
Klasy i obiekty
Wprowadzenie do dziedziczenia
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION