JavaRush /Курси /C# SELF /Модифікатори параметрів ( ref...

Модифікатори параметрів ( ref, out, in)

C# SELF
Рівень 12 , Лекція 4
Відкрита

1. Вступ

У C# за замовчуванням усі параметри методів передаються за значенням, тобто в метод потрапляє копія, а не оригінал.

А якщо завдання — змінити зовнішню змінну зсередини методу? Або повернути з методу одразу кілька значень? Або, можливо, вам потрібно передати в метод велику структуру, та не хочеться копіювати всю цю «валізу без ручки», а достатньо передати посилання на неї, щоб заощадити памʼять?

Усе це — про модифікатори параметрів. Вони дають вам контроль над способом передавання даних між кодом, що викликає метод, і самим методом.

Види модифікаторів

Модифікатор Передача даних Треба ініціалізувати до виклику? Можна читати всередині? Можна писати всередині? Типові сценарії використання
ref В обидва боки Так Так Так Передавання змінної для читання і/або зміни
out Тільки назовні Ні Ні (до присвоєння) Так (обовʼязково!) Повернення кількох значень з методу
in Тільки всередину Так Так Ні Передавання великих структур для «тільки читання» з економією

Не хвилюйтеся, зараз ми розглянемо кожен із них. Це простіше, ніж здається.

2. Модифікатор ref: передаємо за посиланням — і читаємо, і пишемо

Уявіть: ви приносите другові чашку кави — з нею можна і випити, і підігріти, і навіть щось додати. Друг із цією кавою може робити все, що завгодно, і те, що залишиться (або не залишиться), повернеться вам. Так працює ref.


void DoSomething(ref int x)
{
    x = x + 10; // Змінюємо вхідний параметр
}
        
Метод із параметром ref

Щоб передати параметр як ref, потрібно оголосити його з цим модифікатором і вказати модифікатор під час виклику методу:

int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // виведе 15

Це обовʼязково має бути змінна; передати за посиланням вираз не можна. Такий код працювати не буде:

DoSomething(ref 10);		// тут має бути змінна!
  • Перед викликом: змінна має бути ініціалізована (значення має бути присвоєно).
  • Усередині методу: можна читати і змінювати значення.
  • Після виклику: зміни зберігаються.

Приклад: міняємо значення змінних місцями

Іноді потрібно поміняти місцями значення двох змінних. Але є нюанс: у C# значимі типи (наприклад, int) за замовчуванням передаються за значенням. Це означає, що під час передавання їх у метод копіюються самі значення, а не посилання на змінні. Щоб метод міг змінити самі змінні коду, що викликає, треба використовувати ключове слово ref.

Приклад без ref — не працює:

// Спроба поміняти значення — не спрацює
void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(x, y); // Передаються копії значень
Console.WriteLine($"{x}, {y}"); // → 10, 20 — нічого не змінилося!

Усередині методу Swap змінні a і b — це копії x і y. Ми змінюємо тільки ці копії, а оригінали залишаються недоторканними.

Приклад роботи ref — значення міняються місцями


// Міняємо значення змінних з використанням 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); // Передаються посилання на змінні
Console.WriteLine($"{x}, {y}"); // → 20, 10 — усе правильно помінялося
        

ref дає змогу передавати посилання на самі змінні, а не їхні значення.
Тому метод Swap працює напряму з x і y, змінюючи їхній вміст.

Коли використовувати ref?

  • Потрібно змінити передану змінну (наприклад, збільшити її або замінити).
  • Немає потреби повертати нове значення (як із return), бо хочете змінити аргумент напряму.
  • Не використовуйте для «вивантаження» багатьох різних значень — для цього краще підійде інший модифікатор.

3. Модифікатор out: передаємо назовні

А тепер уявіть: ви прийшли до друга з порожньою чашкою і просите: «Наповни чим хочеш, хоч кавою, хоч чаєм». Після цього ваш друг зобовʼязаний щось у неї налити, інакше компілятор повідомить про помилку.

out призначений для того, щоб метод міг повернути кілька різних значень, оминаючи значення, що повертається. Такі параметри обовʼязково мають бути ініціалізовані всередині методу.


void GetCoordinates(out int x, out int y)
{
    x = 5;
    y = 10; // Без цього - помилка!
}
        
