CLR поддерживает две разновидности типов: ссылочные (reference types) и значимые (value types). Большинство типов в FCL — ссылочные, но программисты чаще всего используют значимые. Память для ссылочных типов всегда выделяется из управляемой кучи, а оператор C# new возвращает адрес в памяти, где размещается сам объект. При работе со ссылочными типами необходимо учитывать следующие обстоятельства, относящиеся к производительности приложения:
- Память для ссылочных типов всегда выделяется из управляемой кучи;
- Каждый объект в управляемой куче содержит дополнительные члены, подлежащие инициализации;
- Незанятые полезной информацией байты объекта обнуляются;
- Размещение объекта в управляемой кучи со временем инициируют сборку мусора
Работа с ссылочными типами - дорогостоящая операция, и если бы все типы являлись таковыми, то производительность бы резко упала! Для этого CLR предлагает облегчённые типы - значимые. Они используются достаточно часто и имеют фикисрованный размер. Экземпляры значимых типов не обрабатываются сборщиком мусора.
В документации на .NET Framework можно сразу увидеть, какие типы относят к ссылочным, а какие — к значимым. Если тип называют классом (class), речь идет о ссылочном типе. Например, классы System.Object, System.Exception, System.IO.FileStream и System.Random — это ссылочные типы. В свою очередь, значимые типы в документации называются структурами (structure) и перечислениями (enumeration). Например, структуры System.Int32, System.Boolean, System.Decimal, System.TimeSpan и перечисления System.DayOfWeek, System. IO.FileAttributes и System.Drawing.FontStyle являются значимыми типами.
// Ссылочный тип (поскольку 'class')
class SomeRef { public Int32 x; }
// Значимый тип (поскольку 'struct')
struct SomeVal { public Int32 x; }
static void ValueTypeDemo() {
SomeRef r1 = new SomeRef(); // Размещается в куче
SomeVal v1 = new SomeVal(); // Размещается в стеке
r1.x = 5; // Разыменовывание указателя
v1.x = 5; // Изменение в стеке
Console.WriteLine(r1.x); // Отображается "5"
Console.WriteLine(v1.x); // Также отображается "5"
// В левой части рис. 5.2 показан результат выполнения предыдущих строк
SomeRef r2 = r1; // Копируется только ссылка (указатель)
SomeVal v2 = v1; // Помещаем в стек и копируем члены
r1.x = 8; // Изменяются r1.x и r2.x
v1.x = 9; // Изменяется v1.x, но не v2.x
Console.WriteLine(r1.x); // Отображается "8"
Console.WriteLine(r2.x); // Отображается "8"
Console.WriteLine(v1.x); // Отображается "9"
Console.WriteLine(v2.x); // Отображается "5"
// В правой части рис. 5.2 показан результат выполнения ВСЕХ предыдущих строк }
**Основное достоинство значимых типов в том, что они не размещаются в управляемой куче. Конечно, в сравнении со ссылочными типами у значимых типов есть недостатки. Важнейшие отличия между значимыми и ссылочными типы: **
- Объекты значимого типа существуют в двух формах (см. следующий раздел): неупакованной (unboxed) и упакованной (boxed). Ссылочные типы бывают только в упакованной форме.
- Когда переменной значимого типа присваивается другая переменная значимого типа, выполняется копирование всех ее полей. Когда переменной ссылочного типа присваивается переменная ссылочного типа, копируется только ее адрес.
- Значимые типы являются производными от System.ValueType. Этот тип имеет те же методы, что и System.Object. Однако System.ValueType переопределяет метод Equals, который возвращает true, если значения полей в обоих объектах совпадают. Кроме того, в System.ValueType переопределен метод GetHashCode, который создает хеш-код по алгоритму, учитывающему значения полей экземпляра объекта. Из-за проблем с производительностью в реализации по умолчанию, определяя собственные значимые типы значений, надо переопределить и написать свою реализацию методов Equals и GetHashCode
- Поскольку в объявлении нового значимого или ссылочного типа нельзя указывать значимый тип в качестве базового класса, создавать в значимом типе новые виртуальные методы нельзя. Методы не могут быть абстрактными и неявно являются запечатанными (то есть их нельзя переопределить).
- Переменные ссылочного типа содержат адреса объектов в куче. Когда переменная ссылочного типа создается, ей по умолчанию присваивается null, то есть в этот момент она не указывает на действительный объект. Попытка задействовать переменную с таким значением приведет к генерации исключения NullReferenceException. В то же время в переменной значимого типа всегда содержится некое значение соответствующего типа, а при инициализации всем членам этого типа присваивается 0.
- Вследствие сказанного в предыдущем пункте несколько переменных ссылочного типа могут ссылаться на один объект в куче, благодаря чему, работая с одной переменной, можно изменить объект, на который ссылается другая переменная. В то же время каждая переменная значимого типа имеет собственную копию данных «объекта», поэтому операции с одной переменной значимого типа не влияют на другую переменную.