Полное руководство по языку программирования С# 14 и платформе .NET 10 / Классы, структуры и пространства имен

Типы значений и ссылочные типы

Начальный ⏱ 7 мин чтения 👁 2 🕓 обновлено 11.08.2026

Ранее мы рассматривали следующие элементарные типы данных: int, byte, double, string, object и др. Также есть сложные типы: структуры, перечисления, классы. Все эти типы данных можно разделить на типы значений, еще называемые значимыми типами, (value types) и ссылочные типы (reference types). Важно понимать между ними различия.

Типы значений:

  • Целочисленные типы (byte, sbyte, short, ushort, int, uint, long, ulong)

  • Типы с плавающей запятой (float, double)

  • Тип decimal

  • Тип bool

  • Тип char

  • Перечисления enum

  • Структуры (struct)

Ссылочные типы:

  • Тип object

  • Тип string

  • Классы (class)

  • Интерфейсы (interface)

  • Делегаты (delegate)

В чем же между ними различия? Для этого надо понять организацию памяти в .NET. Здесь память делится на два типа: стек и куча (heap). Параметры и переменные метода, которые представляют типы значений, размещают свое значение в стеке. Стек представляет собой структуру данных, которая растет снизу вверх: каждый новый добавляемый элемент помещается поверх предыдущего. Время жизни переменных таких типов ограничено их контекстом. Физически стек - это некоторая область памяти в адресном пространстве.

Когда программа только запускается на выполнение, в конце блока памяти, зарезервированного для стека устанавливается указатель стека. При помещении данных в стек указатель переустанавливается таким образом, что снова указывает на новое свободное место. При вызове каждого отдельного метода в стеке будет выделяться область памяти или фрейм стека, где будут храниться значения его параметров и переменных.

Например:


class Program
{
	static void Main(string[] args)
    {
		Calculate(5);
    }

	static void Calculate(int t)
    {
		int x = 6;
        int y = 7;
        int z = y + t;
	}
}

При запуске такой программы в стеке будут определяться два фрейма - для метода Main (так как он вызывается при запуске программы) и для метода Calculate:

При вызове этого метода Calculate в его фрейм в стеке будут помещаться значения t, x, y и z. Они определяются в контексте данного метода. Когда метод отработает, область памяти, которая выделялась под стек, впоследствии может быть использована другими методами.

Причем если параметр или переменная метода представляет тип значений, то в стеке будет храниться непосредсвенное значение этого параметра или переменной. Например, в данном случае переменные и параметр метода Calculate представляют значимый тип - тип int, поэтому в стеке будут храниться их числовые значения.

Ссылочные типы хранятся в куче или хипе, которую можно представить как неупорядоченный набор разнородных объектов. Физически это остальная часть памяти, которая доступна процессу.

При создании объекта ссылочного типа в стеке помещается ссылка на адрес в куче (хипе). Когда объект ссылочного типа перестает использоваться, в дело вступает автоматический сборщик мусора: он видит, что на объект в хипе нету больше ссылок, условно удаляет этот объект и очищает память - фактически помечает, что данный сегмент памяти может быть использован для хранения других данных.

Так, в частности, если мы изменим метод Calculate следующим образом:


static void Calculate(int t)
{
	object x = 6;
    int y = 7;
    int z = y + t;
}

То теперь значение переменной x будет храниться в куче, так как она представляет ссылочный тип object, а в стеке будет храниться ссылка на объект в куче.

Составные типы

Теперь рассмотим ситуацию, когда тип значений и ссылочный тип представляют составные типы - структуру и класс:


State state1 = new State(); // State - структура, ее данные размещены в стеке
Country country1 = new Country(); // Country - класс, в стек помещается ссылка на адрес в хипе
                                  // а в хипе располагаются все данные объекта country1
struct State
{
    public int x;
    public int y;
}
class Country
{
    public int x;
    public int y;
}

Здесь в методе Main в стеке выделяется память для объекта state1. Далее в стеке создается ссылка для объекта country1 (Country country1), а с помощью вызова конструктора с ключевым словом new выделяется место в хипе (new Country()). Ссылка в стеке для объекта country1 будет представлять адрес на место в хипе, по которому размещен данный объект..

Таким образом, в стеке окажутся все поля структуры state1 и ссылка на объект country1 в хипе.

Но, допустим, в структуре State также определена переменная ссылочного типа Country. Где она будет хранить свое значение, если она определена в типе значений?


State state1 = new State();
Country country1 = new Country();

struct State
{
    public int x;
    public int y;
    public Country country;
    public State()
    {
        x = 0;
        y = 0;
        country = new Country();
    }
}
class Country
{
    public int x;
    public int y;
}

Значение переменной state1.country - ссылка на область в куче, будет храниться в стеке, а значения ее переменных также будут храниться в куче, так как переменная state1.country представляет ссылочный тип:

Копирование значений

Тип данных надо учитывать при копировании значений. При присвоении данных объекту значимого типа он получает копию данных. При присвоении данных объекту ссылочного типа он получает не копию объекта, а ссылку на этот объект в хипе. Например:


State state1 = new State(); // Структура State
State state2 = new State();
state2.x = 1;
state2.y = 2;
state1 = state2;
state2.x = 5; // state1.x=1 по-прежнему
Console.WriteLine(state1.x); // 1
Console.WriteLine(state2.x); // 5

Country country1 = new Country(); // Класс Country
Country country2 = new Country();
country2.x = 1;
country2.y = 4;
country1 = country2;
country2.x = 7; // теперь и country1.x = 7, так как обе ссылки и country1 и country2 
                // указывают на один объект в хипе
Console.WriteLine(country1.x); // 7
Console.WriteLine(country2.x); // 7

