Перейти к содержимому
Теория · Значимые и ссылочные типы · урок 4 из 11

Объект в параметре

В прошлой теме метод Repaint перекрасил дом, который ему передали: параметр получил копию ссылки. Это как прислать коллеге ссылку на облачный документ: править документ по ней можно, а заменить ссылку, которая лежит у вас, нельзя.

Метод меняет объект

Вспомним это на методе, который пополняет банковский счёт:

C#
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;
}

Давайте разберём пример по строкам:

  1. Deposit(acc) копирует в параметр a ссылку из acc. Объект по-прежнему один.
  2. a.Balance = a.Balance + 50 идёт по ссылке и меняет поле этого общего объекта.
  3. acc.Balance после вызова печатает 150: метод и вызывающий код работали с одним счётом.

Новый объект в параметре

Теперь метод пытается «обнулить» счёт, положив в параметр новый объект:

C#
Account mine = new Account();
mine.Balance = 100;
Reset(mine);
Console.WriteLine(mine.Balance); // 100

void Reset(Account a)
{
    a = new Account();
}

Разберём, что происходит:

  1. Reset(mine) копирует в a ссылку из mine.
  2. a = new Account(); кладёт в a ссылку на новый объект. Меняется только копия ссылки, а в mine лежит прежняя.
  3. Метод завершается, a исчезает, а новый объект остаётся без единой ссылки.
  4. mine.Balance печатает 100: старый счёт никто не трогал.

Метод получил свою ссылку и переписал её на другой документ. Ваша ссылка от этого не изменилась.

Здесь аналогия неточна. Коллега может хранить пересланную ссылку сколько угодно, а параметр живёт, только пока работает метод. Когда метод завершается, его копия ссылки исчезает. Поэтому новый счёт из примера и остался без единой ссылки.

Параметр с ref

ref передаёт в метод саму переменную, а не копию её содержимого. Это работает и для класса, и для структуры — вот отдельная программа с обоими случаями:

C#
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 забыли:

C#
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# передаёт аргументы по значению, а у класса значение — это ссылка.