C#JuniorTechnical

В чём различия между value types и reference types в C#?

Value types (int, struct, enum) хранятся по значению и копируются при присваивании; reference types (class, string, array) хранят ссылку на объект в куче. Это влияет на семантику копирования, null-возможность и производительность.

Value Types и Reference Types

Каждый тип в C# относится к одной из двух категорий, определяющих, где хранятся данные и как они передаются между переменными.

Ключевые отличия

  • Value types: int, double, bool, char, struct, enum, decimal, DateTime. Хранят данные непосредственно. При присваивании создаётся полная копия.
  • Reference types: class, string, object, interface, массивы, делегаты. Переменная хранит ссылку (адрес) на объект в куче. При присваивании копируется ссылка, объект остаётся один.

Семантика копирования

// Value type — независимые копии
int a = 5;
int b = a;
b = 10;
Console.WriteLine(a); // 5 — не изменился

// Reference type — общий объект
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // копируем ссылку!
list2.Add(4);
Console.WriteLine(list1.Count); // 4 — оба указывают на один объект

// Struct — копирование по значению
public struct Point { public int X; public int Y; }
var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // копия
p2.X = 99;
Console.WriteLine(p1.X); // 1 — оригинал не изменился

Передача в методы

// Value type передаётся по значению — метод получает копию
void IncrementValue(int n) => n++;
int x = 5;
IncrementValue(x);
Console.WriteLine(x); // 5 — не изменился

// ref — передаём по ссылке
void IncrementRef(ref int n) => n++;
IncrementRef(ref x);
Console.WriteLine(x); // 6

// Reference type — метод работает с тем же объектом
void AddItem(List<int> items) => items.Add(42);
var list = new List<int>();
AddItem(list);
Console.WriteLine(list.Count); // 1

// Но переприсвоение параметра не затрагивает оригинал
void Replace(List<int> items) => items = new List<int> { 0 };
Replace(list);
Console.WriteLine(list.Count); // 1 — оригинальная ссылка не изменилась

Null-возможность

// Value types не могут быть null (без Nullable)
int n = null; // ошибка компиляции

// Nullable value type
int? nullableN = null;
if (nullableN.HasValue)
    Console.WriteLine(nullableN.Value);

// Короткий синтаксис с оператором ??
int result = nullableN ?? -1;

// Reference types могут быть null
string? s = null;
Console.WriteLine(s?.Length ?? 0); // null-conditional operator

Struct vs Class — когда что выбрать

// Struct — для маленьких иммутабельных значений (координаты, цвет, деньги)
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }
    public Money(decimal amount, string currency) => (Amount, Currency) = (amount, currency);
}

// Class — для объектов с идентичностью, поведением, иерархией наследования
public class BankAccount
{
    public Guid Id { get; } = Guid.NewGuid();
    public decimal Balance { get; private set; }
    public void Deposit(decimal amount) => Balance += amount;
}

Подводные камни

  • Изменяемые (mutable) struct — неожиданное поведение при копировании: изменение локальной копии не отражается на оригинале. Делайте struct иммутабельными через readonly struct.
  • Boxing — помещение value type в переменную типа object или интерфейс создаёт объект на куче: object o = 42 — аллокация.
  • Struct в коллекциях: List<MyStruct> — каждый элемент хранится как копия; изменение через индекс (list[0].X = 1) запрещено, нужна промежуточная переменная.
  • Сравнение reference types через == по умолчанию сравнивает ссылки, а не содержимое (исключение — string, переопределяет ==).
  • Большие struct в hot path — копирование при каждой передаче дороже передачи ссылки; используйте in-параметры для readonly-доступа без копирования.
  • Наследование: struct не поддерживает наследование от других struct/class (только от интерфейсов).

Common mistakes

  • Путать value types, reference types и копирование значений с похожим механизмом из другой версии или платформы.
  • Игнорировать runtime-границы C#: lifecycle, DI scope, SQL translation, UI thread или platform API.
  • Не обсуждать null/empty/error cases и поведение под нагрузкой.

What the interviewer is testing

  • Кандидат объясняет value types, reference types и копирование значений на конкретном примере, а не только определением.
  • Указывает последствия для производительности, тестируемости и поддержки.
  • Различает документированное поведение текущего стека и устаревшие практики.

Sources

Related topics