Встановлення об'єктів у Null / Nothing після використання в .NET


187

Чи слід встановити всі об'єкти null( Nothingу VB.NET) після того, як ви закінчите з ними?

Я розумію , що в .NET необхідно для реалізації будь-яких примірників об'єктів , які реалізують IDisposableінтерфейс , щоб звільнити деякі ресурси , хоча об'єкт все ще може бути що - то після того, як воно розташоване (звідси isDisposedвластивість в формах), так що я припускаю , що це може ще Reside в пам’яті чи хоча б частково?

Я також знаю, що коли об'єкт виходить за межі, він позначається для збору, готового до наступного пропуску сміттєзбірника (хоча це може зайняти час).

Отже, маючи на увазі, це налаштувати його на nullпришвидшення системи, звільняючи пам'ять, оскільки не потрібно виявити, що вона більше не входить у сферу застосування та чи є вони якісь погані побічні ефекти?

Статті MSDN ніколи цього не роблять на прикладах, і зараз я це роблю, оскільки не бачу шкоди. Однак я натрапив на сукупність думок, тому будь-які коментарі корисні.


4
+1 велике запитання. Хтось знає обставину, за якої компілятор повністю оптимізує завдання? тобто хтось дивився на MSIL за різних обставин і відзначав ІЛ для встановлення нуля (або його відсутності).
Тім Медора

Відповіді:


73

Карл абсолютно правильний, немає необхідності встановлювати об’єкти на нуль після використання. Якщо об’єкт реалізований IDisposable, просто переконайтеся, що ви телефонуєте, IDisposable.Dispose()коли закінчите з цим об’єктом (загорнувшись у try.. finally, або using()блок). Але навіть якщо ви не пам’ятаєте зателефонувати Dispose(), метод завершення на об’єкт повинен викликати Dispose()вас.

Я подумав, що це вдале лікування:

Копання в IDisposable

і це

Розуміння IDisposable

Немає сенсу намагатися вдруге здогадатися про GC та його стратегії управління, тому що він самонастроюється та непрозорий. Тут була хороша дискусія про внутрішню роботу з Джефрі Ріхтер у Dot Net Rocks: Джеффрі Ріхтер на моделі пам'яті Windows та книзі Ріхтерса CLR через C # главу 20 має чудове лікування:


6
Правило про невстановлення нуля не є "складним і швидким" ... якщо об'єкт буде розміщений на великій купі об'єктів (розмір становить> 85K), це допоможе GC, якщо ви встановите об'єкт на нуль, коли закінчите використовуючи його.
Скотт Дорман

Я погоджуюсь в обмеженій мірі, але якщо ви не починаєте відчувати тиск в пам'яті, тоді я не бачу необхідності "передчасної оптимізації", встановлюючи об'єкти на нуль після використання.
Кев

21
Весь цей бізнес "не передчасно оптимізуючи" звучить більше як "Віддавайте перевагу повільним і не хвилюйтесь, оскільки процесори стають швидшими, а додатки CRUD все одно не потребують швидкості". Це, можливо, просто я. :)
BobbyShaftoe

19
Що насправді означає - «Колекціонер сміття краще керувати пам’яттю, ніж ти». Це, можливо, тільки я. :)
BobRodes

2
@BobbyShaftoe: Напевно, так само неправильно говорити "передчасна оптимізація погана, завжди", як це переходити до протилежної крайності "звучить більше, як" віддайте перевагу повільному ". Жоден розумний програміст теж не сказав. Це про нюанси та розумність щодо оптимізації. Я особисто переживаю за чіткість коду і ТОГО СУЧАСНО Тестування, оскільки я особисто бачив, що багато людей (в тому числі і я, коли я був молодшим) витрачають багато часу, роблячи «ідеальний» алгоритм, тільки щоб він заощадив 0,1 мс за 100 000 ітерацій за весь час, коли читабельність була повністю знята.
Brent Rittenhouse

36

Ще одна причина, щоб уникнути встановлення об'єктів на нуль, коли ви закінчите з ними, - це те, що насправді може тримати їх живими довше.

напр

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

дозволить об'єкту, на який посилається someType, бути GC'd після виклику "DoSomething", але

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

іноді може тримати об’єкт живим до кінця методу. JIT зазвичай оптимізується геть присвоювання нуля , так як біти кінця коду вгору є однаковим.