Так как state1 - структура, то при присвоении state1 = state2 она получает копию структуры state2. А объект класса country1 при присвоении country1 = country2; получает ссылку на тот же объект, на который указывает country2. Поэтому с изменением country2, так же будет меняться и country1.

Ссылочные типы внутри типов значений

Теперь рассмотрим более изощренный пример, когда внутри структуры у нас может быть переменная ссылочного типа, например, какого-нибудь класса:


State state1 = new State();
State state2 = new State();

state2.country.x = 5;
state1 = state2;
state2.country.x = 8; // теперь и state1.country.x=8, так как state1.country и state2.country
                      // указывают на один объект в хипе
Console.WriteLine(state1.country.x); // 8
Console.WriteLine(state2.country.x); // 8

struct State
{
    public int x;
    public int y;
    public Country country;
    public State()
    {
        x = 0;
        y = 0;
        country = new Country(); // выделение памяти для объекта Country
    }
}
class Country
{
    public int x;
    public int y;
}

Переменные ссылочных типов в структурах также сохраняют в стеке ссылку на объект в хипе. И при присвоении двух структур state1 = state2; структура state1 также получит ссылку на объект country в хипе. Поэтому изменение state2.country повлечет за собой также изменение state1.country.

Объекты классов как параметры методов

Организацию объектов в памяти следует учитывать при передаче параметров по значению и по ссылке. Если параметры методов представляют объекты классов, то использование параметров имеет некоторые особенности. Например, создадим метод, который в качестве параметра принимает объект Person:


Person p = new Person { name = "Tom", age = 23 };
ChangePerson(p);

Console.WriteLine(p.name); // Alice
Console.WriteLine(p.age); // 23

void ChangePerson(Person person)
{
    // сработает
    person.name = "Alice";
    // сработает только в рамках данного метода
    person = new Person { name = "Bill", age = 45 };
    Console.WriteLine(person.name); // Bill
}

class Person
{
    public string name = "";
    public int age;
}

При передаче объекта класса по значению в метод передается копия ссылки на объект. Эта копия указывает на тот же объект, что и исходная ссылка, потому мы можем изменить отдельные поля и свойства объекта, но не можем изменить сам объект. Поэтому в примере выше сработает только строка person.name = "Alice".

А другая строка person = new Person { name = "Bill", age = 45 } создаст новый объект в памяти, и person теперь будет указывать на новый объект в памяти. Даже если после этого мы его изменим, то это никак не повлияет на ссылку p в методе Main, поскольку ссылка p все еще указывает на старый объект в памяти.

Но при передаче параметра по ссылке (с помощью ключевого слова ref) в метод в качестве аргумента передается сама ссылка на объект в памяти. Поэтому можно изменить как поля и свойства объекта, так и сам объект:


Person p = new Person { name = "Tom", age = 23 };
ChangePerson(ref p);

Console.WriteLine(p.name); // Bill
Console.WriteLine(p.age); // 45

void ChangePerson(ref Person person)
{
    // сработает
    person.name = "Alice";
    // сработает
    person = new Person { name = "Bill", age = 45 };
}

class Person
{
    public string name = "";
    public int age;
}

Операция new создаст новый объект в памяти, и теперь ссылка person (она же ссылка p из метода Main) будет указывать уже на новый объект в памяти.

Проверь себя

📋 Тест по теме «Типы значений и ссылочные типы» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

class _____
{
	static void Main(string[] args)
    {
		Calculate(5);
    }

	static void Calculate(int t)
    {
		int x = 6;
        int y = 7;
        int z = y + t;
	}
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

static void Calculate(int t)
{
	_____ x = 6;
    int y = 7;
    int z = y + t;
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

State state1 = _____ State(); // State - структура, ее данные размещены в стеке
Country country1 = new Country(); // Country - класс, в стек помещается ссылка на адрес в хипе
                                  // а в хипе располагаются все данные объекта country1
struct State
{
    public int x;
    public int y;
}
class Country
{
    public int x;
    public int y;
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

_____ state1 = new State();
Country country1 = new Country();

struct State
{
    public int x;
    public int y;
    public Country country;
    public State()
    {
        x = 0;
        y = 0;
        country = new Country();
    }
}
class Country
{
    public int x;
    public int y;
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

_____ state1 = new State(); // Структура State
State state2 = new State();
state2.x = 1;
state2.y = 2;
state1 = state2;
state2.x = 5; // state1.x=1 по-прежнему
Console.WriteLine(state1.x); // 1
Console.WriteLine(state2.x); // 5

Country country1 = new Country(); // Класс Country
Country country2 = new Country();
country2.x = 1;
country2.y = 4;
country1 = country2;
country2.x = 7; // теперь и country1.x = 7, так как обе ссылки и country1 и country2 
                // указывают на один объект в хипе
Console.WriteLine(country1.x); // 7
Console.WriteLine(country2.x); // 7
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

class Program
{
	static void _____(string[] _____)
    {
		Calculate(5);
    }

	static void Calculate(int t)
    {
		int x = 6;
        int y = 7;
        int z = y + t;
	}
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

static void Calculate(int t)
{
	object x = _____;
    int y = _____;
    int z = y + t;
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

{
}
    int y = 7;
	object x = 6;
static void Calculate(int t)
    int z = y + t;
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Типы значений и ссылочные типы»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «Типы значений и ссылочные типы», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

class Program
{
	static void Main(string[] args)
    {
		Calculate(5);
    }

	static void Calculate(int t)
    {
		int x = 6;
        int y = 7;
        int z = y + t;
	}
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Типы значений и ссылочные типы»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

static void Calculate(int t)
{
	object x = 6;
    int y = 7;
    int z = y + t;
}
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Типы значений и ссылочные типы»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Типы значений и ссылочные типы»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «Типы значений и ссылочные типы». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.