Тип элемента данных определяет объем памяти (количество бит), необходимый для его хранения, а также набор членов данных, содержащихся в данном типе. Кроме того, тип определяет, где в памяти будет размещен объект: в стеке (Stack) или в куче (Heap).
Типы делятся на две основные группы: типы значений (Value Types) и ссылочные типы (Reference Types). Способ хранения объектов этих двух групп в памяти полностью различается.
1. Тип значения занимает один непрерывный участок памяти, в котором напрямую хранятся реальные данные.

2. Ссылочный тип требует двух отдельных участков памяти:
- Первый участок хранит реальные данные объекта, он всегда выделяется в куче;
- Второй участок — это адрес ссылки, указывающий на расположение данных в куче. (В стеке хранится адрес из кучи, в эпоху языка C это называлось указателем)

Две приведенные иллюстрации корректны только для самостоятельных объектов, не являющихся членами данных другого объекта.
Важное замечание
Основная часть данных любого объекта ссылочного типа всегда размещается в куче, как показано на рисунках выше.
Однако экземпляры типов значений или адреса ссылок ссылочных типов могут находиться как в стеке, так и в куче — это зависит от структуры написанного кода.
Пример:
Предположим, существует ссылочный тип Person, содержащий два члена: один член типа значения и один ссылочный член.
Многие разработчики ошибочно полагают, что члены типов значений хранятся в стеке, адреса ссылок тоже в стеке, а основные данные объекта в куче (как на рисунках выше), но это неверное утверждение.
Запомните одно правило: все основные данные экземпляра ссылочного типа навсегда выделяются в куче. Оба члена относятся к основным данным этого объекта, поэтому независимо от того, являются ли они типами значений или ссылочными типами, они хранятся вместе в куче.
Пример кода:
class Program
{
static void Main(string[] args)
{
int a = 10;
Person bill = new Person("Bill", 18);
}
}
class Person
{
private string name;
private int age;
private Phone myPhone;
public Person(string name, int age)
{
this.name = name;
this.age = age;
this.myPhone = new Phone();
this.myPhone.number = 12345;
}
}
class Phone
{
public long number;
}Code language: JavaScript (javascript)
Рассмотрим пример выше: класс Person содержит стандартные поля age и name, а также пользовательский тип Phone. Тип Phone, в свою очередь, содержит стандартный тип long для хранения номера телефона.
Читатель может увидеть int age внутри Person или long number внутри Phone и решить, что эти типы значений хранятся в стеке, но это не так. Несмотря на то что это стандартные встроенные типы, они являются членами пользовательского класса, поэтому хранятся в куче вместе с экземпляром объекта. Итоговое расположение в памяти выглядит так:

Основное правило: все члены данных пользовательских классов хранятся в куче, будь то типы значений или ссылочные типы. Если внутри класса есть ссылочный член, как myPhone в примере, то в этом поле записан адрес другого объекта, расположенного в куче.
Если внутри класса Phone будет объявлен еще один пользовательский тип, то в области памяти Phone появится дополнительный участок для хранения адреса ссылки на этот второй объект.
Выполнение программы начинается с функции Main. Адрес самого верхнего ссылочного объекта хранится в стеке, а далее все связанные объекты выстраиваются цепочкой в куче.

Другой пример
// Ссылочный тип — класс
class MyType
{
public int A; // Член типа значения, встроен в экземпляр MyType в куче
public Person B; // Ссылочный член; указатель ссылки хранится в куче, основные данные выделены в отдельном участке кучи
}
class Person
{
public string Name;
}
void Test()
{
// В стеке хранится указатель ссылки на myObj
MyType myObj = new MyType();
/*
Структура кучи:
Объект MyType {
A: значение int, сохраненное встроенно
B: указатель ссылки на экземпляр Person
}
Отдельный участок кучи: Person{ Name: ... }
*/
// Локальная переменная типа значения, выделена напрямую в стеке
int localNum = 10;
}Code language: PHP (php)
Описанные выше правила относятся исключительно к членам данных класса, а не к его методам. Если в методе класса объявлены локальные переменные или ссылочные объекты, то локальные типы значений и адреса ссылочных экземпляров по-прежнему хранятся в стеке. Смотрите код ниже:
Рассмотрим следующий пример:
using System.ComponentModel.DataAnnotations;
class Program
{
static void Main(string[] args)
{
int a = 10;
Person bill = new Person("Bill", 18);
bill.Calculate();
}
}
class Person
{
private string name;
private int age;
private Phone myPhone;
public Person(string name, int age)
{
this.name = name;
this.age = age;
this.myPhone = new Phone();
this.myPhone.number = 12345;
}
public int Calculate()
{
int one = 12;
int two = 23;
Phone myNewPhone = new Phone();
return one + two;
}
}
class Phone
{
public long number;
}Code language: PHP (php)
В этом коде я добавил метод Calculate в класс Person. Внутри метода объявлены локальные переменные как типов значений, так и ссылочных типов, вызываемые из Main. Эти переменные не являются членами класса Person, они относятся к локальной области видимости метода: локальные типы значений хранятся в стеке, адреса ссылок локальных ссылочных объектов тоже сохраняются в стеке.

При выполнении строки bill.Calculate(); расположение данных в памяти выглядит так:

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

После завершения работы Calculate управление возвращается в Main, все локальные переменные, созданные внутри метода, удаляются из стека. Однако основные данные объекта myNewPhone (синяя часть на рисунке) остаются в куче. Из стека удаляется только адрес ссылки, реальные данные не уничтожаются сразу — они остаются в куче до момента сборки мусора GC.
Типы значений и ссылочные типы