Почему существует разделение задач между методами Finalize и Dispose?

Из статьи MSDN о реализации Finalize;

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

Из статьи MSDN о реализации IDisposible.Dispose;

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

Что, даже в контексте каждой (обстоятельной) статьи, кажется довольно двусмысленным определением.

Но вот где я действительно теряю цель Finalize, так это здесь;

Поскольку сборка мусора не является детерминированной, вы не знаете точно, когда сборщик мусора выполняет финализацию. Чтобы освободить ресурсы немедленно, вы также можете реализовать шаблон удаления и интерфейс IDisposable. Реализация IDisposable.Dispose может быть вызвана потребителями вашего класса для освобождения неуправляемых ресурсов, и вы можете использовать метод Finalize для освобождения неуправляемых ресурсов в случае, если метод Dispose не вызывается.

Должен ли я реализовать и то, и другое и принудительно завершить работу в том случае, если приложение-потребитель забудет избавиться от моего объекта?

Мне в новинку так тесно работать с GC ... и я хочу убедиться, что я отбрасываю свои ресурсы должным образом. Однако я не совсем понимаю, почему они существуют и когда их использовать.


person SurelyTheresABetterWay    schedule 29.06.2017    source источник
comment
В первой цитате говорится, что вы должны переопределить Finalize для класса, который использует неуправляемые ресурсы. Поэтому, если у вас нет неуправляемых ресурсов, вы не отменяете Finalize.   -  person rory.ap    schedule 29.06.2017
comment
Если приложение-потребитель забывает распоряжаться вашими ресурсами, это их проблема. Сбор GC может произойти сразу или через 5 минут. Цель Dispose, запущенного в том же потоке, - как можно скорее освободить ресурс (скажем, дескриптор файла), чтобы его мог использовать кто-то другой.   -  person Zein Makki    schedule 29.06.2017
comment
@ user3185569 GC не заботится о неуправляемых ресурсах. Это правда по определению. Определение неуправляемого ресурса - это ресурс, не управляемый сборщиком мусора.   -  person Servy    schedule 29.06.2017
comment
Обычно вы переопределяете финализатор, чтобы он вызывал Dispose. Шаблон по умолчанию для реализации Dispose иллюстрирует это, а также предоставляет информацию в метод о том, запускал ли метод завершения или пользовательский код. Кроме того, метод Dispose является детерминированным, тогда как вы никогда не знаете, когда будет вызван финализатор ...   -  person    schedule 29.06.2017
comment
Какие у вас действительно есть неуправляемые ресурсы, с которыми вам нужно иметь дело?   -  person Servy    schedule 29.06.2017
comment
@Andrei ну, шаблон IDisposable включает реализацию финализатора, поэтому я не уверен, как вы должны об этом забыть.   -  person InBetween    schedule 29.06.2017
comment
@InBetween Одноразовый шаблон, включающий реализацию финализатора , когда это уместно, чего никогда не бывает для всех намерений и целей.   -  person Servy    schedule 29.06.2017
comment
@ Сервия интересная. Итак, что произойдет, если одноразовый объект, содержащий неуправляемые ресурсы, будет скопирован без предварительного удаления? Удаляет ли ОС в конечном итоге утекшие ресурсы или они будут накапливаться до завершения работы приложения?   -  person InBetween    schedule 29.06.2017
comment
@InBetween Это будет радикально зависеть от природы неуправляемого ресурса. Если это память, которая была выделена, она, вероятно, была выделена процессу, поэтому она исчезнет, ​​когда процесс завершится. Если бы это был какой-то настраиваемый ресурс, который не привязан к определенному процессу, он, возможно, никогда не будет очищен. Вы не можете сказать много ничего о неуправляемых ресурсах в целом, потому что они могут быть чем угодно.   -  person Servy    schedule 29.06.2017
comment
@Servy: да, я понимаю это, но если ресурсы выделяются процессом и не освобождаются, у вас есть потенциальная проблема с памятью, которая может привести к сбою вашего приложения. Реализация финализатора может помочь избежать этой проблемы. Конечно, можно предположить, что вы скрываете ошибку в коде, потому что вы должны в первую очередь избавляться от объекта. Я всегда не знаю, какой вариант лучше, поэтому и спрашиваю.   -  person InBetween    schedule 29.06.2017
comment
@InBetween В случае чего-то вроде памяти, выделенной для процесса, вам просто не нужно писать оболочку над этим неуправляемым ресурсом, потому что .NET уже написал эту оболочку. Вам нужно только написать объекты, составляющие IDisposable. Вам понадобится только финализатор для какого-то неуправляемого ресурса, для которого в .NET еще нет оболочки.   -  person Servy    schedule 29.06.2017


Ответы (2)


Реализуйте оба, используя шаблон Dispose. Visual Studio даже возведет за вас строительные леса. Dispose позволяет избавиться от дефицитных ресурсов, как только вы закончите с ними. Finalizer позволяет вам избавиться от них, если по какой-то причине потребитель забывает вызвать ваш метод Dispose, но их экземпляр класса может выйти за пределы области видимости.

