Finalizar vs Eliminar


Respuestas:


120

Otros ya han cubierto la diferencia entre Disposey Finalize(por cierto, el Finalizemétodo todavía se llama destructor en la especificación del lenguaje), por lo que solo agregaré un poco sobre los escenarios en los que el Finalizemétodo es útil.

Algunos tipos encapsulan los recursos desechables de una manera fácil de usar y los eliminan en una sola acción. El uso general es a menudo así: abrir, leer o escribir, cerrar (desechar). Encaja muy bien con la usingconstrucción.

Otros son un poco más difíciles. WaitEventHandlespara instancias no se usan así, ya que se usan para señalar de un hilo a otro. La pregunta entonces se convierte en quién debería recurrir Disposea estos. Como tipos de protección como estos, se implementa un Finalizemétodo que garantiza que los recursos se eliminen cuando la aplicación ya no haga referencia a la instancia.


6060
No pude entender esta respuesta aprobada. Todavía quiero saber lo diferente. ¿Lo que es?
Ismael

22
@Ismael: La mayor situación en la que Finalizepuede justificarse es cuando hay una serie de objetos que están interesados ​​en mantener vivo un recurso, pero no hay ningún medio por el cual un objeto que deja de estar interesado en el recurso pueda averiguar si es el el último. En tal caso, Finalizegeneralmente solo se disparará cuando nadie esté interesado en el objeto. El tiempo flojo de Finalizees horrible para recursos no fungibles como archivos y bloqueos, pero puede estar bien para recursos fungibles.
supercat

13
+1 a supercat para una gran palabra nueva (para mí). El contexto lo dejó bastante claro, pero por si acaso para el resto de nosotros, esto es lo que dice Wikipedia: "La fungibilidad es la propiedad de un bien o una mercancía cuyas unidades individuales son capaces de sustitución mutua, como el petróleo crudo dulce, las acciones en una empresa, bonos, metales preciosos o monedas ".
Jon Coombs

55
@JonCoombs: Eso es bastante correcto, aunque puede valer la pena señalar que el término "recurso fungible" se aplica a cosas que son libremente sustituibles hasta que se adquieren y se vuelven libremente sustituibles nuevamente después de su liberación o abandono . Si el sistema tiene un grupo de objetos de bloqueo y el código adquiere uno que asocia con alguna entidad, entonces, mientras alguien sostenga que una referencia a ese bloqueo con el propósito de asociarlo con esa entidad , ese bloqueo no puede ser sustituido por cualquier otro. Sin embargo, si todo el código que se preocupa por la entidad protegida abandona la cerradura, ...
supercat

... entonces volvería a ser libremente sustituible hasta el momento en que esté asociado con alguna otra entidad.
supercat

135

Se llama al método finalizador cuando su objeto es basura recolectada y no tiene garantía de cuándo ocurrirá esto (puede forzarlo, pero perjudicará el rendimiento).

El Disposemétodo, por otro lado, debe ser llamado por el código que creó su clase para que pueda limpiar y liberar los recursos que haya adquirido (datos no administrados, conexiones de bases de datos, identificadores de archivos, etc.) en el momento en que se realiza el código tu objeto

La práctica estándar es implementar IDisposabley Disposepara que pueda usar su objeto en una usingdeclaración. Tales como using(var foo = new MyObject()) { }. Y en su finalizador, llama Dispose, en caso de que el código de llamada olvidó deshacerse de usted.


17
Debe tener un poco de cuidado al llamar a Dispose desde su implementación de Finalize: Dispose también puede eliminar los recursos administrados, que no desea tocar desde su finalizador, ya que pueden haber sido finalizados ellos mismos.
itowlson

66
@itowlson: Verificar nulo combinado con la suposición de que los objetos se pueden eliminar dos veces (con la segunda llamada sin hacer nada) debería ser lo suficientemente bueno.
Samuel

77
El patrón IDisposal estándar y la implementación oculta de un Dispose (bool) para manejar la eliminación opcional de componentes administrados parece satisfacer ese problema.
Brody

