Важливо відокремити вивезення від збирання сміття. Вони є абсолютно окремими речами, з одним спільним моментом, до якого я прийду за хвилину.
Dispose
, вивезення сміття та його доопрацювання
Коли ви пишете using
заяву, це просто синтаксичний цукор для блоку спробу / нарешті, який Dispose
викликається, навіть якщо код в тілі using
виписки кидає виняток. Це не означає, що об’єктом є сміття, зібране в кінці блоку.
Утилізація стосується некерованих ресурсів (непам’яті). Це можуть бути ручки інтерфейсу користувача, мережеві з'єднання, ручки файлів тощо. Це обмежені ресурси, тому ви, як правило, хочете їх звільнити, як тільки зможете. Ви повинні реалізовувати, IDisposable
коли ваш тип "володіє" некерованим ресурсом, безпосередньо (як правило, через IntPtr
), або опосередковано (наприклад, через a Stream
, SqlConnection
тощо).
Сам збір сміття стосується лише пам’яті - одним невеликим поворотом. Збирач сміття здатний знайти предмети, на які вже не можна посилатися, та звільнити їх. Він не шукає сміття весь час - лише тоді, коли виявить, що йому потрібно (наприклад, якщо у одного "покоління" купи не вистачає пам'яті).
Поворот - доопрацювання . Збирач сміття зберігає перелік об'єктів, які вже недоступні, але мають фіналізатор (написано як~Foo()
на C #, дещо заплутано - вони не схожі на деструктори C ++). Він запускає фіналізатори на цих об'єктах, про всяк випадок, якщо їм доведеться провести додаткове очищення до звільнення пам'яті.
Фіналізатори майже завжди використовуються для очищення ресурсів у випадку, коли користувач типу забув утилізувати їх впорядкованому порядку. Тож якщо ви відкриєте, FileStream
але забудете зателефонувати Dispose
або Close
, фіналізатор згодом випустить основну ручку файлу для вас. У добре написаній програмі фіналізатори, на мою думку, майже ніколи не повинні стріляти.
Встановлення змінної в null
Одне невелике значення щодо встановлення змінної на null
- це майже ніколи не потрібно для збору сміття. Іноді ви можете це зробити, якщо це змінна команда, хоча, на мій досвід, рідкість "частини" об'єкта вже не потрібна. Коли це локальна змінна, JIT, як правило, досить розумний (у режимі випуску), щоб знати, коли ви не збираєтеся знову використовувати посилання. Наприклад:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
Один раз, коли, можливо, варто встановити локальну змінну null
, коли ви знаходитесь у циклі, і деякі гілки циклу повинні використовувати змінну, але ви знаєте, що ви досягли точки, у якій цього не робите. Наприклад:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
Впровадження ID / одноразових фіналізаторів
Отже, чи повинні ваші власні типи впроваджувати фіналізатори? Майже точно не. Якщо ви лише опосередкованоFileStream
володієте некерованими ресурсами (наприклад, у вас є змінною учасника), то додавання власного фіналізатора не допоможе: потік майже напевно буде придатний для збору сміття, коли ваш об’єкт є, тому ви можете просто розраховувати на FileStream
наявність фіналізатора (при необхідності - це може стосуватися чогось іншого тощо). Якщо ви хочете безпосередньо керувати некерованим ресурсом, SafeHandle
це ваш друг - це займе трохи часу, щоб розпочати роботу, але це означає, що вам майже ніколи не доведеться писати фіналізатор ще раз . Зазвичай вам потрібен фіналізатор лише у тому випадку, якщо у вас є реальна пряма обробка на ресурсі (an IntPtr
), і ви хочете перейти доSafeHandle
як тільки зможете. (Там є два посилання - читайте обидва, в ідеалі.)
Джо Даффі має дуже довгий набір рекомендацій щодо фіналізаторів та IDisposable (у співавторстві з великою кількістю розумних людей), які варто прочитати. Варто усвідомлювати, що якщо ви запечатуєте свої класи, це набагато полегшує життя: схема переосмислення Dispose
виклику нового віртуального Dispose(bool)
методу тощо є актуальною лише тоді, коли ваш клас призначений для успадкування.
Це було трохи невдало, але, будь ласка, попросіть роз'яснити, де ви хочете трохи :)