Синглтон с финализатором, но не с IDisposable

Вот что я понимаю об IDisposable и финализаторах из "CLR via C #", "Effective C #" и других ресурсов:

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

Хотя я понимаю причины и согласен со всем вышеперечисленным, есть один сценарий, в котором, на мой взгляд, имеет смысл нарушить эти правила: одноэлементный класс, отвечающий за неуправляемые ресурсы (например, предоставление единой точки доступа к определенным файлам. ).

Я считаю, что всегда неправильно иметь метод Dispose () для синглтона, потому что экземпляр синглтона должен жить в течение всего срока службы приложения, и если какой-либо клиентский код вызывает Dispose (), тогда вы набиты. Однако вам нужен финализатор, чтобы при выгрузке приложения финализатор мог очистить неуправляемые ресурсы.

Поэтому наличие одноэлементного класса с финализатором, который не реализует IDisposable, кажется мне разумным, но этот тип дизайна противоречит тому, что, как я понимаю, является передовой практикой.

Это разумный подход? Если нет, то почему и каковы лучшие альтернативы?


person Ben Robbins    schedule 21.01.2009    source источник


Ответы (6)


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

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

Я поддерживаю тех, кто говорит, что этот дизайн, возможно, неправильный (и его будет сложнее исправить, если впоследствии вы обнаружите, что вам действительно нужны два экземпляра). Создайте объект (или объект-оболочку с ленивой загрузкой) в вашей точке входа и передать его через код туда, где это необходимо, ясно указав, кто несет ответственность за его предоставление, затем вы можете изменить решение использовать только один, с небольшим влиянием на остальную часть кода (который использует то, что получает данный)

person ShuggyCoUk    schedule 21.01.2009

Сначала я хотел бы упомянуть, что объектно-ориентированные шаблоны проектирования и их последствия не всегда влияют на каждое языковое решение, даже в объектно-ориентированных языках. Вы, безусловно, можете найти классические шаблоны проектирования, которые легче реализовать на одном языке (Smalltalk), чем на другом (C ++).

При этом я не уверен, что согласен с предпосылкой, что одноэлементный экземпляр должен удаляться только в конце приложения. В описаниях шаблонов проектирования, которые я читал для синглтона (или Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования) упомянуть это как свойство этого паттерна. Синглтон должен гарантировать, что в любой момент времени существует только один экземпляр класса; это не означает, что он должен существовать, пока существует приложение.

У меня такое ощущение, что на практике многие синглтоны существуют на протяжении большей части жизни приложения. Однако рассмотрим приложение, которое использует TCP-соединение для связи с сервером, но также может существовать в отключенном режиме. При подключении вы хотите, чтобы синглтон поддерживал информацию о подключении и состояние подключения. После отключения вы можете оставить тот же синглтон или избавиться от него. Хотя некоторые могут утверждать, что имеет смысл сохранить синглтон (и я могу даже быть среди них), в самом шаблоне проектирования нет ничего, что мешает вам избавиться от него - если соединение переделано, синглтон может быть создан. опять же, поскольку в данный момент не существует его экземпляров.

Другими словами, вы можете создавать сценарии, в которых для синглтонов логично иметь IDisposable.

person Matt Jordan    schedule 21.01.2009
comment
Факт в том, что если вы можете представить причину замены вашего объекта в течение жизни приложения, этот объект не может на законных основаниях быть синглтоном. Что касается того, почему ... Скажем, я схватил синглтон и передал его тому, что в нем нуждалось (потому что, я знаю, внедрение зависимостей - это хорошо). Потом что-то где-то решает заменить. Теперь есть два синглтона (старый, который я обходил, и новый). Сама эта возможность нарушает все определение синглтона, то есть этот код всегда видит только один экземпляр. - person cHao; 31.01.2012
comment
Итак, прежде всего, святой старый ответ Бэтмена. Во-вторых, ваш пример не имеет смысла. Если вы передаете экземпляр синглтона объекту, а затем какой-то другой объект решает, что ему нужен синглтон, то шаблон позволяет это - он должен получить обратную ссылку на объект, который в настоящее время находится в памяти. Я хотел сказать, что узор не мешает вам избавиться от объекта. В этом случае, используя ваш пример, если объект был удален, единственный экземпляр больше не должен существовать, поэтому, когда другой объект запрашивает Singleton, он должен получить новый экземпляр. - person Matt Jordan; 10.02.2012
comment
Если объект удален, экземпляр действительно все еще существует - любой, кто приобрел его до того, как он был удален, все равно будет иметь его. Таким образом, очевидно, что сборщик мусора невозможен, но теперь он непригоден для использования. Подумайте о последствиях этого утверждения в многопоточном контексте. Выражение типа Singleton.getInstance().doStuff() больше не является потокобезопасным, даже если оба getInstance и doStuff. Между двумя вызовами может пройти поток и удалить только что полученный экземпляр. И это даже в предположении вашего мифического сценария «все всегда звонят getInstance». - person cHao; 10.02.2012

Пока ваш финализатор не вызывает методы (например, Dispose) для любых других управляемых объектов, все должно быть в порядке. Просто помните, что порядок завершения не детерминирован. То есть, если ваш одноэлементный объект Foo содержит ссылку на объект Bar, который требует удаления, вы не можете надежно написать:

