Когда я должен использовать GC.SuppressFinalize ()?


288

В .NET, при каких обстоятельствах я должен использовать GC.SuppressFinalize()?

Какие преимущества дает мне этот метод?


Я видел несколько вопросов о финализаторах и IDisposable, у stackoverflow также должно быть что-то о GC.SupressFinalize и слабых ссылках
Сэм Саффрон,

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

Да, я собирался опубликовать отдельный вопрос о слабых ссылках, все это может быть связано при создании пулов объектов. Также я должен задать вопрос о возрождении объекта аля ReRegisterForFinalize
Сэм Саффрон

Ответы:


297

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

Рекомендуемый IDisposableшаблон, когда у вас есть финализатор:

public class MyClass : IDisposable
{
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // called via myClass.Dispose(). 
                // OK to use any private object references
            }
            // Release unmanaged resources.
            // Set large fields to null.                
            disposed = true;
        }
    }

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

    ~MyClass() // the finalizer
    {
        Dispose(false);
    }
}

Обычно CLR хранит вкладки на объектах с помощью финализатора, когда они создаются (что делает их более дорогими при создании). SuppressFinalizeсообщает GC, что объект был очищен должным образом и не должен идти в очередь финализатора. Это похоже на деструктор C ++, но не действует ни на что подобное.

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

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

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

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

public void Dispose() // Implement IDisposable
{
    Dispose(true);
#if DEBUG
    GC.SuppressFinalize(this);
#endif
}

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif

1
В первом фрагменте кода я просто публикую информацию о том, как выглядит рекомендуемый шаблон IDisposable + финализатор. Отладка кода это хорошо, но это может отвлекать. .. Я могу только рекомендовать избегать финализаторов, за исключением классов, которые имеют неуправляемые ресурсы. Написание безопасного кода финализатора нетривиально.
Роберт Полсон

1
Привет, Почему нам нужно вызвать dispose с false в качестве параметра из финализатора? Что делать, если распоряжение никогда не вызывали, а затем оно не будет распоряжаться? Что если мы просто проверим, был ли удален объект или нет, и проведем реальную очистку.
Мечтатель

3
@Dreamer - это зависит от вашей реализации. В общем, вы хотите знать, вызывается ли Dispose финализатором по сравнению с реализацией IDisposable.Dispose (). Если вызывается из финализатора, вы должны предположить, что личные ссылки больше не действительны, и вы действительно ничего не можете сделать. Однако, если вызывается из IDisposable.Dispose (), вы знаете, что ссылки по-прежнему действительны.
Роберт Полсон

32
Если реализации класса IDisposableнет sealed, тогда он должен включать вызов, GC.SuppressFinalize(this) даже если он не включает определяемый пользователем финализатор . Это необходимо для обеспечения правильной семантики для производных типов, которые добавляют определяемый пользователем финализатор, но только переопределяют защищенный Dispose(bool)метод.
Сэм Харуэлл

1
sealedВажно не быть упомянутым @SamHarwell для производных классов. CodeAnalysis приводит к ca1816 + ca1063, когда класс не запечатан, но запечатанные классы хороши без него SuppressFinalize.
Дашесы

38

SupressFinalizeсообщает системе, что любая работа, выполненная в финализаторе, уже выполнена, поэтому финализатор вызывать не нужно. Из документов .NET:

Объекты, которые реализуют интерфейс IDisposable, могут вызывать этот метод из метода IDisposable.Dispose, чтобы сборщик мусора не вызывал Object.Finalize для объекта, который ему не требуется.

В общем, любой Dispose()метод должен вызывать большинство GC.SupressFinalize(), потому что он должен очищать все, что будет очищено в финализаторе.

SupressFinalizeэто просто то, что обеспечивает оптимизацию, которая позволяет системе не беспокоить очередь объекта в поток финализатора. Правильно написанный Dispose()/ финализатор должен работать правильно с вызовом или без него GC.SupressFinalize().


2

Этот метод должен вызываться для Disposeметода объектов, который реализует IDisposable, таким образом, GC не будет вызывать финализатор в другой раз, если кто-то вызовет Disposeметод.

См .: метод GC.SuppressFinalize (Object) - Документы Microsoft


9
Я думаю, что «Must» - это неправильно, даже не «должен» - просто в некоторых сценариях вы можете исключить накладные расходы на постановку в очередь / завершение объекта.
Базовый

1
Dispose(true);
GC.SuppressFinalize(this);

Если объект имеет финализатор, .net помещает ссылку в очередь финализации.

Поскольку у нас есть вызов Dispose(ture), он очищает объект, поэтому нам не нужна очередь завершения для выполнения этой работы.

Поэтому вызовите GC.SuppressFinalize(this)удаление ссылки в очереди завершения.


0

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

Стоимость GC.KeepAlive()и GC.SuppressFinalize(this), по сути, одинакова в любом классе, в котором нет финализатора, и классы, у которых действительно есть финализаторы, обычно должны вызываться GC.SuppressFinalize(this), поэтому использование последней функции в качестве последнего шага Dispose()может не всегда быть необходимым, но это не будет быть неправым.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.