Методы и параметры: ref, out, in, params, именованные аргументы
↑ 1.3 C♯: основы языка · ← Предыдущая · → Следующая
Зачем это на собесе
модификаторы параметров
ref,out,inи передача ссылочных типов по значению — классическая ловушка на собесе: «изменится ли объект или ссылка вызывающего».
Объяснение
Передача по значению (по умолчанию)
В метод передаётся копия переменной:
- value type — копия данных;
- reference type — копия ссылки (метод может изменить объект, но присвоение параметру новой ссылки не влияет на вызывающего).
void Rename(Patient p) { p.Name = "Новое"; } // вызывающий УВИДИТ изменение объекта
void Replace(Patient p) { p = new Patient(); } // вызывающий НЕ увидит: заменили копию ссылки
void Inc(int x) { x++; } // вызывающий НЕ увидитref — передача по ссылке
Метод работает с самой переменной вызывающего. Переменная должна быть инициализирована.
void Replace(ref Patient p) { p = new Patient { Name = "Новый" }; } // вызывающий увидит новый объект
void Inc(ref int x) { x++; }
int n = 1; Inc(ref n); // n == 2out — выходной параметр
Метод обязан присвоить значение. Переменная может быть не инициализирована. Паттерн Try*:
if (int.TryParse(text, out var number)) Use(number);
if (_cache.TryGetValue(id, out var patient)) return patient;
bool TryFindPatient(Guid id, [NotNullWhen(true)] out Patient? patient) { ... }in — только для чтения по ссылке
Передача без копирования, но изменять нельзя. Полезно для больших struct:
double Distance(in Vector3 a, in Vector3 b) => (a - b).Length();Для readonly struct защитных копий не будет. Для обычной struct вызов метода внутри in может создать скрытую копию.
ref readonly и ref-возвраты (продвинуто)
ref int FindSlot(int[] arr, int value) { ... return ref arr[i]; } // вернуть ссылку на элемент
ref var slot = ref FindSlot(data, 5); slot = 10; // изменили элемент массиваparams
Переменное число аргументов:
void Log(string template, params object[] args) { ... }
Log("{0} {1}", a, b);
// C# 13: params с коллекциями и Span без аллокации массива
void Sum(params ReadOnlySpan<int> values) { ... }Именованные и необязательные аргументы
Task<Report> BuildAsync(Guid patientId, string format = "pdf", bool includeHistory = false, CancellationToken ct = default);
await BuildAsync(id, includeHistory: true, ct: token); // пропускаем format, читаемоЗначения по умолчанию вшиваются в вызывающий код (как const) — меняя их в публичной библиотеке, пересобирайте потребителей.
Локальные функции и expression-bodied
decimal Total(IEnumerable<Item> items)
{
return items.Sum(Price);
static decimal Price(Item i) => i.Price * i.Qty; // static — не захватывает внешние переменные
}Нюансы и подводные камни
ref,outиinнельзя использовать вasync-методах и итераторах (yield).- Злоупотребление
outусложняет API: для нескольких результатов лучше кортеж или record. - Много необязательных bool-параметров — признак, что нужен объект настроек (options-объект).
- Изменение объекта, переданного в метод, — побочный эффект, неочевидный для вызывающего. Документируйте или избегайте.
Тестирование
[Fact]
public void Reassigning_parameter_does_not_affect_caller()
{
var p = new Patient { Name = "A" };
Replace(p); // без ref
p.Name.Should().Be("A");
}Вопросы с ответами
Если передать объект класса в метод и изменить его свойство, увидит ли вызывающий изменение? А если присвоить параметру новый объект?
Изменение свойства увидит: обе ссылки указывают на один объект. Присвоение нового объекта не увидит, потому что меняется копия ссылки. Чтобы заменить объект у вызывающего, нужен ref.
Чем ref отличается от out?
ref требует инициализированную переменную, метод может читать и менять её. out может быть не инициализирован до вызова, но метод обязан присвоить значение до выхода.
Зачем модификатор in?
Передать большую структуру по ссылке без копирования и с гарантией, что метод её не изменит.
В чём опасность значений по умолчанию у параметров в публичных библиотеках?
Значения вшиваются в вызывающий код при компиляции. Изменение в библиотеке не подхватится без пересборки потребителей.
Связанные темы
- Предыдущая: Nullable: значимые типы и nullable reference types · Следующая: Исключения: try, catch, finally, when, свои исключения
- Value и reference типы: Типы: value и reference, struct и class, CTS
- Span и ref struct: Span и Memory: stackalloc, ref struct
- Делегаты и лямбды: Делегаты, Func, Action, лямбды, замыкания
- Передача объектов в JS: Объекты: копирование, сравнение, дескрипторы