Объект в параметре
В прошлой теме метод Repaint перекрасил дом, который ему передали: параметр получил копию ссылки. Это как прислать коллеге ссылку на облачный документ: править документ по ней можно, а заменить ссылку, которая лежит у вас, нельзя.
Метод меняет объект
Вспомним это на методе, который пополняет банковский счёт:
Account acc = new Account();
acc.Balance = 100;
Deposit(acc);
Console.WriteLine(acc.Balance); // 150
void Deposit(Account a)
{
a.Balance = a.Balance + 50;
}
class Account
{
public int Balance;
}Давайте разберём пример по строкам:
Deposit(acc)копирует в параметрaссылку изacc. Объект по-прежнему один.a.Balance = a.Balance + 50идёт по ссылке и меняет поле этого общего объекта.acc.Balanceпосле вызова печатает150: метод и вызывающий код работали с одним счётом.
Новый объект в параметре
Теперь метод пытается «обнулить» счёт, положив в параметр новый объект:
Account mine = new Account();
mine.Balance = 100;
Reset(mine);
Console.WriteLine(mine.Balance); // 100
void Reset(Account a)
{
a = new Account();
}Разберём, что происходит:
Reset(mine)копирует вaссылку изmine.a = new Account();кладёт вaссылку на новый объект. Меняется только копия ссылки, а вmineлежит прежняя.- Метод завершается,
aисчезает, а новый объект остаётся без единой ссылки. mine.Balanceпечатает100: старый счёт никто не трогал.
Метод получил свою ссылку и переписал её на другой документ. Ваша ссылка от этого не изменилась.
Здесь аналогия неточна. Коллега может хранить пересланную ссылку сколько угодно, а параметр живёт, только пока работает метод. Когда метод завершается, его копия ссылки исчезает. Поэтому новый счёт из примера и остался без единой ссылки.
Параметр с ref
ref передаёт в метод саму переменную, а не копию её содержимого. Это работает и для класса, и для структуры — вот отдельная программа с обоими случаями:
Account acc = new Account();
acc.Balance = 100;
Reset(ref acc);
Console.WriteLine(acc.Balance); // 0
Point pos = new Point();
Shift(ref pos);
Console.WriteLine(pos.X); // 10
void Reset(ref Account a) => a = new Account();
void Shift(ref Point p) => p.X = p.X + 10;
class Account { public int Balance; }
struct Point { public int X; }ref для объекта
С ref параметр a — второе имя самой переменной acc. Поэтому a = new Account() кладёт ссылку на новый объект прямо в acc, и печатается 0: у нового счёта баланс нулевой.
ref для структуры
Без ref метод получил бы ксерокопию точки, как в прошлом уроке. С ref параметр p — сама переменная pos, и p.X = p.X + 10 сдвигает вашу точку: печатается 10.
По значению или по ссылке
Этот вопрос часто задают на собеседовании: «Объекты в C# передаются по ссылке?» Точный ответ — нет: по умолчанию всё передаётся по значению, а у класса значение — это ссылка. Передать аргумент по ссылке позволяют ref и out из темы о методах.
Таблица показывает, что может метод в каждом из четырёх случаев:
| Параметр | Без ref | С ref |
|---|---|---|
| структура | меняет только свою копию | меняет вашу структуру |
| класс | меняет общий объект, но не вашу переменную | может положить в вашу переменную другой объект |
Частая ошибка
Метод должен поменять местами два счёта, но ref забыли:
Account x = new Account();
x.Balance = 1;
Account y = new Account();
y.Balance = 2;
Swap(x, y); // ✗
Console.WriteLine(x.Balance); // 1
void Swap(Account first, Account second)
{
Account temp = first;
first = second;
second = temp;
}
class Account { public int Balance; }Программа компилируется и печатает 1: счета не поменялись местами. Swap получил копии ссылок и переставил их у себя, а x и y по-прежнему ведут к своим объектам. Это и есть ловушка фразы «объекты передаются по ссылке». Чтобы обмен сработал, нужен ref и в объявлении, и в вызове: Swap(ref x, ref y).
Главное
- Метод с параметром-классом получает копию ссылки и может менять общий объект.
- Присваивание параметру нового объекта не меняет переменную вызывающего кода.
refпередаёт саму переменную: метод может положить в неё другой объект или изменить вашу структуру.- По умолчанию C# передаёт аргументы по значению, а у класса значение — это ссылка.