Parece que no hay razón para implementar el destructor (el método ~ MyClass ()) y más bien siempre implementar y llamar al método Dispose (). ¿O estoy equivocado? ¿Podría alguien darme un ejemplo cuando ambos deben implementarse?
dpelisek

66

Finalizar es el método de detención, llamado por el recolector de basura cuando reclama un objeto. Desechar es el método de "limpieza determinista", llamado por las aplicaciones para liberar valiosos recursos nativos (identificadores de ventana, conexiones de bases de datos, etc.) cuando ya no se necesitan, en lugar de dejarlos retenidos indefinidamente hasta que el GC llegue al objeto.

Como usuario de un objeto, siempre usa Dispose. Finalizar es para el GC.

Como implementador de una clase, si mantiene los recursos administrados que deben eliminarse, implementa Dispose. Si tiene recursos nativos, implementa Dispose y Finalize, y ambos llaman a un método común que libera los recursos nativos. Estos modismos suelen combinarse mediante un método privado Dispose (bool disposing), que Dispose llama con verdadero y Finaliza llamadas con falso. Este método siempre libera recursos nativos, luego verifica el parámetro de eliminación y, si es cierto, elimina los recursos administrados y llama a GC.SuppressFinalize.



2
El patrón original recomendado para las clases que contenían una combinación de recursos autolimpiables ("gestionados") y no autolimpiables ("no gestionados") ha sido obsoleto durante mucho tiempo. Un mejor patrón es envolver por separado cada recurso no administrado en su propio objeto administrado que no contenga ninguna referencia fuerte a nada que no sea necesario para su limpieza. Todo lo que un objeto finalizable tenga una referencia fuerte directa o indirecta tendrá una vida útil del GC extendida. Encapsular las cosas que se necesitan para la limpieza le permitirá a uno evitar extender la vida útil del GC de las cosas que no lo son.
supercat

2
@JCoombs: Disposees bueno, e implementarlo correctamente es generalmente fácil. Finalizees malo, e implementarlo correctamente es generalmente difícil. Entre otras cosas, debido a que el GC se asegurará de que la identidad de ningún objeto sea "reciclada" mientras exista alguna referencia a ese objeto, es fácil limpiar un montón de Disposableobjetos, algunos de los cuales pueden haber sido limpiados. No hay problema; Cualquier referencia a un objeto sobre el que Disposeya se ha llamado seguirá siendo una referencia a un objeto sobre el que Disposeya se ha llamado.
supercat

2
@JCoombs: los recursos no administrados, por el contrario, generalmente no tienen esa garantía. Si el objeto Fredposee el identificador de archivo # 42 y lo cierra, el sistema puede adjuntar ese mismo número a algún identificador de archivo que se le da a otra entidad. En ese caso, el identificador de archivo # 42 no se referiría al archivo cerrado de Fred, sino al archivo que estaba en uso activo por esa otra entidad; para Fredtratar de cerrar la manija # 42 de nuevo sería desastroso. Intentar hacer un seguimiento 100% confiable de si un objeto no administrado ha sido liberado aún es viable. Intentar hacer un seguimiento de varios objetos es mucho más difícil.
supercat

2
@JCoombs: si cada recurso no administrado se coloca en su propio objeto contenedor que no hace nada más que controlar su vida útil, entonces el código externo que no sabe si el recurso ha sido liberado, pero sabe que debería serlo si aún no lo ha sido , puede pedir con seguridad al objeto contenedor que lo libere; el objeto contenedor sabrá si lo ha hecho y puede llevar a cabo o ignorar la solicitud. El hecho de que el GC garantice que una referencia al envoltorio siempre será una referencia válida al envoltorio es una garantía muy útil .
supercat

43

Finalizar

  • Los finalizadores siempre deben ser protected, no publico privatepara que el método no se pueda invocar directamente desde el código de la aplicación y, al mismo tiempo, se puede llamar al base.Finalizemétodo
  • Los finalizadores deben liberar solo recursos no administrados.
  • El marco no garantiza que un finalizador se ejecute en absoluto en una instancia determinada.
  • Nunca asigne memoria en finalizadores ni llame a métodos virtuales desde finalizadores.
  • Evite la sincronización y genere excepciones no controladas en los finalizadores.
  • El orden de ejecución de los finalizadores no es determinista; en otras palabras, no puede confiar en que otro objeto aún esté disponible dentro de su finalizador.
  • No defina finalizadores en tipos de valor.
  • No crees destructores vacíos. En otras palabras, nunca debe definir explícitamente un destructor a menos que su clase necesite limpiar recursos no administrados y si define uno, debería hacer algo de trabajo. Si, más tarde, ya no necesita limpiar recursos no administrados en el destructor, elimínelos por completo.

