Почему всегда необходимо реализовать IDisposable для объекта, который имеет член IDisposable? - PullRequest
4 голосов
/ 23 мая 2011

Из того, что я могу сказать, принято правило, что если у вас есть класс A с членом m, который является IDisposable, A должен реализовать IDisposable, и он должен вызвать внутри него m.Dispose ().

Я не могу найти вескую причину, почему это так.

Я понимаю правило, что если у вас есть неуправляемые ресурсы, вы должны предоставить финализатор вместе с IDisposable, чтобы, если пользователь неЯвно вызовите Dispose, финализатор все равно будет очищаться во время GC.

Однако, с этим правилом кажется, что вам не нужно иметь правило, о котором идет речь в этом вопросе.Например ...

Если у меня есть класс:

class MyImage{
  private Image _img;
  ... }

Соглашения гласят, что у меня должен быть MyImage : IDisposable.Но если Image следовал соглашениям и внедрил финализатор, и меня не волнует своевременное освобождение ресурсов, какой смысл?

ОБНОВЛЕНИЕ

Нашел хорошее обсуждениена то, что я пытался получить на здесь .

Ответы [ 7 ]

22 голосов
/ 23 мая 2011

Но если Image следовал соглашениям и внедрил финализатор, и мне наплевать на своевременное освобождение ресурсов, какой в ​​этом смысл?

Вы упустили пункт утилизацииполностью.Это не о вашем удобстве.Речь идет об удобстве других компонентов , которые могут захотеть использовать эти неуправляемые ресурсы.Если вы не можете гарантировать, что никакой другой код в системе не заботится о своевременном освобождении ресурсов, а пользователь не заботится о своевременном освобождении ресурсов, вы должны освобождать свои ресурсы каккак можно скорее.Это вежливо.

В классической Дилемме узника одинокий перебежчик в мире кооператоров получает огромное преимущество.Но в вашем случае быть одиноким перебежчиком дает вам лишь небольшую выгоду, если вы лично сэкономите несколько минут, написав некачественный код, игнорирующий лучшие практики.Страдают ваши пользователи и все программы, которые они используют, а вы практически ничего не получаете.Ваш код использует тот факт, что другие программы разблокируют файлы и освобождают мьютексы и все такое.Будь хорошим гражданином и сделай то же самое для них.Это не сложно сделать, и это делает всю программную экосистему лучше.

ОБНОВЛЕНИЕ: Вот пример реальной ситуации, с которой моя команда сейчас сталкивается.

У нас естьтестовая утилита.У него есть «утечка ручек» в том, что куча неуправляемых ресурсов не подвергается агрессивной утилизации;в одной «задаче» протекает, наверное, полдюжины ручек.Он ведет список «задач для выполнения», когда обнаруживает отключенные тесты и т. Д.У нас есть десять или двадцать тысяч задач в этом списке, поэтому мы очень быстро получаем так много выдающихся дескрипторов - дескрипторов, которые должны быть отключены и возвращены обратно в операционную систему - что вскоре ни один из кодов в системе, не , относящиеся к тестированию, могут выполняться.Код теста не волнует.Работает просто отлично.Но в конечном итоге проверяемый код не может создавать окна сообщений или другой пользовательский интерфейс, и вся система либо зависает, либо выходит из строя.

У сборщика мусора нет оснований знать, что он должен запускать финализаторы более агрессивно, чтобы быстрее освободить эти дескрипторы;зачем это?Его работа заключается в управлении памятью. Ваша работа заключается в управлении дескрипторами, поэтому вы должны выполнять эту работу.

6 голосов
/ 23 мая 2011

Но если Image следовал соглашениям и внедрил финализатор, и мне наплевать на своевременное освобождение ресурсов, какой смысл?

Тогда его нет, еслиВы не заботитесь о своевременном выпуске, и вы можете гарантировать, что одноразовый объект написан правильно (по правде говоря, я никогда не делаю такое предположение, даже с кодом MS. Вы никогда не знаете, когда что-то случайно проскользнуло).Дело в том, что вы должны заботиться, поскольку вы никогда не знаете, когда это вызовет проблему.Подумайте об открытом соединении с базой данных.Оставить его без дела, означает, что он не заменен в бассейне.Вы можете закончить, если у вас есть несколько запросов на один.

Ничто не говорит о том, что вы должны делать это, если вам все равно.Думайте об этом так, это похоже на освобождение переменных в неуправляемой программе.Вам не нужно, но это очень желательно.Если ни по какой другой причине человек, унаследовавший от программы, не должен задаться вопросом, почему об этом не позаботились, а затем попытаться выяснить это.

2 голосов
/ 23 мая 2011

Во-первых, нет никакой гарантии, когда объект будет очищен потоком финализатора - подумайте о случае, когда класс имеет ссылку на соединение SQL. Если вы не удостоверитесь, что это будет утилизировано незамедлительно, у вас будет открытое соединение в течение неизвестного периода времени - и вы не сможете его повторно использовать.

Во-вторых, финализация не является дешевым процессом - вы должны убедиться, что, если ваши объекты утилизируются должным образом, вы вызываете GC.SuppressFinalize (this), чтобы предотвратить финализацию.

Расширяя аспект "не дешево", поток финализатора является высокоприоритетным потоком. Он отнимет ресурсы у вашего основного приложения, если вы дадите ему слишком много работы.

