Синглтон с финализатором, но не IDisposable
-
19-08-2019 - |
Вопрос
Это то, что я понимаю об IDisposable и финализаторах из "CLR через C #", "Effective C #" и других ресурсов:
- IDisposable предназначен для детерминированной очистки управляемых и неуправляемых ресурсов.
- Классы, которые отвечают за неуправляемые ресурсы (напримердескрипторы файлов) должны реализовывать IDisposable и предоставлять финализатор, гарантирующий, что они будут очищены, даже если клиентский код не вызывает Dispose() в экземпляре.
- Классы, которые отвечают только за управляемые ресурсы, никогда не должны реализовывать финализатор.
- Если у вас есть финализатор, вы должны реализовать IDisposable (это позволяет клиентскому коду выполнять правильные действия и вызывать Dispose(), в то время как финализатор предотвращает утечку ресурсов, если они забудут).
Хотя я понимаю доводы в пользу всего вышесказанного и согласен с ними, есть один сценарий, в котором, я думаю, имеет смысл нарушить эти правила:одноэлементный класс, который отвечает за неуправляемые ресурсы (например, предоставление единой точки доступа к определенным файлам).
Я считаю, что всегда неправильно использовать метод Dispose() для синглтона, потому что экземпляр singleton должен жить в течение всего срока службы приложения, и если какой-либо клиентский код вызывает Dispose(), то вы забиты.Однако вам нужен финализатор, чтобы при выгрузке приложения он мог очищать неуправляемые ресурсы.
Таким образом, наличие одноэлементного класса с финализатором, который не реализует IDisposable, кажется мне разумным решением, однако такой тип дизайна противоречит тому, что, как я понимаю, является лучшими практиками.
Является ли это разумным подходом?Если нет, то почему нет и каковы наилучшие альтернативы?
Решение
Если неуправляемый ресурс освобождается только при выходе из приложения, вам даже не нужно беспокоиться о финализаторе, поскольку выгрузка процесса в любом случае должна справиться с этим за вас.
Если у вас несколько доменов приложений и вы хотите иметь дело с выгрузкой домена приложения, это одна из возможных проблем, но, возможно, вам не нужно беспокоиться об этом.
Я поддерживаю тех, кто говорит, что этот дизайн, возможно, не совсем правильный (и его будет сложнее исправить, если впоследствии вы обнаружите, что вам действительно нужны два экземпляра) Создайте объект (или объект-оболочку отложенной загрузки) в вашей точке входа и передайте его через код туда, где это необходимо, давая понять, кто несет ответственность за его предоставление кому, затем вы можете изменить решение использовать только один объект с небольшим эффектом для остальной части кода (который использует то, что ему дают)
Другие советы
Сначала я бы упомянул, что шаблоны объектно-ориентированного проектирования и их последствия не всегда влияют на каждое языковое решение, даже в объектно-ориентированных языках.Вы, конечно, можете найти классические шаблоны проектирования, которые проще реализовать на одном языке (Smalltalk) в отличие от другого (C ++).
При этом я не уверен, что согласен с предположением о том, что одноэлементный экземпляр должен быть удален только в конце приложения.Ничего в описаниях шаблонов проектирования, которые я читал для Синглтон (или Шаблоны проектирования:Элементы многоразового объектно-ориентированного программного обеспечения) упомяните это как свойство этого шаблона.Синглтон должен гарантировать, что в любой момент времени существует только один экземпляр класса;это не означает, что он должен существовать до тех пор, пока существует приложение.
У меня есть ощущение, что на практике многие синглтоны действительно существуют на протяжении большей части срока службы приложения.Однако рассмотрим приложение, которое использует TCP-соединение для связи с сервером, но также может существовать в отключенном режиме.При подключении вы хотели бы, чтобы синглтон сохранял информацию о соединении и его состоянии.После отключения вы можете захотеть сохранить тот же самый синглтон - или вы можете избавиться от синглтона.Хотя некоторые могут возразить, что имеет смысл сохранить синглтон (и я, возможно, даже вхожу в их число), в самом шаблоне проектирования нет ничего, что мешало бы вам избавиться от него - если соединение переделано, синглтон может быть создан снова, поскольку в данный момент времени его экземпляра не существует.
Другими словами, вы можете создавать сценарии, в которых для одиночек логично иметь IDisposable .
Пока ваш финализатор не вызывает методы (такие как Dispose) для любых других управляемых объектов, все должно быть в порядке.Просто помните, что порядок завершения не является детерминированным.То есть, если ваш одноэлементный объект Foo содержит ссылку на object Bar, который требует удаления, вы не сможете надежно записать:
~Foo()
{
Bar.Dispose();
}
Сборщик мусора, возможно, уже собрал Bar.
Рискуя наступить в кучу ОО - слизи (т.е.начиная войну), одной из альтернатив использованию синглтона является использование статического класса.
Хотя это может вызвать у вас проблемы с проверкой кода и предупреждения FxCop, по сути, нет ничего плохого в реализации финализатора без IDisposable.Однако выполнение этого для синглтона не является надежным способом захвата процесса или разрыва домена приложения.
Рискуя предложить субъективный совет по дизайну:Если объект действительно не имеет состояния, сделайте его статическим классом.Если это с сохранением состояния, спросите, почему это Синглтон:вы создаете изменяемую глобальную переменную.Если вы пытаетесь захватить закрытие приложения, разберитесь с этим, когда завершится ваш основной цикл.
Применимость Синглтона к любой конкретной ситуации в стороне,
Я думаю, что нет ничего плохого в том, чтобы избавиться от Синглтона.В сочетании с отложенным созданием экземпляра это просто означает, что вы освобождаете ресурс, если он вам временно не нужен, а затем повторно приобретаете его по мере необходимости.
Если вы хотите создать синглтон с финализатором, вам, вероятно, следует использовать статическую ссылку на него как WeakReference .Это потребует немного дополнительной работы для обеспечения потокобезопасности в средстве доступа, но это позволит собирать мусор из синглтона, когда его никто не использует (если кто-то впоследствии вызовет метод getInstance(), он получит новый экземпляр).Если бы использовалась статическая строгая ссылка, это поддерживало бы работоспособность экземпляра singleton, даже если бы на него не было других ссылок.