← Назад к вопросам

Как хранятся в памяти ссылочные типы данных в C#?

1.0 Junior🔥 111 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как хранятся ссылочные типы данных в памяти в C#

Хотя я основной специалист в Data Science на Python, понимание управления памятью в C# важно для интеграции ML моделей в .NET приложения и для написания эффективного кода в целом.

Основные концепции

В C# есть два типа данных:

  • Value types (значимые): хранятся в Stack
  • Reference types (ссылочные): хранятся в Heap

Stack и Heap

Stack (стек):

  • Быстрый доступ
  • Автоматическая очистка (когда функция заканчивается)
  • Ограниченный размер
  • Хранит значимые типы и ссылки

Heap (куча):

  • Медленнее, чем Stack
  • Требует сборки мусора (Garbage Collection)
  • Большой размер
  • Хранит сами объекты

Схема хранения ссылочного типа

# Пример на C# (концептуально)
public class Person
{
    public string Name { get; set; }  // Ссылочный тип
    public int Age { get; set; }       // Значимый тип
}

var person = new Person { Name = "John", Age = 30 };

В памяти это выглядит так:

STACK                                    HEAP
┌─────────────────────────────┐    ┌──────────────────────────────┐
│ person (ссылка)   │ 0x1000  │───→│ Person Object                │
│                   │         │    │                              │
│ (указывает на     │         │    │ Name (ссылка)  │ 0x2000    │
│  объект в Heap)   │         │    │            ┌───────────────┼──→ "John"
│                   │         │    │            │               │
│                   │         │    │ Age        │ 30            │
│                   │         │    │                              │
└─────────────────────────────┘    └──────────────────────────────┘

Переменная person хранит АДРЕС объекта (0x1000), не сам объект!

Ссылочные типы в C#

Classes (классы):

public class Animal
{
    public string Name { get; set; }
    public void Speak() { Console.WriteLine("Sound"); }
}

var dog = new Animal { Name = "Rex" };
// dog хранит ссылку на объект Animal в Heap
// Если dog передать другой функции, обе переменные указывают на ОДИН объект

Interfaces, Delegates, Strings (особый случай):

// Строки — тоже ссылочные типы
string name = "John";  // Ссылка на объект String в Heap

// Но строки IMMUTABLE (неизменяемы)
string newName = name.ToUpper();  // Создаёт новый объект в Heap

Arrays (массивы):

int[] numbers = new int[] { 1, 2, 3 };  // Ссылка на массив в Heap
// Массив сам хранится в Heap

Копирование ссылочных типов

ВАЖНО: Копируется только ссылка, не объект!

var person1 = new Person { Name = "John" };
var person2 = person1;  // Копируется только ссылка!

person2.Name = "Jane";

Console.WriteLine(person1.Name);  // "Jane" ← Изменилось!
Console.WriteLine(person2.Name);  // "Jane"

// Обе переменные указывают на ОДИН объект в Heap

// Визуально:
// person1 ──┐
//           ├──→ Person { Name = "Jane" }
// person2 ──┘

Deep Copy vs Shallow Copy

Shallow Copy (по умолчанию):

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }  // Вложенный ссылочный тип
}

public class Address
{
    public string City { get; set; }
}

var person1 = new Person
{
    Name = "John",
    Address = new Address { City = "NYC" }
};

var person2 = person1;  // Shallow copy
person2.Address.City = "LA";

Console.WriteLine(person1.Address.City);  // "LA" ← Изменилось в обоих!

// Визуально:
// person1.Address ──┐
//                   ├──→ Address { City = "LA" }
// person2.Address ──┘
// Обе ссылки указывают на ОДИН объект Address

Deep Copy (глубокое копирование):

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
    
    // Метод для глубокого копирования
    public Person DeepCopy()
    {
        return new Person
        {
            Name = this.Name,  // String создаст новый объект
            Address = new Address { City = this.Address.City }
            // Создаём новый Address объект
        };
    }
}

var person1 = new Person
{
    Name = "John",
    Address = new Address { City = "NYC" }
};

