¿Cuándo debo usar GC.SuppressFinalize ()?

287

En .NET, ¿en qué circunstancias debo usar GC.SuppressFinalize()?

¿Qué ventaja (s) me da el uso de este método?

GEOCHET
fuente
He visto algunas preguntas sobre finalizadores e IDisposable, stackoverflow también debería tener algo sobre GC.SupressFinalize y referencias débiles
Sam Saffron
No creo que las referencias débiles hagan mucho con respecto a los finalizadores, tal vez debería publicar una pregunta más directa sobre ellos.
Michael Burr
Yerp, tenía la intención de publicar una pregunta por separado sobre las referencias débiles, todo esto puede unirse cuando construyes grupos de objetos. También debería hacer una pregunta sobre la recuperación de objetos ala ReRegisterForFinalize
Sam Saffron

Respuestas:

296

SuppressFinalizesolo debe ser llamado por una clase que tenga un finalizador. Informa al recolector de basura (GC) que el thisobjeto se limpió por completo.

El IDisposablepatrón recomendado cuando tienes un finalizador es:

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);
    }
}

Normalmente, el CLR mantiene pestañas en los objetos con un finalizador cuando se crean (haciéndolos más caros de crear). SuppressFinalizele dice al GC que el objeto se limpió correctamente y no necesita ir a la cola del finalizador. Parece un destructor de C ++, pero no actúa como uno.

La SuppressFinalizeoptimización no es trivial, ya que sus objetos pueden vivir mucho tiempo esperando en la cola del finalizador. No caigas en la tentación de recurrir SuppressFinalizea otros objetos. Ese es un defecto grave esperando a suceder.

Las pautas de diseño nos informan que un finalizador no es necesario si su objeto se implementa IDisposable, pero si tiene un finalizador, debe implementarlo IDisposablepara permitir la limpieza determinista de su clase.

La mayoría de las veces deberías poder escaparte IDisposablepara limpiar recursos. Solo debe necesitar un finalizador cuando su objeto retiene recursos no administrados y debe garantizar que esos recursos se limpien.

Nota: A veces los codificadores agregarán un finalizador para depurar compilaciones de sus propias IDisposableclases para probar que el código ha dispuesto su IDisposableobjeto correctamente.

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

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif
Robert Paulson
fuente
1
En el primer fragmento de código, solo publico cómo se ve el patrón recomendado de IDisposable + finalizador. El código de depuración es bueno, pero puede distraer. .. Solo puedo recomendar evitar finalizadores, excepto para las clases que tienen recursos no administrados. Escribir código finalizador seguro no es trivial.
Robert Paulson
1
Hola, ¿por qué necesitamos llamar a dispose con false como parámetro del finalizador? ¿Qué pasa si nunca se llamó a la eliminación y luego no se eliminará? ¿Qué pasa si solo verificamos si el objeto ha sido eliminado o no y hacemos la limpieza real?
Dreamer
3
@Dreamer: depende de su implementación. En general, desea saber si el finalizador llama a Dispose versus la implementación IDisposable.Dispose (). Si se llama desde el finalizador, debe asumir que las referencias privadas ya no son válidas y que realmente no puede hacer mucho. Sin embargo, si se llama desde IDisposable.Dispose (), sabrá que las referencias siguen siendo válidas.
Robert Paulson
32
Si la implementación de la clase IDisposableno es sealed, entonces debe incluir la llamada a GC.SuppressFinalize(this) incluso si no incluye un finalizador definido por el usuario . Esto es necesario para garantizar una semántica adecuada para los tipos derivados que agregan un finalizador definido por el usuario pero solo anulan el Dispose(bool)método protegido .
Sam Harwell
1
No ser sealedcomo lo menciona @SamHarwell es importante, para las clases derivadas. CodeAnalysis da como resultado ca1816 + ca1063 cuando la clase no está sellada, pero las clases selladas están bien sin ella SuppressFinalize.
guiones el
38

SupressFinalizele dice al sistema que cualquier trabajo que se haya realizado en el finalizador ya se ha realizado, por lo que no es necesario llamar al finalizador. De los documentos de .NET:

Los objetos que implementan la interfaz IDisposable pueden llamar a este método desde el método IDisposable.Dispose para evitar que el recolector de basura llame a Object.Finalize en un objeto que no lo requiere.

En general, la mayoría de los Dispose()métodos deberían poder llamar GC.SupressFinalize(), ya que deberían limpiar todo lo que se limpiaría en el finalizador.

SupressFinalizees solo algo que proporciona una optimización que permite al sistema no molestarse en poner el objeto en el subproceso finalizador. Un Dispose()finalizador / escrito correctamente debería funcionar correctamente con o sin una llamada a GC.SupressFinalize().

Michael Burr
fuente
2

Ese método debe invocarse en el Disposemétodo de los objetos que implementan IDisposable, de esta manera el GC no llamaría al finalizador otra vez si alguien llama al Disposemétodo.

Ver: Método GC.SuppressFinalize (Object) - Microsoft Docs

alberteína
fuente
9
Creo que "Debe" está mal, ni siquiera "debería". Es solo que en algunos escenarios, puede eliminar la sobrecarga de poner en cola / finalizar el objeto.
Básico
1
Dispose(true);
GC.SuppressFinalize(this);

Si el objeto tiene finalizador, .net coloca una referencia en la cola de finalización.

Como tenemos una llamada Dispose(ture), borra el objeto, por lo que no necesitamos la cola de finalización para hacer este trabajo.

Entonces llame a GC.SuppressFinalize(this)eliminar referencia en la cola de finalización.

Max CHien
fuente
0

Si una clase, o cualquier derivado de él, podrían sostener la última referencia en vivo a un objeto con un finalizador, a continuación, ya sea GC.SuppressFinalize(this)o GC.KeepAlive(this)debería ser llamado en el objeto después de cualquier operación que pueda estar afectado adversamente por que finalizador, asegurando así que el won finalizador No ejecute hasta después de que se complete esa operación.

El costo de GC.KeepAlive()y GC.SuppressFinalize(this)es esencialmente el mismo en cualquier clase que no tenga un finalizador, y las clases que sí tienen finalizadores generalmente deberían llamar GC.SuppressFinalize(this), por lo que usar la última función como el último paso Dispose()puede no ser siempre necesario, pero no lo será. estar equivocado.

Super gato
fuente