Це цікавий момент. Я завжди думав, що об'єкти не виходять із сфери застосування до тих пір, поки не буде завершено метод, за яким вони охоплюються. Якщо, звичайно, об'єкт не розміщений у блоці Використання або явно встановлено "Нічого" або "Недійсне".
Гуру Джош

1
Переважним способом забезпечити, щоб вони залишилися в живих, є використання GC.KeepAlive(someType); See ericlippert.com/2013/06/10/construction-destruction
NotMe

14

Ні, не обнуляйте об'єкти. Ви можете ознайомитись з http://codebetter.com/blogs/karlseguin/archive/2008/04/27/foundations-of-programming-pt-7-back-to-basics-memory.aspx для отримання додаткової інформації, але налаштування речей null нічого не зробить, окрім забруднення вашого коду.


1
Приємне та детальне пояснення пам’яті у спільному посиланні
user2323308

Посилання розірвано. Без пов’язаного вмісту ця відповідь є скоріше корисною і її слід видалити.
Імре Пювель


7

Взагалі, немає необхідності обнуляти об’єкти після використання, але в деяких випадках я вважаю це хорошою практикою.

Якщо об’єкт реалізує IDisposable і зберігається в полі, я вважаю, що добре його обнулити, лише щоб уникнути використання об'єкта, що знаходиться у розпорядженні. Помилки такого роду можуть бути болючими:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Добре обнулити поле після його розміщення та отримати NullPtrEx прямо у тій лінії, де це поле знову використовується. В іншому випадку ви можете зіткнутися з якоюсь криптованою помилкою вниз по лінії (залежно від того, що саме робить DoSomething).


8
Добре, що розміщений об'єкт повинен кинути ObjectDisposedException, якщо він вже був розміщений. Для цього, наскільки я знаю, потрібен код котла, але все одно, Disposed - це все-таки погано продумана парадигма.
nicodemus13

3
Ctrl + F для .Dispose(). Якщо ви виявите, ви не використовуєте IDisposable правильно. Єдине використання для одноразового об'єкта повинно бути в межах блоку використання. А після користування-блоком ви вже навіть не маєте доступу до нього myField. І всередині блоку, що використовує, встановити значення nullне потрібно, блок-блок буде розпоряджатися об'єктом для вас.
Суамер

7

Цілком ймовірно, що ваш код недостатньо структурований, якщо ви відчуваєте потребу в цьому null змінних.

Існує кілька способів обмежити область змінної:

Як згадував Стів Транбі

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Так само ви можете просто використовувати фігурні дужки:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Я вважаю, що використання фігурних дужок без будь-яких "заголовків" дійсно очистити код і допомогти зробити його більш зрозумілим.


Я спробував використати спеціальні локальні сфери один раз (в основному це smarta $$). Компанія вибухнула.
Суамер

Ще одна примітка: це тому, що компілятор c # знайде локальні змінні, що реалізують IDisposable, і викличе .Dispose (MOST Of the time), коли їх область закінчується. Однак ... SQL-з'єднання - це великий час, коли .Dispose () ніколи не оптимізовано. Є деякі типи, які потребують явної уваги, тому я завжди завжди роблю речі явно просто, щоб мене не покусали.
Суамер

5

Єдиний раз, коли вам слід встановити змінну до нуля, це те, коли змінна не виходить за межі і вам більше не потрібні пов'язані з нею дані. Інакше немає потреби.


2
Це правда, але це також означає, що ви, ймовірно, повинні переробити код. Я не думаю, що мені ніколи не потрібно було декларувати змінну за межами призначеного обсягу.
Карл Сегуїн

2
Якщо під "змінною" розуміються об'єктні поля, то ця відповідь має багато сенсу. У випадку, коли "змінна" означає лише "локальну змінну" (методу), ми, мабуть, тут говоримо про нишеві випадки (наприклад, про метод, який працює набагато довше, ніж зазвичай).
stakx - більше не вносяться повідомлення

5

Взагалі не потрібно встановлювати нуль. Але припустимо, у вас є функція Reset у своєму класі.

Тоді ви можете це зробити, тому що ви не хочете двічі викликати розпорядження, оскільки частина диспозитивів може бути невірно реалізована та викинути виняток System.ObjectDisposed

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }

Найкраще, можливо, відстежувати це окремим прапором.
Тулані Чівандіква

3