person PhillipH    schedule 29.06.2017
comment
Практически всегда неправильно реализовывать финализатор. Если вы реализуете финализатор для любого типа, вы почти наверняка не должны этого делать. - person Servy; 29.06.2017
comment
Извините, это совершенно неверно. В Руководстве по разработке платформы MSDN .net Platform говорится о том, как реализовать шаблон Dipose здесь docs.microsoft.com/en-us/dotnet/standard/design-guidelines/. Если вы реализуете Dispose, вы всегда должны реализовывать Finalize. Если вы реализуете Finalize без dispose или dispose без Finalize - значит, вы делаете что-то не так. - person PhillipH; 29.06.2017
comment
Вы должны прочитать свою ссылку, потому что она противоречит вашим собственным утверждениям. Он описывает, когда уместно и когда нет уместно иметь финализатор, а когда это уместно, а когда нет, для реализации IDisposable. Ситуации, в которых целесообразно реализовать IDisposable, вероятно, будут время от времени возникать у большинства разработчиков C #. Ситуации, в которых уместно добавить финализатор, почти никогда не возникают ни у одного разработчика C #. Если вы пишете финализатор, очень высока вероятность, что вы делаете что-то не так. - person Servy; 29.06.2017
comment
Если вы имеете в виду не финализировать исключительно, тогда да, но если вы реализуете шаблон Dispose, вы всегда реализуете финализатор - даже VS.net делает это таким образом. Вы всегда можете подавить финализатор в Dispose (в любом случае лучше всего). Если вы реализуете Dispose без Finalizer, вы не очень хорошо защищаете свои ресурсы, не относящиеся к среде CLR - рассмотрите простой объект Bitmap, встроенный в ваш класс; вы можете Dispose, когда ваш класс получает Disposed, но если ваш вызывающий объект забывает Dispose () / using {}, ваш Finalizer - это все, что стоит на пути более поздней ошибки GDI. - person PhillipH; 29.06.2017
comment
Нет, это неправильно. Вы не избавляетесь от Bitmap в финализаторе. Вы никогда не прикасаетесь к другим управляемым объектам в Finzalizer. Вы используете финализатор только для прямого взаимодействия (а не косвенно через другие управляемые объекты) с неуправляемым ресурсом. Доступ к другому управляемому объекту в финализаторе небезопасно. В вашем примере, поскольку у объекта есть неуправляемые ресурсы, которые нужно очистить напрямую, ему нечего делать в его финализаторе, он должен просто избавиться от растрового изображения в Dispose и не иметь финализатора. - person Servy; 29.06.2017
comment
И, конечно же, это типичный пример. Вы очень редко будете иметь фактический неуправляемый ресурс для непосредственной очистки, у вас достаточно часто будет какой-то другой IDisposable объект, который вы создаете. Когда у вашего объекта есть другие IDisposable объекты, он должен реализовывать IDisposable, удалять их при удалении и вообще не иметь финализатора. - person Servy; 29.06.2017
comment
Не превращая это в бесконечную дискуссию; Я согласен с вами, пока вы не скажете избавиться от них, когда он будет удален, и не иметь финализатора вообще - суть шаблона Dispose состоит в том, чтобы убедиться, что, даже если ваш Dispose не вызывается, вы не утекаете неуправляемые ресурсы, даже если они сами составлены IDisposable. Вам не нужно вызывать процедуры удаления из вашего финализатора, но он существует, чтобы убедиться, что ваш код безопасен и не утечки ресурсов. - person PhillipH; 30.06.2017
comment
Нет, вам не только не нужно удалять управляемые объекты в финализаторе, но и небезопасно вызывать Dispose метод другого объекта из финализатора. Это неприемлемо, когда-либо. Вы вполне можете сломать код, сделав это. Финализатор предназначен для удаления непосредственно составленных неуправляемых ресурсов, а не управляемых объектов, которые сами имеют неуправляемые ресурсы. Если вы составляете управляемый объект, который сам по себе является одноразовым, то это его ответственность за удаление любых непосредственно составленных неуправляемых ресурсов. - person Servy; 30.06.2017

Просто реализуйте такой шаблон IDisposable, если у вас есть неуправляемые ресурсы:

public class ApiClient : IDisposable 
{  
    private readonly HttpClient http = new HttpClient();

    public void Dispose()
    {  
        Dispose(true);  
        GC.SuppressFinalize(this);  
    }  

    protected virtual void Dispose(bool disposing)
    {  
        if (disposing)
        {
            http?.Dispose();  
        }  
    }  
}

Подробнее об одноразовом шаблоне здесь. Я никогда не забываю использовать оператор using для моих одноразовых объектов, чтобы убедиться, что вызывается метод удаления:

using (var api = new ApiClient()) 
{
    // use api
}
person Andrei    schedule 29.06.2017
comment
Нет причин подавлять финализатор объекта, у которого нет финализатора. Также нет причин иметь параметр, указывающий, удаляется ли объект в финализаторе, если у вас нет финализатора. - person Servy; 29.06.2017
comment
Теперь у вас есть финализатор для объекта, которому на самом деле нечего делать. Вы не должны добавлять финализаторы к объектам, которым на самом деле нечего убирать в их финализаторах. - person Servy; 29.06.2017
comment
@Servy Мне бы очень хотелось увидеть ваш правильный ответ - person Andrei; 29.06.2017
comment
Посмотрите на дубликат, у него есть множество хороших ответов по этому поводу. - person Servy; 29.06.2017