Объекты никогда не выходят из области видимости в C #, как в C ++. Они обрабатываются сборщиком мусора автоматически, когда они больше не используются. Это более сложный подход, чем C ++, где область действия переменной полностью детерминирована. Сборщик мусора CLR активно просматривает все созданные объекты и работает, если они используются.
Объект может выйти из области видимости в одной функции, но если его значение будет возвращено, то GC посмотрит, удерживает ли вызывающая функция возвращаемое значение.
Установка ссылок на объекты на null
не нужна, так как сборка мусора работает, определяя, на какие объекты ссылаются другие объекты.
На практике вам не нужно беспокоиться о разрушении, оно просто работает и это здорово :) 1008 *
Dispose
должен вызываться на всех объектах, которые реализуют IDisposable
, когда вы закончите работать с ними. Обычно вы используете блок using
с этими объектами, например:
using (var ms = new MemoryStream()) {
//...
}
РЕДАКТИРОВАТЬ В переменной области. Крейг спросил, влияет ли переменная область действия на время жизни объекта. Чтобы правильно объяснить этот аспект CLR, мне нужно объяснить несколько понятий из C ++ и C #.
Фактическая область действия переменной
В обоих языках переменную можно использовать только в той же области, в которой она была определена - класс, функция или блок операторов, заключенный в фигурные скобки. Однако тонкое различие заключается в том, что в C # переменные не могут быть переопределены во вложенном блоке.
В C ++ это совершенно законно:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
В C # вы получаете ошибку компилятора:
int iVal = 8;
if(iVal == 8) {
int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}
Это имеет смысл, если вы посмотрите на сгенерированный MSIL - все переменные, используемые функцией, определены в начале функции. Посмотрите на эту функцию:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
Ниже сгенерированный IL. Обратите внимание, что iVal2, который определен внутри блока if, фактически определен на уровне функций. Фактически это означает, что C # имеет только область действия класса и уровня функций, что касается времени жизни переменной.
.method public hidebysig static void Scope() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 iVal,
[1] int32 iVal2,
[2] bool CS$4$0000)
//Function IL - omitted
} // end of method Test2::Scope
Область действия C ++ и время жизни объекта
Всякий раз, когда переменная C ++, размещенная в стеке, выходит из области видимости, она уничтожается. Помните, что в C ++ вы можете создавать объекты в стеке или в куче. Когда вы создаете их в стеке, когда выполнение выходит из области видимости, они выталкиваются из стека и уничтожаются.
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
Когда объекты C ++ создаются в куче, они должны быть явно уничтожены, иначе это утечка памяти. Нет такой проблемы с переменными стека.
C # Время жизни объекта
В CLR объекты (т.е. ссылочные типы) всегда создаются в управляемой куче. Это дополнительно подкрепляется синтаксисом создания объекта. Рассмотрим этот фрагмент кода.
MyClass stackObj;
В C ++ это создаст экземпляр в MyClass
в стеке и вызовет его конструктор по умолчанию. В C # это создаст ссылку на класс MyClass
, который ни на что не указывает. Единственный способ создать экземпляр класса - использовать оператор new
:
MyClass stackObj = new MyClass();
В некотором смысле, объекты C # во многом похожи на объекты, которые создаются с использованием синтаксиса new
в C ++ - они создаются в куче, но в отличие от объектов C ++, ими управляет среда выполнения, поэтому вам не нужно беспокоиться о том, чтобы уничтожить их.
Поскольку объекты всегда в куче, тот факт, что ссылки на объекты (то есть указатели) выходят из области видимости, становится спорным. При определении необходимости сбора объекта требуется больше факторов, чем просто наличие ссылок на объект.
C # Ссылка на объект
Джон Скит сравнил ссылки на объекты в Java с фрагментами строки, которые прикреплены к всплывающей подсказке, которая является объектом. Та же аналогия применима к ссылкам на объекты C #. Они просто указывают на местоположение кучи, содержащей объект. Таким образом, установка его в null не оказывает непосредственного влияния на время жизни объекта, воздушный шар продолжает существовать, пока GC не «вытолкнет» его.
Продолжая аналогию с воздушным шаром, представляется логичным, что, если к воздушному шару не прикреплены нити, его можно уничтожить. Фактически именно так работают объекты с подсчетом ссылок в неуправляемых языках. За исключением того, что этот подход не очень хорошо работает для циклических ссылок. Представьте себе два воздушных шарика, которые связаны друг с другом цепочкой, но ни у одного из них нет цепочки ни к чему другому. Согласно простым правилам подсчета ссылок, они оба продолжают существовать, даже если вся группа воздушных шаров «осиротела».
.NET объекты очень похожи на гелиевые шарики под крышей. Когда крыша открывается (GC бежит) - неиспользованные воздушные шары всплывают, хотя могут быть группы воздушных шаров, которые связаны друг с другом.
.NET GC использует комбинацию поколений GC, меток и разверток. Подход, основанный на поколениях, включает предпочтение среды выполнения для проверки объектов, которые были выделены в последнее время, поскольку они с большей вероятностью будут неиспользованными, а разметка и развертка - во время выполнения, просматривая весь граф объектов и выясняя, есть ли группы объектов, которые не используются. Это адекватно решает проблему циклической зависимости.
Кроме того, .NET GC работает в другом потоке (так называемом потоке финализатора), так как у него довольно много работы, и выполнение этого в основном потоке прерывает вашу программу.