подібний вид "немає необхідності встановлювати об'єкти на нуль після використання" не зовсім точний. Час потрібно змінити NULL змінну після її розміщення.

Так, вам ТАКОЖ слід дзвонити .Dispose()або .Close()на все, що є, коли ви закінчите. Будь то ручки файлів, з'єднання бази даних або одноразові об'єкти.

Окремим від цього є дуже практичний зразок LazyLoad.

Скажімо , у мене є і екземпляри ObjAз class A. Class Aмає суспільна властивість PropBзclass B .

Внутрішньо PropBвикористовує приватну змінну _Bта за замовчуванням нуль. Коли PropB.Get()він використовується, він перевіряє, чи _PropBє недійсним, і якщо він є, відкриває ресурси, необхідні для інстанції Bв _PropB. Потім він повертається _PropB.

На мій досвід, це дійсно корисна хитрість.

Там, де виникає потреба в нульовому _PropBзначенні A, якщо ви скидаєте або змінюєте A якимось чином, щоб вміст був дочірніми попередніми значеннями , вам потрібно буде розпоряджатись і _PropBобнуляти нуль, щоб LazyLoad міг скинути для отримання потрібного значення, якщо код вимагає цього.

Якщо ви зробите лише _PropB.Dispose()та незабаром після того, як очікуєте, що перевірка на нуль для LazyLoad буде успішною, вона не буде нульовою, і ви подивитеся на застарілі дані. По суті, ви повинні скасувати її післяDispose() того, щоб бути впевненим.

Я впевнений, хотів би, щоб це було інакше, але я вже зараз маю код, що демонструє цю поведінку після Dispose()ввімкнення _PropBі поза функцією виклику, яка виконувала функцію Dispose (і, таким чином, майже вийшла за рамки), приватна опора все ще не має нуля, а застарілі дані все ще є.

Врешті-решт, розпоряджається властивість зведе нанівець, але це з моєї точки зору не детерміновано.

Основна причина, як натякає dbkk, полягає в тому, що батьківський контейнер ( ObjAз PropB) зберігає екземпляр _PropBв області застосування, незважаючи на Dispose().


Хороший приклад показує, як встановлення на нуль вручну означає більш фатальну помилку для абонента, що є хорошою справою.
булочки

1

Є деякі випадки, коли має сенс скасувати нульові посилання. Наприклад, коли ви пишете колекцію - як черговість з пріоритетом - і за своїм договором, ви не повинні зберігати ці об’єкти для клієнта після того, як клієнт видалив їх з черги.

Але така річ має значення лише у колекціях, що довго живуть. Якщо черга не переживе кінця функції, в якій вона була створена, то це має значення набагато менше.

В цілому, ви дійсно не повинні турбуватися. Нехай компілятор і GC роблять свою роботу, щоб ви могли зробити своє.


1

Подивіться також цю статтю: http://www.codeproject.com/KB/cs/idisposable.aspx

Здебільшого встановлення об’єкта на нуль не впливає. Єдиний час, коли ви повинні це впевнитись, це якщо ви працюєте з "великим об'єктом", який має розмір більше 84 К (наприклад, растрові карти).


1

Стівен Клірі дуже добре пояснює цю посаду: чи варто встановити змінні на "Нульове сприяння збору сміття"?

Каже:

Короткий відповідь, для нетерплячого Так, якщо змінна є статичним полем, або якщо ви пишете перелічуючий метод (використовуючи віддачу прибутковості) або асинхронний метод (використовуючи асинхронізацію та очікування). Інакше ні.

Це означає, що в звичайних методах (нечисленних та несинхронних) ви не встановлюєте нульові локальні змінні, параметри методу чи поля екземпляра.

(Навіть якщо ви реалізуєте IDisposable.Dispose, ви все одно не повинні встановлювати змінні на null).

Важливе, що ми повинні врахувати, - це Статичні поля .

Статичні поля - це завжди корінні об'єкти , тому їх завжди вважають "живими" збирач сміття . Якщо статичне поле посилається на об’єкт, який більше не потрібен, його слід встановити на нуль, щоб збирач сміття вважав його придатним для збору.

Встановлювати статичні поля на нуль безглуздо, якщо весь процес вимикається. У цій купі збирається сміття, включаючи всі кореневі об'єкти.

Висновок:

Статичні поля ; ось про це. Все інше - це марна трата часу .


0