Disponer

  • Implemente IDisposableen cada tipo que tenga un finalizador
  • Asegúrese de que un objeto quede inutilizable después de realizar una llamada al Disposemétodo. En otras palabras, evite usar un objeto después de que el Disposemétodo haya sido invocado.
  • Llame Disposea todos los IDisposabletipos una vez que haya terminado con ellos
  • Permitir Disposeser llamado varias veces sin generar errores.
  • Suprima las llamadas posteriores al finalizador desde el Disposemétodo utilizando el GC.SuppressFinalizemétodo
  • Evite crear tipos de valor desechables
  • Evite lanzar excepciones desde dentro de los Disposemétodos

Eliminar / Patrón finalizado

  • Microsoft recomienda que implemente ambos Disposey Finalizecuando trabaje con recursos no administrados. La Finalizeimplementación se ejecutaría y los recursos aún se liberarían cuando el objeto se recolecte basura, incluso si un desarrollador no llamara al Disposemétodo explícitamente.
  • Limpie los recursos no administrados en el Finalizemétodo, así como en el Disposemétodo. Además, llame al Disposemétodo para cualquier objeto .NET que tenga como componentes dentro de esa clase (que tenga recursos no administrados como miembro) del Disposemétodo.

17
Leí esta misma respuesta en todas partes y todavía no puedo entender cuál es el propósito de cada uno. Solo leo reglas tras reglas, nada más.
Ismael

@Ismael: y también el autor no agrega nada excepto copiar y pegar texto de MSDN.
Tarik

@tarik ya lo aprendí. Tuve la concepción de "promesa" esa vez que pregunté esto.
Ismael

31

Finalizar recibe una llamada del GC cuando este objeto ya no está en uso.

Dispose es solo un método normal al que el usuario de esta clase puede llamar para liberar cualquier recurso.

Si el usuario olvidó llamar a Dispose y si la clase tiene Finalize implementado, GC se asegurará de que se llame.


3
La respuesta más limpia de la historia
dariogriffo

19

Hay algunas claves del libro MCSD Certification Toolkit (examen 70-483) pág. 193:

destructor ≈ (es casi igual a)base.Finalize() , el destructor se convierte en una versión de anulación del método Finalize que ejecuta el código del destructor y luego llama al método Finalize de la clase base. Entonces es totalmente no determinista, no se puede saber cuándo se llamará porque depende de GC.

Si una clase no contiene recursos gestionados ni recursos no gestionados , no debería implementar IDisposableni tener un destructor.

Si la clase solo ha administrado recursos , debería implementarse IDisposablepero no debería tener un destructor. (Cuando se ejecuta el destructor, no puede estar seguro de que todavía existan objetos administrados, por lo que no puede llamar a sus Dispose()métodos de todos modos).

Si la clase solo tiene recursos no administrados , debe implementarse IDisposabley necesita un destructor en caso de que el programa no llame Dispose().

Dispose()El método debe ser seguro para ejecutarse más de una vez. Puede lograrlo utilizando una variable para realizar un seguimiento de si se ha ejecutado anteriormente.

Dispose()debería liberar recursos gestionados y no gestionados .

El destructor solo debe liberar recursos no administrados . Cuando se ejecuta el destructor, no puede estar seguro de que todavía existan objetos administrados, por lo que no puede llamar a sus métodos Dispose de todos modos. Esto se obtiene utilizando el protected void Dispose(bool disposing)patrón canónico , donde solo los recursos administrados se liberan (se eliminan) cuando disposing == true.

Después de liberar recursos, Dispose()debe llamarGC.SuppressFinalize para que el objeto pueda omitir la cola de finalización.