Редактировать: Хорошо, вот статья в блоге Криса Брумми о Завершение , включая то, почему это дорого. (Я знал, что где-то прочту об этом)

1 голос
/ 27 мая 2011

Если объект, который вы используете, реализует IDisposable, он говорит вам, что должен сделать что-то важное, когда вы закончите с ним.Эта важная вещь может заключаться в освобождении неуправляемых ресурсов или отсоединении от событий, чтобы он не обрабатывал события после того, как вы думаете, что с ним покончено, и т. Д., И т. Д. Не вызывая Dispose, вы говорите, что знаете лучшео том, как работает этот объект, чем первоначальный автор.В некоторых крошечных крайних случаях это может быть действительно так, если вы сами создали класс IDisposable или знаете об ошибке или проблеме производительности, связанной с вызовом Dispose.В целом, очень маловероятно, что игнорирование класса, запрашивающего вас утилизировать его, когда вы закончите, является хорошей идеей.

Говоря о финализаторах - как уже указывалось, у них есть стоимость, которую можно избежатьУтилизация объекта (если он использует SuppressFinalize).Не только стоимость запуска самого финализатора, а не только стоимость ожидания завершения этого финализатора, прежде чем сборщик мусора сможет собрать объект.Объект с финализатором сохраняется в коллекции, в которой он идентифицирован как неиспользуемый и нуждающийся в финализации.Так что это будет повышаться (если это еще не во втором поколении).Это несколько ударов по эффектам:

  • Следующее более высокое поколение будет собираться реже, поэтому после запуска финализатора, вы можете долго ждать, пока GC дойдет до этого поколения и подметает вашвозразитьТаким образом, освобождение памяти может занять гораздо больше времени.
  • Это добавляет ненужное давление на коллекцию, в которую продвигается объект.Если он повышен с поколения 0 до поколения 1, то теперь поколение 1 будет заполняться раньше, чем нужно.
  • Это может привести к более частым сборкам мусора в старших поколениях, что является еще одним ударом по производительности.
  • Если финализатор объекта не завершен к тому времени, когда GC подходит к более высокому поколению, объект может быть снова продвинут.Следовательно, в плохом случае вы можете вызвать продвижение объекта из поколения 0 в поколение 2 без веской причины.

Очевидно, что если вы делаете это только с одним объектом, это вряд ли будет стоить вам ничегозаметно.Если вы делаете это как обычную практику, поскольку вам кажется, что вызов Dispose для объектов, которые вы используете, утомителен, тогда это может привести ко всем перечисленным выше проблемам.

Dispose - это как замок на входной двери.Вероятно, по какой-то причине, и если вы выходите из здания, вам, вероятно, стоит запереть дверь.Если это не было хорошей идеей, заблокировать его, не было бы блокировки.

1 голос
/ 24 мая 2011

Многие классы требуют, чтобы Dispose вызывался для обеспечения корректности. Например, если в некотором коде C # используется итератор с блоком «наконец», код в этом блоке не будет работать, если перечислитель создан с этим итератором и не удален. Хотя в некоторых случаях было бы непрактично гарантировать, что объекты были очищены без финализаторов, по большей части код, который полагается на финализаторы для правильной работы или во избежание утечек памяти, является плохим кодом.

Если ваш код приобретает право собственности на объект IDisposable, то, если только не очищена очистка объекта или если ваш код не создаст объект, вызвав конструктор (в отличие от метода фабрики), у вас нет способа узнать, что такое реальный тип объект есть, и можно ли от него безопасно отказаться. Microsoft, возможно, изначально намеревалась отказаться от любого типа объекта, но это нереально, и вера в то, что отказ от любого типа должен быть безопасным, бесполезна. Если объект подписывается на события, для обеспечения безопасного отказа потребуется либо добавить уровень слабой косвенности ко всем событиям, либо уровень (не слабой) косвенности ко всем другим доступам. Во многих случаях лучше требовать, чтобы вызывающая сторона располагала объектом правильно, чем добавляла значительные накладные расходы и усложняла, чтобы исключить отказ.

Также обратите внимание, что даже когда объекты пытаются приспособиться к оставлению, они все равно могут быть очень дорогими. Создайте коллекцию Microsoft.VisualBasic.Collection (или как она там называется), добавьте несколько объектов, а также создайте и утилизируйте миллион перечислителей. Нет проблем - выполняется очень быстро. Теперь создайте и откажитесь от миллиона перечислителей. Главный праздник повтора, если вы не заставите сборщиков каждые несколько тысяч счетчиков. Объект Collection написан так, чтобы его можно было оставить, но это не значит, что он не требует больших затрат.

1 голос
/ 23 мая 2011

Если вас не волнует своевременное освобождение ресурсов, тогда действительно нет смысла. Если вы можете быть уверены, что код предназначен только для вашего потребления, и у вас есть много свободной памяти / ресурсов, почему бы не позволить GC пылесосить его, когда он захочет. OTOH, если кто-то еще использует ваш код и создает много экземпляров (например, MyImage), будет довольно сложно контролировать использование памяти / ресурсов, если оно не удачно распределяется.

0 голосов
/ 23 мая 2011

Даже если вам все равно в этом конкретном случае, вы все равно должны следовать стандарту, потому что в некоторых случаях вам будет все равно.Гораздо проще установить стандарт и всегда следовать ему на основе конкретных рекомендаций, чем иметь стандарт, который вы иногда игнорируете.Это особенно верно, когда ваша команда растет и ваш продукт стареет.

...