Я вважаю, що, розробивши реалізатори GC, ви не можете прискорити GC з недійсним. Я впевнений, що вони вважають за краще, щоб ви не переживали себе з тим, як / коли працює GC - ставитесь до цього, як до цієї всюдисущої істоти і спостерігаючи за вами та з… .

Особисто я часто явно встановлюю змінні значення недійсними, коли закінчую з ними як форму самодокументації. Я не декларую, використовую, а потім встановлюю нульову пізніше - я скасовую нуль відразу після того, як вони більше не потрібні. Я прямо кажу: "Я офіційно закінчую з тобою ... не зник ..."

Чи потрібна зведення нанівець мовою GC'd? Ні. Це корисно для GC? Можливо, так, може, ні, не знаю напевно, дизайном я справді не можу це контролювати, і незалежно від сьогоднішньої відповіді з цією версією чи іншою, майбутні реалізації GC можуть змінити відповідь поза моїм контролем. Плюс, якщо / при оптимізації нульової оптимізації це трохи більше, ніж примхливий коментар, якщо ви хочете.

Я вважаю, якщо це робить мій намір зрозумілішим для наступного бідного дурня, який йде по моїх слідах, і якщо це "може" потенційно допомогти GC іноді, то воно того варто для мене. Переважно це змушує мене почувати себе впорядковано та ясно, а Монго любить відчувати себе впорядковано та ясно. :)

Я дивлюся на це так: мови програмування існують, щоб люди давали іншим людям уявлення про наміри, а компілятор запитував на роботу, що робити - компілятор перетворює цей запит на іншу мову (іноді кілька) для процесора - процесор (и) може дати зрозуміти, якою мовою ви користувалися, налаштування вкладки, коментарі, стилістичні акценти, назви змінних тощо - це все про бітовий потік, який розповідає про те, які регістри та опкоди та місця пам’яті можна згорнути. Багато речей, написаних у коді, не перетворюються на те, що споживається процесором у послідовності, яку ми вказали. Наш C, C ++, C #, Lisp, Babel, асемблер або будь-яка інша теорія, а не реальність, написана як робота. Що ви бачите, це не те, що ви отримуєте, так, навіть мовою асемблера.

Я розумію, що "непотрібні речі" (наприклад, порожні рядки) "- це не що інше, як шум і захаращення коду". Це я був раніше в кар’єрі; Я цілком це розумію. У цей момент я схиляюся до того, що робить код яснішим. Це не так, як я додаю до своїх програм навіть 50 рядків "шуму" - це кілька рядків тут чи там.

Бувають винятки з будь-якого правила. У сценаріях з мінливою пам'яттю, статичною пам'яттю, умовами перегонів, одиночними кнопками, використанням "несвіжих" даних і всіма подібними гнилями, це різне: вам потрібно управляти власною пам'яттю, блокувати і зводити нанівець як приклад, тому що пам'ять не є частиною Всесвіт GC'd - сподіваємось, це всі розуміють. Решту часу з мовами GC'd це вже питання стилю, а не необхідності або гарантованого підвищення продуктивності.

В кінці робочого дня переконайтеся, що ви розумієте, що підходить до ГК, а що ні; заблокувати, розпоряджатися та зводити нанівець відповідним чином; віск на, віск від; вдихнути, видихнути; а для всього іншого я кажу: якщо вам добре, зробіть це. Ваш пробіг може змінюватися ... як слід ...


0

Я думаю, що повернути щось до нуля є безладним. Уявіть сценарій, коли предмет, який встановлюється зараз, піддається впливу, наприклад, через властивість. Тепер якимось фрагментом коду випадково використовує цю властивість після вилучення товару, ви отримаєте нульовий посилання на виняток, яке вимагає певного дослідження, щоб з’ясувати, що саме відбувається.

Я вважаю, що рамкові одноразові матеріали дозволять кинути ObjectDisposedException, що є більш значимим. Якщо не встановити ці значення назад до нуля, було б краще тоді з цієї причини.


-1

Деякі об'єкти припускають .dispose()метод, який змушує ресурс вилучати з пам'яті.


11
Ні, це не так; Dispose () не збирає об'єкт - він використовується для детермінованого очищення, як правило, вивільнення некерованих ресурсів.
Марк Гравелл

1
Маючи на увазі, що детермінізм стосується лише керованих ресурсів, а не некерованих (тобто пам’яті)
nicodemus13
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.