Un ejemplo de una implementación para una clase con recursos no administrados y administrados:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

2
Esta es una buena respuesta! Pero creo que esto está mal: "el destructor debería llamar a GC.SuppressFinalize". En cambio, ¿no debería el método público Dispose () llamar a GC.SuppressFinalize? Ver: docs.microsoft.com/en-us/dotnet/api/… Llamar a este método evita que el recolector de basura llame a Object.Finalize (que es destruido por el destructor).
Ewa

7

El 99% de las veces, tampoco debería preocuparse. :) Pero, si sus objetos contienen referencias a recursos no administrados (identificadores de ventana, identificadores de archivos, por ejemplo), debe proporcionar una forma para que su objeto administrado libere esos recursos. Finalizar da control implícito sobre la liberación de recursos. Es llamado por el recolector de basura. Dispose es una forma de dar un control explícito sobre una liberación de recursos y se puede llamar directamente.

Hay mucho más que aprender sobre el tema de la recolección de basura , pero eso es un comienzo.


55
Estoy bastante seguro de más de 1% de las aplicaciones C # usar bases de datos: en la que tiene que preocuparse por cosas IDisposable SQL.
Samuel

1
Además, debe implementar IDisposable si encapsula IDisposables. Lo que probablemente cubre el otro 1%.
Darren Clark el

@Samuel: No veo qué bases de datos tiene que ver con eso. Si está hablando de cerrar conexiones, está bien, pero es un asunto diferente. No tiene que disponer objetos para cerrar las conexiones de manera oportuna.
JP Alioto

1
@JP: Pero el patrón Usando (...) hace que sea mucho más fácil de manejar.
Brody

2
De acuerdo, pero ese es exactamente el punto. El patrón de uso oculta la llamada a Eliminar para usted.
JP Alioto

6

El finalizador es para la limpieza implícita: debe usar esto siempre que una clase administre recursos que absolutamente deben limpiarse, ya que de lo contrario se perderían identificadores / memoria, etc.

Implementar correctamente un finalizador es notoriamente difícil y debe evitarse siempre que sea posible: la SafeHandleclase (disponible en .Net v2.0 y superior) ahora significa que muy raramente (si alguna vez) necesita implementar un finalizador más.

La IDisposableinterfaz es para la limpieza explícita y se usa mucho más comúnmente; debe usarla para permitir a los usuarios liberar o limpiar recursos explícitamente cada vez que hayan terminado de usar un objeto.

Tenga en cuenta que si tiene un finalizador, también debe implementar la IDisposableinterfaz para permitir que los usuarios liberen explícitamente esos recursos antes de lo que serían si el objeto fuera basura recolectada.

Consulte la Actualización de DG: Desechar, Finalizar y Gestión de Recursos para lo que considero el mejor y más completo conjunto de recomendaciones sobre finalizadores y IDisposable.


3

El resumen es -

  • Usted escribe un finalizador para su clase si tiene referencia a recursos no administrados y desea asegurarse de que esos recursos no administrados se liberen cuando una instancia de esa clase se recolecte basura automáticamente . Tenga en cuenta que no puede llamar al Finalizador de un objeto explícitamente: el recolector de basura lo llama automáticamente cuando lo considere necesario.
  • Por otro lado, implementa la interfaz IDisposable (y, en consecuencia, define el método Dispose () como resultado de su clase) cuando su clase hace referencia a recursos no administrados, pero no desea esperar a que el recolector de basura se inicie (que puede ser en cualquier momento, sin el control del programador) y desea liberar esos recursos tan pronto como termine. Por lo tanto, puede liberar explícitamente recursos no administrados llamando al método Dispose () de un objeto.

Además, otra diferencia es que, en la implementación Dispose (), también debe liberar los recursos administrados , mientras que eso no debe hacerse en el Finalizador. Esto se debe a que es muy probable que los recursos administrados a los que hace referencia el objeto ya se hayan limpiado antes de que esté listo para finalizar.

Para una clase que usa recursos no administrados, la mejor práctica es definir ambos, el método Dispose () y el Finalizador, para usarlos como respaldo en caso de que un desarrollador olvide deshacerse explícitamente del objeto. Ambos pueden usar un método compartido para limpiar recursos administrados y no administrados:

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