~Foo()
{
    Bar.Dispose();
}

Сборщик мусора, возможно, уже собрал Bar.

Рискуя попасть в кучу OO goo (т.е. начать войну), одна из альтернатив использования синглтона - использование статического класса.

person Jim Mischel    schedule 21.01.2009
comment
+1 для статического класса - синглтоны не так необходимы, как предполагает их повсеместность. - person Andrew Hare; 21.01.2009
comment
Концептуально единственное различие между синглтоном и статическим классом состоит в том, что синглтон - это одно статическое поле, которое указывает на множество полей экземпляра, где статический класс - это набор статических полей. Когда дело доходит до управления ресурсами, различия между ними незначительны. - person Scott Wisniewski; 21.01.2009
comment
что сказал Скотт - одноразовые синглтоны - это просто фундаментальная проблема - person annakata; 21.01.2009
comment
Неужели GC действительно собрал Bar? Из того, что я читал в другом месте, хотя Bar, возможно, был завершен, объекты, на которые ссылается объект, ожидающий завершения Foo, имеют право на завершение, но не на сборку мусора; Тем не менее, я прошу некоторых пояснений по этому поводу. Конечно, вызов Bar.Dispose будет небезопасным, если только кто-то не знает, что он не будет получать никаких блокировок, создавать какие-либо новые объекты или генерировать какие-либо исключения (я думаю, что даже пойманное исключение может быть опасным из-за создания объекта Exception), но другие действия могут быть безопасными. - person supercat; 26.07.2010
comment
@supercat: порядок завершения не гарантируется. Если объект находится в очереди финализации, то его ссылки на другие объекты не существуют для целей определения права этих объектов на сбор. Итак, в то время как Foo содержит ссылку на Bar, это похоже на слабую ссылку. Таким образом, Bar можно собрать до завершения Foo. - person Jim Mischel; 24.01.2012
comment
@JimMischel: Раньше я думал, что все работает именно так, но это не так. После того, как система определила, какие элементы не имеют активных ссылок, все такие элементы, которые имеют финализаторы, добавляются в корневую очередь объектов, для которых необходимо запустить их финализаторы. Это приводит к тому, что все такие объекты и любые объекты, на которые они ссылаются, становятся активными (хотя они больше не могут быть добавлены в очередь финализации, их финализаторы будут запущены как можно скорее, и вполне вероятно, что после того, как финализаторы будут запущены других ссылок не останется). Заметьте, кстати, что есть два типа слабых ссылок: - person supercat; 24.01.2012
comment
@JimMischel: обычные слабые ссылки будут аннулированы, когда объект будет добавлен в очередь finalize-now, но слабые ссылки, отслеживающие воскрешение, остаются действительными до тех пор, пока объект не будет фактически уничтожен. Обратите внимание: поскольку любой класс может создавать ссылки отслеживания воскрешения для любого открытого экземпляра любого другого класса, часто нет гарантии, что экземпляр класса, который был объявлен подходящим для немедленной финализации, не будет воскрешен и его методы будут вызваны в любое время. до или после фактического запуска финализатора. - person supercat; 24.01.2012

Хотя это может вызвать у вас возражения при проверке кода и предупреждения FxCop, в реализации финализатора без IDisposable нет ничего принципиально неправильного. Однако выполнение этого на синглтоне не является надежным способом захвата процесса или удаления домена приложения.

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

person Jeffrey Hantin    schedule 21.01.2009
comment
Если объект действительно не имеет состояния, сделайте его статическим классом. Если это состояние, вопрос, почему это синглтон, я полностью согласен, но мне нравится резкость процитированного. Есть ссылка на это? - person annakata; 21.01.2009
comment
Я придумал это на лету. В течение некоторого времени я выступал против большинства видов использования синглтонов в репозитории портлендских паттернов из-за их влияния на причинно-следственную связь в коде. - person Jeffrey Hantin; 22.01.2009

Помимо применимости синглтона к любой конкретной ситуации,

Я думаю, что в Disposing of Singleton нет ничего плохого. В сочетании с ленивым созданием это просто означает, что вы освобождаете ресурс, если он вам временно не нужен, а затем повторно получаете его по мере необходимости.

person Alexander Abramov    schedule 21.01.2009
comment
Этот фрагмент кода может получить, а Dispose синглтон ничего не говорит о том, может ли кто-нибудь еще его использовать. Если вызывается финализатор, его никто не использует; обратите внимание, что это могло бы произойти только в том случае, если бы статическая / глобальная ссылка на синглтон была WeakReference. В противном случае эта ссылка сама защитит синглтон от завершения. - person supercat; 26.07.2010

Если вы хотите создать синглтон с финализатором, вы, вероятно, должны иметь статическую ссылку на него WeakReference. Это потребует небольшой дополнительной работы, чтобы обеспечить безопасность потоков в аксессоре, но это позволит синглтону собираться мусором, когда его никто не использует (если кто-то впоследствии вызовет метод GetInstance (), они получат новый экземпляр). Если бы использовалась статическая сильная ссылка, она сохраняла бы экземпляр синглтона в живых, даже если бы на него не было других ссылок.

person supercat    schedule 27.09.2010