var person2 = person1.DeepCopy();  // Глубокое копирование
person2.Address.City = "LA";

Console.WriteLine(person1.Address.City);  // "NYC" ← Не изменилось!
Console.WriteLine(person2.Address.City);  // "LA"

// Визуально:
// person1.Address ──→ Address { City = "NYC" }
// person2.Address ──→ Address { City = "LA" }
// Разные объекты!

Null ссылки

Person person = null;  // Ссылка указывает на ничего

// В Stack хранится нулевой указатель
// В памяти:
STACK
┌─────────────┐
│ person      │ null (0x0000)
└─────────────┘

// Попытка обращения вызывает NullReferenceException
person.Name = "John";  // ОШИБКА!

Garbage Collection (сборка мусора)

Когда на объект в Heap никто не ссылается, сборщик мусора удаляет его:

var person1 = new Person { Name = "John" };
person1 = null;  // Больше нет ссылок на объект

// GC автоматически удалит объект Person из Heap
// Память освобождается

// Визуально до:
// HEAP: Person { Name = "John" }  ← занимает память

// Визуально после GC:
// HEAP: [пусто]  ← память освобождена

Сравнение ссылок

var person1 = new Person { Name = "John" };
var person2 = new Person { Name = "John" };
var person3 = person1;

person1 == person2  // False (разные объекты, разные ссылки)
person1.Equals(person2)  // False (по умолчанию сравнивает ссылки)

person1 == person3  // True (одна ссылка, один объект)
person1.Equals(person3)  // True

// Но можно переопределить Equals для сравнения содержимого:
public override bool Equals(object obj)
{
    if (obj is Person other)
        return this.Name == other.Name;
    return false;
}

person1.Equals(person2)  // True (теперь сравниваем Name)

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

По ссылке (по умолчанию):

public void ModifyPerson(Person person)
{
    person.Name = "Jane";  // Изменяет оригинальный объект
}

var person = new Person { Name = "John" };
ModifyPerson(person);

Console.WriteLine(person.Name);  // "Jane" ← Изменилось!
// Передаётся ссылка, не копия объекта

По значению (с ref/out):

public void ReplacePerson(ref Person person)
{
    person = new Person { Name = "Bob" };  // Заменяет саму ссылку
}

var person = new Person { Name = "John" };
ReplacePerson(ref person);

Console.WriteLine(person.Name);  // "Bob" ← Переменная указывает на другой объект

Практические следствия

1. Производительность: ссылочные типы быстрее, чем копирование

// ✅ Быстро
var data = new LargeDataSet { /* много данных */ };
ProcessData(data);  // Передаётся только ссылка

// ❌ Медленно
var data = new LargeStruct { /* много данных */ };  // Struct
ProcessData(data);  // Копируется весь объект!

2. Изменяемость: будьте осторожны с побочными эффектами

public class MutablePerson
{
    public string Name { get; set; }
}

var original = new MutablePerson { Name = "John" };
var reference = original;
reference.Name = "Jane";  // Неожиданно изменил original!

// Решение: клонирование или использование record (неизменяемых типов)
public record Person(string Name);
var person1 = new Person("John");
var person2 = person1 with { Name = "Jane" };  // Новый объект

Современный подход: Records (C# 9+)

// Records — неизменяемые ссылочные типы
public record Person(string Name, int Age);

var person1 = new Person("John", 30);
var person2 = person1 with { Name = "Jane" };  // Новая копия

// Сравнение работает по значению
var person3 = new Person("John", 30);
person1 == person3  // True! (сравниваются значения, не ссылки)

// Это решает множество проблем с ссылочными типами

Итого: В C# ссылочные типы хранятся в Heap, а переменные содержат адреса объектов. Копирование переменной копирует только ссылку, не сам объект. Это быстро, но требует внимательности с побочными эффектами и используемой памятью. Garbage Collector автоматически освобождает память неиспользуемых объектов.

Как хранятся в памяти ссылочные типы данных в C#? | PrepBro