Метод із параметрами out
  • Перед викликом: змінну можна не ініціалізувати; можна навіть написати просто out int x у виклику.
  • Усередині методу: потрібно обовʼязково присвоїти значення до виходу з методу, інакше компілятор повідомить про помилку.
  • Після виклику: змінна міститиме нове значення.

Приклад: повертаємо одразу два значення з методу

void ParseNameAndAge(string input, out string name, out int age)
{
    string[] parts = input.Split(',');	// перетворюємо рядок на масив рядків: ділимо на частини за ','
    name = parts[0];
    age = int.Parse(parts[1]);
}

string userInput = "Іван,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} років");

Коли використовувати out?

  • Коли потрібно повернути з методу кілька значень (наприклад, розібрати рядок на частини, як вище).
  • Коли тип значення, що повертається, наперед не визначений (наприклад, спроба перетворення рядка на число).

Приклади з .NET: Метод int.TryParse(string, out int) — класика!

string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
    Console.WriteLine($"Перетворено: {parsedNumber}");
}
else
{
    Console.WriteLine("Помилка перетворення!");
}

Метод int.TryParse повертає true, якщо йому вдалося перетворити переданий рядок на число, і false, якщо не вдалося. Саме числове значення метод повертає через out.

Типові помилки й кумедні ситуації під час використання ref і out

  • Забули ініціалізувати змінну для ref — компілятор повідомить про помилку.
  • Не присвоїли значення для out — компілятор також повідомить про помилку.
  • Забули явно вказати модифікатор під час виклику: написали DoSomething(x) замість DoSomething(ref x).
  • Використовувати ref або out з константами чи літералами не можна! Це можуть бути тільки змінні. Лише вони мають адресу в памʼяті, і на неї можна сформувати посилання.

4. Модифікатор in: тільки для читання й тільки за посиланням

Іноді потрібно передати в метод якийсь великий і складний об’єкт. Зазвичай такі об’єкти передають за посиланням, але при цьому їх можна випадково змінити методом, який має до них повний доступ — адже ми дали йому посилання.

Теоретично можна було б зробити повну копію об’єкта й передати її. Тоді оригінал ніхто не змінить. Але якщо об’єкт дуже великий, ми даремно копіюватимемо великі обсяги даних. Значно вигідніше передати об’єкт у метод і попросити компілятор стежити за тим, щоб його ніхто не змінював.

in — модифікатор, що дає змогу передавати структури за посиланням, але тільки для читання. Це схоже на «виставку коштовного меча під склом»: подивитися можна, а от узяти до рук — на жаль, ні.


void PrintPoint(in Point pt)
{
    pt.X = 5; // Помилка! Тільки читання.
    Console.WriteLine($"Точка: {pt.X}, {pt.Y}");  // А так можна.
}
        
Метод із параметром in (тільки для читання)
  • Перед викликом: змінна має бути ініціалізована.
  • Усередині методу: можна тільки читати, змінювати не можна.
  • Економія памʼяті: структура не копіюється, а передається за посиланням.

Застосовується, наприклад, для масивів великих структур, щоб уникнути зайвих копіювань. Натомість із класами (посилальними типами) in особливого ефекту не дасть.

Приклад: передаємо велику структуру за посиланням, щоб не копіювати

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

void PrintBigData(in BigData data)
{
    data.A = 10; // не можна - тільки для читання!
    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. Питання, які часто хвилюють:

Чи можна використовувати ref/out/in з масивами та посилальними типами?
Так, можна. Але памʼятайте: самі по собі масиви — уже посилальні типи. Передаючи масив за допомогою ref, ви можете змінити саме посилання. Найчастіше це потрібно саме для структур.

У чому різниця між ref і out, якщо в обох випадках можна отримати значення?
У ref змінна має бути ініціалізована перед використанням. У out — не обовʼязково, але всередині методу вона ОБОВʼЯЗКОВО має бути присвоєна хоча б раз.

Що, якщо спробувати використати літерал (наприклад, число 5) з ref/out/in?
Компілятор одразу повідомить про помилку. Лише змінні!

Скільки параметрів можна зробити ref/out/in?
Скільки завгодно — жодних обмежень. Та не захоплюйтеся заради читабельності коду: якщо у вас їх більше двох-трьох, варто подумати про повернення кортежу, структури або класу.

1
Опитування
Класи та об'єкти, рівень 12, лекція 4
Недоступний
Класи та об'єкти
Вступ до наслідування
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