Иллюстрированный самоучитель по Architecture .NET

Управляемые и неуправляемые ссылки и типы значений

Существуют фундаментальные отличия между тем, как управляемые и неуправляемые коды обрабатывают ссылки и типы значений. Неуправляемый код C++ позволяет объявлять локальные переменные, параметры методов и члены классов как относящиеся к типам, определенным неуправляемыми классами или структурами. Такие типы называются типами значений, так как подобные переменные содержат значения, которые в действительности являются данными. C++ также позволяет определять переменную как указатель или как ссылку на тип, определенный классом или структурой.

Такие типы называются ссылочными типами, или типами ссылки, так как переменные реально не содержат значения, которые являются объектом, а вместо этого являются ссылками на объект соответствующего типа в неуправляемой динамически распределяемой области памяти. Это может немного удивить, потому что C++ пытается провести концептуальное разграничение между типами указателей и ссылочными типами. И все же в действительности ссылка в C++ является просто разновидностью постоянного (константного) указателя.

Объявление типа значения представляет собой в действительности выделение пространства памяти под реальные значения. Однако в управляемом коде нельзя объявить управляемый класс или структуру в качестве типа значения (если только не используется ключевое слово _value (значение)). Это можно увидеть в следующем примере программы, ManagedAndUnmanagedRefAndVALTypes, где компилятор отметит как ошибочное объявление переменной в качестве типа значения с помощью управляемого класса ManagedClass. Для того чтобы это увидеть, попробуйте раскомментировать строку программы, содержащую оператор ManagedClassmcObj;. В результате появится сообщение об ошибке, извещающее, что вы, возможно, хотели объявить указатель, а не значение.

С другой стороны, не будет ошибкой определение переменной типа значения с помощью неуправляемого класса UnmanagedClass. Заметим, что также не будет ошибкой создание экземпляров класса ManagedClass в управляемой динамически распределяемой области памяти и экземпляров класса UnmanagedClass в неуправляемой динамически распределяемой области памяти с использованием оператора new (создать). Единственным отличием в этих случаях будет то, что для управляемого объекта не нужен оператор delete (удалить) для того, чтобы избежать утечки памяти, а для неуправляемого экземпляра такой оператор понадобится.

В нашем конкретном примере оператор delete (удалить) используется в последней строке для удаления объекта pmcobj, и комментарий утверждает, что "удалять обычно не требуется, но здесь необходимо". Оператор delete (удалить) добавлен в последнюю строку программы не потому, что непосредственная очистка требуется для управляемого объекта (сборщик мусора это сделает и без нас), а добавлен он из временных соображений. Это сделано потому, что вызов Console::WriteLine в деструкторе управляемого класса в противном случае производился бы в самом конце выполнения программы, уже после того, как выходной поток Console (Консоль) был бы закрыт.

Другими словами, если бы деструктор не был вызван явно оператором delete (удалить), управляемый объект попытался бы выполнить вывод в несуществующий поток, что вызвало бы исключение (System.ObjectDisposedException: Cannot access a closed Stream – Нельзя обращаться к закрытому потоку). Это демонстрирует наиболее общую причину явного удаления управляемого объекта, которая заключается в том, что иногда требуется явно указать момент разрушения объекта.

Хотелось бы еще обратить внимание на то, что компилятор обрабатывает примитивные типы данных (такие как int, float (с плавающей точкой), double (с удвоенной точностью), char (символ), и т.д.) не так, как управляемые классы сборщика мусора (_gс) или структуры сборщика мусора (_gс), потому что примитивные типы всегда являются типами значений.

//ManagedAndUnmanagedRefAndVALTypes.срр
fusing <mscorlib.dll>
using namespace System;
// использование пространства имен Система;
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
public:
ManagedClass()
{
Console::WriteLine("ManagedClass");
}
^ManagedClass()
{
Console::WriteLine("~ManagedClass");
}
};
_nogc class UnmanagedClass
{
public:
UnmanagedClass()
{
Console::WriteLine("UnmanagedClass"); }
~UnmanagedClass() (
Console::WriteLine("~UnmanagedClass"); } };
void main(void) {
ManagedClass *pmcObj = new ManagedClass();
//ManagedClass mcObj; // ошибка, тип значения, не допустим
UnmanagedClass *pumcObj = new UnmanagedClass();
delete pumcObj;
// требуется удалить из-за отсутствия сборки мусора
UnmanagedClass umcObj; // нет ошибки, тип значения допустим
int i = 3;
// нет ошибки, тип значения допустим для примитивных типов 'delete pmcObj;
// удалять обычно не требуется, но здесь необходимо
}

Вот что выведет приведенный пример программы:

ManagedClass UnmanagedClass
~UnmanagedClass UnmanagedClass
~ManagedClass
~UnmanagedClass
Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.