El mejor ejemplo que conozco.

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

2

Diferencia entre los métodos Finalizar y Eliminar en C #.

GC llama al método finalize para reclamar los recursos no administrados (como el funcionamiento del archivo, la API de Windows, la conexión de red, la conexión de la base de datos), pero el tiempo no está fijo cuando GC lo llamaría. GC lo llama implícitamente, significa que no tenemos un control de bajo nivel sobre él.

Método de eliminación: tenemos un control de bajo nivel sobre él como lo llamamos desde el código. podemos reclamar los recursos no administrados siempre que consideremos que no son utilizables. Podemos lograr esto implementando el patrón de eliminación.


1

Las instancias de clase a menudo encapsulan el control sobre los recursos que no son administrados por el tiempo de ejecución, como los identificadores de ventana (HWND), las conexiones de la base de datos, etc. Por lo tanto, debe proporcionar una forma explícita e implícita de liberar esos recursos. Proporcione control implícito mediante la implementación del Método de finalización protegido en un objeto (sintaxis de destructor en C # y Extensiones administradas para C ++). El recolector de basura llama a este método en algún momento después de que ya no hay referencias válidas para el objeto. En algunos casos, es posible que desee proporcionar a los programadores que usan un objeto la capacidad de liberar explícitamente estos recursos externos antes de que el recolector de basura libere el objeto. Si un recurso externo es escaso o costoso, se puede lograr un mejor rendimiento si el programador libera recursos explícitamente cuando ya no se utilizan. Para proporcionar un control explícito, implemente el método Dispose proporcionado por la interfaz IDisposable. El consumidor del objeto debe llamar a este método cuando termine de usar el objeto. Se puede llamar a Dispose incluso si otras referencias al objeto están vivas.

Tenga en cuenta que incluso cuando proporciona un control explícito a través de Dispose, debe proporcionar una limpieza implícita utilizando el método Finalize. Finalizar proporciona una copia de seguridad para evitar que los recursos se filtren permanentemente si el programador no puede llamar a Eliminar.


1

La principal diferencia entre Dispose y Finalize es que:

Disposegeneralmente se llama por su código. Los recursos se liberan instantáneamente cuando lo llamas. La gente se olvida de llamar al método, por lo using() {}que se inventa una declaración. Cuando su programa termine la ejecución del código dentro del {}, llamará al Disposemétodo automáticamente.

Finalizeno es llamado por su código. Está destinado a ser llamado por el recolector de basura (GC). Eso significa que el recurso podría liberarse en cualquier momento en el futuro cuando GC decida hacerlo. Cuando GC haga su trabajo, pasará por muchos métodos de Finalización. Si tiene una lógica pesada en esto, hará que el proceso sea lento. Puede causar problemas de rendimiento para su programa. Así que ten cuidado con lo que pones allí.

Yo personalmente escribiría la mayor parte de la lógica de destrucción en Dispose. Con suerte, esto aclara la confusión.


-1

Como sabemos, disponer y finalizar ambos se utilizan para liberar recursos no administrados ... pero la diferencia es que finalizar utiliza dos ciclos para liberar los recursos, mientras que la disposición utiliza un ciclo.


Dispose libera el recurso de inmediato. Finalizar puede o no liberar el recurso con algún grado de oportunidad.
supercat

1
Ah, probablemente quiere decir que "el GC debe detectar un objeto finalizable dos veces antes de recuperar su memoria", lea más aquí: ericlippert.com/2015/05/18/…
aeroson

-4

Para responder en la primera parte, debe proporcionar ejemplos en los que las personas usan un enfoque diferente para el mismo objeto de clase. De lo contrario, es difícil (o incluso extraño) responder.

En cuanto a la segunda pregunta, mejor lea primero este uso adecuado de la interfaz IDisposable que afirma que

¡Es tu elección! Pero elija Eliminar.

En otras palabras: el GC solo conoce el finalizador (si existe. También conocido como destructor para Microsoft). Un buen código intentará limpiar desde ambos (finalizador y Eliminar).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.