Об'єкти ніколи не виходять за межі в C #, як це робиться в C ++. З ними збирається сміттєзбірник автоматично, коли вони більше не використовуються. Це більш складний підхід, ніж C ++, де область змінної повністю детермінована. Збірник сміття CLR активно проходить через усі створені об’єкти та працює, якщо вони використовуються.
Об'єкт може вийти "поза межі" в одній функції, але якщо його значення повернеться, тоді GC буде вивчати, чи відповідає функція виклику поверненню.
Встановлення посилань на об'єкти не null
є необхідним, оскільки збирання сміття працює шляхом розробки, на які об’єкти посилаються інші об'єкти.
На практиці ви не повинні турбуватися про знищення, це просто працює і це чудово :)
Dispose
необхідно викликати всі об’єкти, які реалізуються, IDisposable
коли ви закінчите роботу з ними. Зазвичай ви використовуєте using
блок з такими об'єктами, як:
using (var ms = new MemoryStream()) {
//...
}
EDIT Про змінну область застосування. Крейг запитав, чи впливає змінна область впливу на термін експлуатації об'єкта. Щоб правильно пояснити цей аспект 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;
}
}
Нижче наведено генерований ІЛ. Зауважте, що 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 ++ ви можете створювати об'єкти на стеці або на купі. Коли ви створюєте їх у стеку, коли виконання залишає область, вони вискакують з стека і руйнуються.
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 #. Вони просто вказують на розташування купи, яка містить об'єкт. Таким чином, встановлення його на нуль не має негайного впливу на термін експлуатації об'єкта, повітряна куля продовжує існувати, поки GC "не спливає" її.
Продовжуючи аналогію на повітряній кулі, здавалося б логічним, що коли повітряна куля не має прикріплених до неї струн, її можна зруйнувати. Насправді саме так працюють посилальні обчислені об'єкти в некерованих мовах. За винятком того, що цей підхід не дуже добре працює для кругових посилань. Уявіть два повітряні кулі, які з'єднані разом струною, але жодна куля не має струни ні до чого іншого. За простими правилами підрахунку посилань, вони обидва продовжують існувати, навіть якщо вся повітряна куля "осиротіла".
Об'єкти .NET дуже схожі на гелієві кулі під дахом. Коли дах відкриється (GC біжить) - невикористані повітряні кулі пливуть далеко, хоча можуть бути групи повітряних куль, які прив'язані разом.
.NET GC використовує комбінацію поколінь GC та позначення та розгортки. Підхід генерації передбачає виконання часу, що сприяє огляду об'єктів, які були виділені останнім часом, оскільки вони, швидше за все, не використовуються, а позначення та розгортка передбачає час виконання, проходячи весь графік об'єктів, і відпрацьовування, якщо є об'єкти групи, які не використовуються. Це адекватно стосується проблеми кругової залежності.
Крім того, .NET GC працює на іншому потоці (так званий фінілайзерний потік), оскільки він має зовсім небагато зробити, і це робити в основному потоці перерве вашу програму.