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?
Сколько угодно, никаких лимитов! Только не увлекайтесь ради читаемости кода: если у вас их больше двух-трёх, стоит задуматься о возвращении кортежа, структуры или класса.

2
Задача
C# SELF, 12 уровень, 4 лекция
Недоступна
Использование модификатора ref
Использование модификатора ref
2
Задача
C# SELF, 12 уровень, 4 лекция
Недоступна
Использование модификатора out
Использование модификатора out
1
Опрос
Классы и объекты, 12 уровень, 4 лекция
Недоступен
Классы и объекты
Введение в наследование
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 57
27 января 2026
Скучные задачи