Uso adecuado de la interfaz IDisposable


1659

Sé por leer la documentación de Microsoft que el uso "primario" de la IDisposableinterfaz es limpiar recursos no administrados.

Para mí, "no administrado" significa cosas como conexiones de bases de datos, sockets, manejadores de ventanas, etc. Pero, he visto código donde el Dispose()método se implementa para liberar recursos administrados , lo que me parece redundante, ya que el recolector de basura debería ocuparse de eso para ti.

Por ejemplo:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Mi pregunta es, ¿esto hace que el recolector de basura libere la memoria utilizada MyCollectionmás rápido de lo normal?

editar : Hasta ahora, la gente ha publicado algunos buenos ejemplos de uso de IDisposable para limpiar recursos no administrados, como conexiones de bases de datos y mapas de bits. Pero suponga que _theListen el código anterior contiene un millón de cadenas, y desea liberar esa memoria ahora , en lugar de esperar al recolector de basura. ¿Lo lograría el código anterior?


34
Me gusta la respuesta aceptada porque te dice el 'patrón' correcto de usar IDisposable, pero como dijo el OP en su edición, no responde a su pregunta prevista. IDisposable no 'llama' al GC, solo 'marca' un objeto como destruible. Pero, ¿cuál es la forma real de liberar memoria 'en este momento' en lugar de esperar a que se active GC? Creo que esta pregunta merece más discusión.
Punit Vora

40
IDisposableNo marca nada. El Disposemétodo hace lo que tiene que hacer para limpiar los recursos utilizados por la instancia. Esto no tiene nada que ver con GC.
John Saunders, el

44
@Juan. Yo entiendo IDisposable. Y es por eso que dije que la respuesta aceptada no responde la pregunta prevista del OP (y la edición de seguimiento) sobre si IDisposable ayudará a <i> liberar memoria </i>. Como IDisposableno tiene nada que ver con liberar memoria, solo recursos, entonces, como dijiste, no es necesario configurar las referencias administradas como nulas, que es lo que OP estaba haciendo en su ejemplo. Entonces, la respuesta correcta a su pregunta es "No, no ayuda a liberar memoria más rápido. De hecho, no ayuda a liberar memoria en absoluto, solo recursos". Pero de todos modos, gracias por tu aporte.
Punit Vora

99
@desigeek: si este es el caso, entonces no debería haber dicho "IDisposable no 'llama' al GC, solo 'marca' un objeto como destruible"
John Saunders

55
@desigeek: No hay forma garantizada de liberar memoria de manera determinista. Puede llamar a GC.Collect (), pero esa es una solicitud amable, no una demanda. Todos los subprocesos en ejecución deben suspenderse para que la recolección de basura continúe; lea el concepto de puntos de seguridad de .NET si desea obtener más información, por ejemplo, msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Si no se puede suspender un subproceso, por ejemplo, porque hay una llamada al código no administrado, GC.Collect () puede no hacer nada en absoluto.
Concrete Gannet

Respuestas:


2609

El punto de disposición es liberar recursos no administrados. Debe hacerse en algún momento, de lo contrario nunca se limpiarán. El recolector de basura no sabe cómo llamar DeleteHandle()a una variable de tipo IntPtr, no sabe si necesita o no llamar DeleteHandle().

Nota : ¿Qué es un recurso no administrado ? Si lo encontró en Microsoft .NET Framework: está administrado. Si te metiste a MSDN, no está administrado. Todo lo que haya utilizado para llamadas P / Invoke para salir del mundo cómodo y agradable de todo lo que tiene disponible en .NET Framework no está administrado, y ahora usted es responsable de limpiarlo.

El objeto que ha creado necesita exponer algún método, que el mundo exterior pueda llamar, para limpiar recursos no administrados. El método se puede nombrar como quieras:

public void Cleanup()

o

public void Shutdown()

Pero en cambio hay un nombre estandarizado para este método:

public void Dispose()

Incluso se creó una interfaz IDisposableque tiene ese único método:

public interface IDisposable
{
   void Dispose()
}

Entonces, hace que su objeto exponga la IDisposableinterfaz, y de esa manera promete que ha escrito ese método único para limpiar sus recursos no administrados:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Y tu estas listo. Excepto que puedes hacerlo mejor.


¿Qué sucede si su objeto ha asignado un System.Drawing.Bitmap de 250 MB (es decir, la clase de mapa de bits administrado .NET) como algún tipo de búfer de cuadros? Claro, este es un objeto .NET administrado, y el recolector de basura lo liberará. Pero, ¿de verdad quieres dejar 250 MB de memoria simplemente sentado allí, esperando que el recolector de basura finalmente llegue y lo libere? ¿Qué pasa si hay una conexión de base de datos abierta ? Seguramente no queremos que esa conexión permanezca abierta, esperando que el GC finalice el objeto.

Si el usuario ha llamado Dispose()(lo que significa que ya no planea usar el objeto), ¿por qué no deshacerse de esos mapas de bits y conexiones de bases de datos derrochadores?

Entonces ahora nosotros:

  • deshacerse de los recursos no administrados (porque tenemos que hacerlo), y
  • deshacerse de los recursos administrados (porque queremos ser útiles)

Así que vamos a actualizar nuestro Dispose()método para deshacernos de esos objetos administrados:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

¡Y todo está bien, excepto que puedes hacerlo mejor !


¿Qué pasa si la persona olvidó llamar Dispose()a su objeto? ¡Entonces perderían algunos recursos no administrados !

Nota: No perderán recursos administrados , porque eventualmente el recolector de basura se ejecutará, en un hilo de fondo, y liberará la memoria asociada con cualquier objeto no utilizado. Esto incluirá su objeto y cualquier objeto administrado que use (por ejemplo, el Bitmapy el DbConnection).

Si la persona olvidó llamar Dispose(), ¡ aún podemos salvar su tocino! Todavía tenemos una forma de llamarlo para ellos: cuando el recolector de basura finalmente consigue en torno a la liberación (es decir finalización) nuestro objeto.

Nota: El recolector de basura eventualmente liberará todos los objetos administrados. Cuando lo hace, llama al Finalize método en el objeto. El GC no sabe, ni le importa, acerca de su método de eliminación . Ese fue solo un nombre que elegimos para un método que llamamos cuando deseamos deshacernos de cosas no administradas.

La destrucción de nuestro objeto por parte del recolector de basura es el momento perfecto para liberar esos molestos recursos no administrados. Hacemos esto anulando el Finalize()método.

Nota: En C #, no anula explícitamente el Finalize()método. Escribe un método que se parece a un destructor de C ++ , y el compilador lo considera su implementación del Finalize()método:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Pero hay un error en ese código. Verá, el recolector de basura se ejecuta en un subproceso de fondo ; no sabes el orden en que se destruyen dos objetos. Es completamente posible que en su Dispose()código, el objeto administrado del que está tratando de deshacerse (porque quería ser útil) ya no esté allí:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Entonces, lo que necesita es una forma de Finalize()saber Dispose()que no debe tocar ningún recurso administrado (porque es posible que ya no esté allí ), sin dejar de liberar recursos no administrados.

El patrón estándar para hacer esto es tener Finalize()y Dispose()ambos llamar a un tercer método (!); donde pasa un refrán booleano si lo está llamando Dispose()(en lugar de Finalize()), lo que significa que es seguro liberar recursos administrados.

Este método interno podría recibir un nombre arbitrario como "CoreDispose" o "MyInternalDispose", pero es tradición llamarlo Dispose(Boolean):

protected void Dispose(Boolean disposing)

Pero un nombre de parámetro más útil podría ser:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Y cambia su implementación del IDisposable.Dispose()método a:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

y tu finalizador para:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Nota : Si su objeto desciende de un objeto que se implementa Dispose, no olvide llamar a su método Dispose básico cuando anule Dispose:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

¡Y todo está bien, excepto que puedes hacerlo mejor !


Si el usuario llama Dispose()a su objeto, entonces todo se ha limpiado. Más tarde, cuando aparezca el recolector de basura y llame a Finalizar, volverá a llamar Dispose.

No solo es un desperdicio, sino que si su objeto tiene referencias basura a objetos que ya eliminó de la última llamada Dispose(), ¡intentará eliminarlos nuevamente!

Notarás en mi código que tuve cuidado de eliminar las referencias a los objetos que he dispuesto, por lo que no trato de invocar Disposeuna referencia de objeto basura. Pero eso no impidió que un error sutil se deslizara.

Cuando el usuario llama Dispose(): se destruye el identificador CursorFileBitmapIconServiceHandle . Más tarde, cuando se ejecuta el recolector de basura, intentará destruir el mismo controlador nuevamente.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

La forma de solucionar esto es decirle al recolector de basura que no necesita molestarse en finalizar el objeto: sus recursos ya se han limpiado y no se necesita más trabajo. Para ello, llame GC.SuppressFinalize()al Dispose()método:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Ahora que el usuario ha llamado Dispose(), tenemos:

  • recursos no gestionados liberados
  • recursos gestionados liberados

No tiene sentido que el GC ejecute el finalizador: todo se ha solucionado.

¿No podría usar Finalizar para limpiar recursos no administrados?

La documentación para Object.Finalizedice:

El método Finalizar se utiliza para realizar operaciones de limpieza en los recursos no administrados retenidos por el objeto actual antes de que el objeto sea destruido.

Pero la documentación de MSDN también dice, para IDisposable.Dispose:

Realiza tareas definidas por la aplicación asociadas con la liberación, liberación o restablecimiento de recursos no administrados.

Entonces cual es? ¿Cuál es el lugar para limpiar los recursos no administrados? La respuesta es:

¡Es tu elección! Pero elige Dispose.

Ciertamente, podría colocar su limpieza no administrada en el finalizador:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

El problema con esto es que no tienes idea de cuándo el recolector de basura se encargará de finalizar tu objeto. Sus recursos nativos no administrados, innecesarios y no utilizados se quedarán hasta que el recolector de basura finalmente se ejecute. Entonces llamará a su método finalizador; limpiar recursos no administrados. La documentación de Object.Finalize señala esto:

La hora exacta en que se ejecuta el finalizador no está definida. Para garantizar la liberación determinista de recursos para instancias de su clase, implemente un método Close o proporcione una IDisposable.Disposeimplementación.

Esta es la virtud de usar Disposepara limpiar recursos no administrados; puede saber y controlar cuándo se limpian los recursos no administrados. Su destrucción es "determinista" .


Para responder a su pregunta original: ¿Por qué no liberar memoria ahora, en lugar de cuando el GC decida hacerlo? Tengo un software de reconocimiento facial que necesita deshacerse de 530 MB de imágenes internas ahora , ya que ya no son necesarias. Cuando no lo hacemos: la máquina se detiene por completo.

Lectura adicional

Para cualquiera a quien le guste el estilo de esta respuesta (explicando el por qué , por lo que el cómo se vuelve obvio), le sugiero que lea el Capítulo Uno del COM esencial de Don Box:

En 35 páginas, explica los problemas del uso de objetos binarios e inventa COM ante sus ojos. Una vez que se da cuenta del por qué de COM, las 300 páginas restantes son obvias y solo detallan la implementación de Microsoft.

Creo que cada programador que alguna vez haya tratado con objetos o COM debería, como mínimo, leer el primer capítulo. Es la mejor explicación de cualquier cosa.

Lectura extra de bonificación

Cuando todo lo que sabes está mal por Eric Lippert

Por lo tanto, es muy difícil escribir un finalizador correcto, y el mejor consejo que puedo darle es no intentarlo .


12
Puede hacerlo mejor: debe agregar una llamada a GC.SuppressFinalize () en Dispose.
zócalo

55
@Daniel Earwicker: Es verdad. A Microsoft le encantaría que dejaras de usar Win32 por completo y te apegaras a llamadas de .NET Framework muy bien extraíbles, portátiles e independientes del dispositivo. Si quieres hurgar en el sistema operativo debajo; porque crees que sabes qué sistema operativo se está ejecutando: estás tomando tu vida en tus propias manos. No todas las aplicaciones .NET se ejecutan en Windows o en un escritorio.
Ian Boyd el

34
Esta es una gran respuesta, pero creo que sin embargo se beneficiaría de una lista de código final para un caso estándar y para un caso donde la clase se deriva de una clase base que ya implementa Dispose. por ejemplo, después de haber leído aquí ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) también me he confundido sobre lo que debo hacer al derivar de la clase que ya implementa Dispose ( Hola, soy nuevo en esto).
integra753

55
@GregS y otros: en general, no me molestaría en establecer referencias null. En primer lugar, significa que no puede hacerlos readonlyy, en segundo lugar, debe hacer !=nullverificaciones muy feas (como en el código de ejemplo). Podría tener una bandera disposed, pero es más fácil no preocuparse por ella. .NET GC es lo suficientemente agresivo como para que una referencia a un campo xya no se cuente como 'usada' cuando pasa la x.Dispose()línea.
porges

77
En la segunda página del libro de Don Box que mencionó, él usa el ejemplo de una implementación O (1) del algoritmo de búsqueda, cuyos "detalles se dejan como ejercicio para el lector". Me reí.
wip

65

IDisposablea menudo se usa para explotar la usingdeclaración y aprovechar una manera fácil de hacer una limpieza determinista de objetos administrados.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

66
Personalmente, me gusta, pero en realidad no coincide con las pautas de diseño del marco.
mqp

44
Lo consideraría un diseño adecuado porque permite ámbitos deterministas fáciles y construcciones / limpiezas de alcance, especialmente cuando se entremezcla con bloques de manejo de excepciones, bloqueo y uso de recursos no administrados de formas complejas. El lenguaje ofrece esto como una característica de primera clase.
yfeldblum

No sigue exactamente las reglas especificadas en el FDG, pero sin duda es un uso válido del patrón, ya que es necesario para ser utilizado por la "declaración de uso".
Scott Dorman

2
Mientras Log.Outdent no arroje, definitivamente no hay nada de malo en esto.
Daniel Earwicker

1
Las diversas respuestas a ¿Es abusivo usar IDisposable y "usar" como un medio para obtener un "comportamiento de alcance" para la seguridad de excepción? Entra un poco más en detalle sobre por qué a las diferentes personas les gusta / no les gusta esta técnica. Es algo controvertido.
Brian

44

El propósito del patrón Dispose es proporcionar un mecanismo para limpiar los recursos administrados y no administrados y cuando eso ocurre depende de cómo se llame al método Dispose. En su ejemplo, el uso de Dispose no está haciendo nada relacionado con la eliminación, ya que borrar una lista no tiene ningún impacto en la eliminación de esa colección. Del mismo modo, las llamadas para establecer las variables en nulo tampoco tienen impacto en el GC.

Puede consultar este artículo para obtener más detalles sobre cómo implementar el patrón Dispose, pero básicamente se ve así:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

El método más importante aquí es Dispose (bool), que en realidad se ejecuta en dos circunstancias diferentes:

  • disposición == verdadero: el código de un usuario ha llamado al método directa o indirectamente. Los recursos gestionados y no gestionados se pueden eliminar.
  • disponer == falso: el tiempo de ejecución ha llamado al método desde el interior del finalizador, y no debe hacer referencia a otros objetos. Solo los recursos no administrados pueden ser eliminados.

El problema con simplemente dejar que el GC se encargue de hacer la limpieza es que no tienes control real sobre cuándo el GC ejecutará un ciclo de recolección (puedes llamar a GC.Collect (), pero realmente no deberías) para que los recursos se queden alrededor más de lo necesario. Recuerde, llamar a Dispose () en realidad no causa un ciclo de recolección o de ninguna manera hace que el GC recolecte / libere el objeto; simplemente proporciona los medios para una limpieza más determinista de los recursos utilizados y le dice al GC que esta limpieza ya se ha realizado.

El objetivo de IDisposable y el patrón de eliminación no se trata de liberar memoria inmediatamente. La única vez que una llamada a Eliminar tendrá incluso la posibilidad de liberar memoria de inmediato es cuando maneja el escenario de disposición == falso y manipula los recursos no administrados. Para el código administrado, la memoria no se recuperará hasta que el GC ejecute un ciclo de recopilación, sobre el cual realmente no tiene control (aparte de llamar a GC.Collect (), que ya he mencionado no es una buena idea).

Su escenario no es realmente válido ya que las cadenas en .NET no usan recursos no administrados y no implementan IDisposable, no hay forma de obligarlos a "limpiarse".


44
¿No te olvidaste de implementar el finalizador?
Budda

@Budda: No, él está usando un SafeHandle. No hay necesidad de un destructor.
Henk Holterman el

99
+1 para agregar la red de seguridad para múltiples llamadas a Dispose (). La especificación dice que las llamadas múltiples deberían ser seguras. Demasiadas clases de Microsoft no pueden implementar eso, y obtienes la molesta ObjectDisposedException.
Jesse Chisholm

55
Pero Dispose (bool disposing) es su propio método en su clase SimpleCleanup y nunca será llamado por el marco. Como solo lo llama con "verdadero" como parámetro, 'eliminar' nunca será falso. Su código es muy similar al ejemplo de MSDN para IDisposable, pero le falta el finalizador, como señaló @Budda, que es de donde vendría la llamada con disposing = false.
yoyo

19

No debería haber más llamadas a los métodos de un objeto después de haber llamado a Dispose (aunque un objeto debería tolerar más llamadas a Dispose). Por lo tanto, el ejemplo en la pregunta es tonto. Si se llama Dispose, entonces el objeto en sí puede descartarse. Por lo tanto, el usuario debe descartar todas las referencias a ese objeto completo (establecerlas como nulas) y todos los objetos relacionados internos se limpiarán automáticamente.

En cuanto a la pregunta general sobre administrado / no administrado y la discusión en otras respuestas, creo que cualquier respuesta a esta pregunta tiene que comenzar con una definición de un recurso no administrado.

Todo se reduce a que hay una función a la que puede llamar para poner el sistema en un estado, y hay otra función a la que puede llamar para que vuelva a salir de ese estado. Ahora, en el ejemplo típico, la primera podría ser una función que devuelve un identificador de archivo, y la segunda podría ser una llamada a CloseHandle.

Pero, y esta es la clave, podrían ser cualquier par de funciones coincidentes. Uno construye un estado, el otro lo derriba. Si el estado se ha construido pero aún no se ha derribado, existe una instancia del recurso. Debe organizar el desmontaje en el momento adecuado: el recurso no es administrado por el CLR. El único tipo de recurso administrado automáticamente es la memoria. Hay dos tipos: el GC y la pila. Los tipos de valores son administrados por la pila (o enganchando un viaje dentro de los tipos de referencia), y los tipos de referencia son administrados por el GC.

Estas funciones pueden causar cambios de estado que se pueden intercalar libremente, o pueden necesitar estar perfectamente anidadas. Los cambios de estado pueden ser seguros para subprocesos o no.

Mire el ejemplo en la pregunta de Justice. Los cambios en la sangría del archivo de registro deben estar perfectamente anidados, o todo sale mal. También es poco probable que sean seguras.

Es posible engancharse con el recolector de basura para limpiar sus recursos no administrados. Pero solo si las funciones de cambio de estado son seguras para subprocesos y dos estados pueden tener vidas que se superponen de alguna manera. ¡Entonces el ejemplo de Justice de un recurso NO debe tener un finalizador! Simplemente no ayudaría a nadie.

Para ese tipo de recursos, solo puede implementar IDisposable, sin un finalizador. El finalizador es absolutamente opcional, tiene que serlo. Esto se pasa por alto o ni siquiera se menciona en muchos libros.

Luego debe usar la usingdeclaración para tener alguna posibilidad de asegurarse de que Disposese llame. Esto es esencialmente como enganchar un viaje con la pila (por lo que como finalizador es para el GC, usinges para la pila).

La parte que falta es que tiene que escribir manualmente Dispose y hacer que llame a sus campos y a su clase base. Los programadores de C ++ / CLI no tienen que hacer eso. El compilador lo escribe para ellos en la mayoría de los casos.

Hay una alternativa, que prefiero para los estados que se anidan perfectamente y no son seguros (aparte de cualquier otra cosa, evitar IDisposable le ahorra el problema de tener una discusión con alguien que no puede resistirse a agregar un finalizador a cada clase que implementa IDisposable) .

En lugar de escribir una clase, escribes una función. La función acepta un delegado para volver a llamar a:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Y luego un ejemplo simple sería:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

La lambda que se pasa sirve como un bloque de código, por lo que es como si hicieras tu propia estructura de control para cumplir el mismo propósito que using, excepto que ya no tienes ningún peligro de que la persona que llama abusa de ella. No hay forma de que puedan dejar de limpiar el recurso.

Esta técnica es menos útil si el recurso es del tipo que puede tener vidas superpuestas, porque entonces desea poder construir el recurso A, luego el recurso B, luego matar el recurso A y luego matar el recurso B. No puede hacer eso si has obligado al usuario a anidar perfectamente así. Pero luego debe usarlo IDisposable(pero aún sin un finalizador, a menos que haya implementado threadsafety, que no es gratuito).


re: "No debería haber más llamadas a los métodos de un objeto después de haber llamado a Dispose". "Debería" ser la palabra clave. Si tiene acciones asincrónicas pendientes, pueden aparecer después de que su objeto haya sido eliminado. Causando una excepción ObjectDisposedException.
Jesse Chisholm

La suya parece ser la única respuesta que no sea la mía, que toca la idea de que los recursos no administrados encapsulan un estado que el GC no comprende. Sin embargo, un aspecto clave de un recurso no administrado es que una o más entidades cuyo estado podría necesitar limpieza pueden continuar existiendo incluso si el objeto que "posee" el recurso no lo tiene. ¿Cómo te gusta mi definición? Bastante similar, pero creo que hace que el "recurso" sea un poco más sustantivo (es el "acuerdo" del objeto externo para alterar su comportamiento, a cambio de la notificación de cuándo ya no se necesitan sus servicios)
supercat

@supercat: si está interesado, escribí la siguiente publicación un par de días después de escribir la respuesta anterior: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker

1
@DanielEarwicker: artículo interesante, aunque puedo pensar en al menos un tipo de recurso no administrado que realmente no cubre: suscripciones a eventos de objetos de larga vida. Las suscripciones a eventos son fungibles, pero incluso si la memoria fuera ilimitada, la eliminación de ellas podría ser costosa. Por ejemplo, un enumerador para una colección que permite la modificación durante la enumeración puede necesitar suscribirse para actualizar las notificaciones de la colección, y una colección puede actualizarse muchas veces durante su vida útil. Si los enumeradores son abandonados sin darse de baja ...
supercat

1
El par de operaciones entery exites el núcleo de cómo pienso en un recurso. Suscribirse / darse de baja en eventos debe encajar en eso sin dificultad. En términos de las características ortogonales / fungibles, es prácticamente indistinguible de una pérdida de memoria. (Esto no es sorprendente ya que la suscripción solo agrega objetos a una lista).
Daniel Earwicker

17

Escenarios que utilizo IDisposable: limpiar recursos no administrados, cancelar la suscripción a eventos, cerrar conexiones

El idioma que uso para implementar IDisposable ( no threadsafe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

La explicación completa del patrón se puede encontrar en msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
nunca debe incluir un finalizador a menos que tenga recursos no administrados. Incluso entonces, la implementación preferida es envolver el recurso no administrado en SafeHandle.
Dave Black

11

Sí, ese código es completamente redundante e innecesario y no hace que el recolector de basura haga nada que de otra manera no haría (una vez que una instancia de MyCollection sale del alcance, eso es). Especialmente las .Clear()llamadas.

Respuesta a su edición: más o menos. Si hago esto:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Es funcionalmente idéntico a esto para fines de administración de memoria:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Si realmente realmente necesita liberar la memoria en este mismo instante, llame GC.Collect(). Sin embargo, no hay razón para hacer esto aquí. La memoria se liberará cuando sea necesario.


2
re: "La memoria se liberará cuando sea necesario". Más bien diga: "cuando GC decida que es necesario". Es posible que vea problemas de rendimiento del sistema antes de que GC decida que realmente se necesita memoria . Liberarlo ahora puede no ser esencial, pero puede ser útil.
Jesse Chisholm

1
Hay algunos casos de esquina en los que anular referencias dentro de una colección puede acelerar la recolección de basura de los elementos a los que se hace referencia. Por ejemplo, si se crea una matriz grande y se llena con referencias a elementos más pequeños recién creados, pero no es necesaria por mucho tiempo después de eso, abandonar la matriz puede hacer que esos elementos se mantengan hasta el próximo Nivel 2 GC, mientras que ponerlo a cero primero puede hacer que los artículos sean elegibles para el siguiente nivel 0 o nivel 1 GC. Sin duda, tener objetos grandes de corta duración en el Montón de objetos grandes es asqueroso de todos modos (no me gusta el diseño) pero ...
supercat

1
... poner a cero tales arreglos antes de abandonarlos, a veces disminuyo el impacto de GC.
supercat

11

Si MyCollectionse va a recolectar basura de todos modos, entonces no debería necesitar tirarla. Hacerlo simplemente agitará la CPU más de lo necesario e incluso puede invalidar algunos análisis precalculados que el recolector de basura ya ha realizado.

Solía IDisposablehacer cosas como asegurarme de que los hilos estén dispuestos correctamente, junto con los recursos no administrados.

EDITAR En respuesta al comentario de Scott:

La única vez que las métricas de rendimiento del GC se ven afectadas es cuando se realiza una llamada al [sic] GC.Collect () "

Conceptualmente, el GC mantiene una vista del gráfico de referencia de objeto y todas las referencias a él desde los marcos de pila de hilos. Este montón puede ser bastante grande y abarcar muchas páginas de memoria. Como optimización, el GC almacena en caché su análisis de páginas que es poco probable que cambien con mucha frecuencia para evitar volver a escanear la página innecesariamente. El GC recibe una notificación del núcleo cuando los datos en una página cambian, por lo que sabe que la página está sucia y requiere un nuevo análisis. Si la colección está en Gen0, es probable que otras cosas en la página también estén cambiando, pero esto es menos probable en Gen1 y Gen2. Como anécdota, estos ganchos no estaban disponibles en Mac OS X para el equipo que transfirió el GC a Mac para que el complemento Silverlight funcionara en esa plataforma.

Otro punto contra la eliminación innecesaria de recursos: imagine una situación en la que un proceso se está descargando. Imagine también que el proceso se ha estado ejecutando durante algún tiempo. Lo más probable es que muchas de las páginas de memoria de ese proceso se hayan cambiado al disco. Por lo menos ya no están en la caché L1 o L2. En tal situación, no tiene sentido que una aplicación que se está descargando cambie todos esos datos y páginas de códigos de nuevo a la memoria para 'liberar' los recursos que el sistema operativo va a liberar de todos modos cuando el proceso finalice. Esto se aplica a los recursos administrados e incluso a ciertos recursos no administrados. Solo se deben eliminar los recursos que mantienen vivos los subprocesos que no son de fondo; de lo contrario, el proceso seguirá vivo.

Ahora, durante la ejecución normal, hay recursos efímeros que deben limpiarse correctamente (como @fezmonkey señala las conexiones de bases de datos, sockets, identificadores de ventanas ) para evitar pérdidas de memoria no administradas. Estos son los tipos de cosas que deben eliminarse. Si creas alguna clase que posee un hilo (y por dueño, quiero decir que lo creó y, por lo tanto, es responsable de garantizar que se detenga, al menos según mi estilo de codificación), entonces esa clase probablemente deba implementar IDisposabley derribar el hilo durante Dispose.

.NET Framework usa la IDisposableinterfaz como una señal, incluso una advertencia, para los desarrolladores de que esta clase debe eliminarse. No puedo pensar en ningún tipo de marco que implemente IDisposable(excluyendo implementaciones de interfaz explícitas) donde la eliminación es opcional.


Calling Dispose es perfectamente válido, legal y alentador. Los objetos que implementan IDisposable generalmente lo hacen por una razón. La única vez que las métricas de rendimiento del GC se ven afectadas es cuando se realiza una llamada al GC.Collect ().
Scott Dorman

Para muchas clases .net, la eliminación es "algo" opcional, lo que significa que abandonar las instancias "por lo general" no causará ningún problema siempre que uno no se vuelva loco creando nuevas instancias y abandonándolas. Por ejemplo, el código generado por el compilador para los controles parece crear fuentes cuando se instancian los controles y los abandona cuando se eliminan los formularios; Si uno crea y dispone miles de controles, esto podría atar miles de identificadores de GDI, pero en la mayoría de los casos los controles no se crean ni destruyen tanto. Sin embargo, uno todavía debe tratar de evitar tal abandono.
supercat

1
En el caso de las fuentes, sospecho que el problema es que Microsoft nunca definió realmente qué entidad es responsable de disponer el objeto "fuente" asignado a un control; en algunos casos, los controles pueden compartir una fuente con un objeto de vida más larga, por lo que tener el control Dispose the font sería malo. En otros casos, se asignará una fuente a un control y en ningún otro lugar, por lo que si el control no lo dispone, nadie lo hará. Por cierto, esta dificultad con las fuentes podría haberse evitado si hubiera habido una clase FontTemplate no desechable separada, ya que los controles no parecen usar el controlador GDI de su fuente.
supercat

Sobre el tema de las Dispose()llamadas opcionales , consulte: stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

En el ejemplo que publicó, todavía no "libera la memoria ahora". Toda la memoria es basura recolectada, pero puede permitir que la memoria se recolecte en una generación anterior . Tendría que ejecutar algunas pruebas para estar seguro.


Las Pautas de diseño del marco son pautas y no reglas. Le indican para qué sirve principalmente la interfaz, cuándo usarla, cómo usarla y cuándo no.

Una vez leí el código que era un simple RollBack () en caso de falla utilizando IDisposable. La siguiente clase de MiniTx verificaría una marca en Dispose () y si la Commitllamada nunca sucedió, entonces se llamaría Rollbacka sí misma. Agregó una capa de indirección que hace que el código de llamada sea mucho más fácil de entender y mantener. El resultado se parecía a:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

También he visto que el código de tiempo / registro hace lo mismo. En este caso, el método Dispose () detuvo el temporizador y registró que el bloque había salido.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Así que aquí hay un par de ejemplos concretos que no hacen ninguna limpieza de recursos no administrada, pero sí usan IDisposable para crear un código más limpio.


Eche un vistazo al ejemplo de @Daniel Earwicker utilizando funciones de orden superior. Para la evaluación comparativa, el tiempo, el registro, etc. Parece mucho más sencillo.
Aluan Haddad


6

No repetiré las cosas habituales sobre Usar o liberar recursos no administrados, todo eso ha sido cubierto. Pero me gustaría señalar lo que parece un error común.
Dado el siguiente código

Clase pública LargeStuff
  Implementa IDisposable
  Private _Large como cadena ()

  'Algún código extraño que significa _Large ahora contiene varios millones de cadenas largas.

  Public Sub Dispose () Implementa IDisposable.Dispose
    _Large = Nada
  End Sub

Me doy cuenta de que la implementación Desechable no sigue las pautas actuales, pero espero que todos tengan la idea.
Ahora, cuando se llama a Dispose, ¿cuánta memoria se libera?

Respuesta: ninguna.
Llamar a Dispose puede liberar recursos no administrados, NO PUEDE reclamar memoria administrada, solo el GC puede hacerlo. Eso no quiere decir que lo anterior no sea una buena idea, de hecho, seguir el patrón anterior sigue siendo una buena idea. Una vez que se ha ejecutado Dispose, no hay nada que impida que el GC vuelva a reclamar la memoria que estaba utilizando _Large, a pesar de que la instancia de LargeStuff todavía puede estar dentro del alcance. Las cadenas en _Large también pueden estar en gen 0 pero la instancia de LargeStuff podría ser gen 2, por lo que nuevamente, la memoria se volvería a reclamar antes.
Sin embargo, no tiene sentido agregar un finalizador para llamar al método Dispose que se muestra arriba. Eso retrasará la recuperación de la memoria para permitir que se ejecute el finalizador.


1
Si una instancia de LargeStuffha existido el tiempo suficiente para llegar a la Generación 2, y si _Largecontiene una referencia a una cadena recién creada que está en la Generación 0, entonces si la instancia de LargeStuffse abandona sin anular _Large, entonces la cadena a la que hace referencia _Largese mantendrá hasta la próxima colección Gen2. La reducción a cero _Largepuede permitir que la cadena se elimine en la próxima colección Gen0. En la mayoría de los casos, anular referencias no es útil, pero hay casos en los que puede ofrecer algún beneficio.
supercat

5

Además de su uso principal como una forma de controlar la vida útil de los recursos del sistema (¡completamente cubierto por la increíble respuesta de Ian , felicitaciones!), El combo IDisposable / using también se puede utilizar para determinar el cambio de estado de los recursos globales (críticos) : la consola , los hilos , el proceso , cualquier objeto global como una instancia de aplicación .

He escrito un artículo sobre este patrón: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Ilustra cómo puede proteger algunos estados globales de uso frecuente de una manera reutilizable y legible : colores de consola , cultura actual de hilos , propiedades de objetos de aplicaciones de Excel ...


4

En todo caso, esperaría que el código sea menos eficiente que cuando se omite.

Llamar a los métodos Clear () no es necesario, y el GC probablemente no lo haría si Dispose no lo hiciera ...


2

Hay cosas que la Dispose()operación hace en el código de ejemplo que podrían tener un efecto que no ocurriría debido a un GC normal del MyCollectionobjeto.

Si los objetos referenciados por _theListo _theDictson referidos por otros objetos, entonces eso List<>o Dictionary<>de objetos no estarán sujetos a la colección pero de repente no tendrá ningún contenido. Si no hubiera una operación Dispose () como en el ejemplo, esas colecciones aún contendrían su contenido.

Por supuesto, si esta fuera la situación, lo llamaría un diseño roto; solo estoy señalando (pedagógicamente, supongo) que la Dispose()operación podría no ser completamente redundante, dependiendo de si hay otros usos del List<>o Dictionary<>no se muestra en el fragmento.


Son campos privados, por lo que creo que es justo asumir que el OP no está dando referencias a ellos.
mqp

1) el fragmento de código es solo un código de ejemplo, así que solo estoy señalando que puede haber un efecto secundario que es fácil de pasar por alto; 2) los campos privados son a menudo el objetivo de una propiedad / método getter, tal vez demasiado (algunas personas consideran que getter / setters es un poco anti-patrón).
Michael Burr

2

Un problema con la mayoría de las discusiones sobre "recursos no administrados" es que realmente no definen el término, pero parecen implicar que tiene algo que ver con el código no administrado. Si bien es cierto que muchos tipos de recursos no administrados interactúan con el código no administrado, pensar en recursos no administrados en esos términos no es útil.

En cambio, uno debería reconocer lo que todos los recursos administrados tienen en común: todos ellos implican un objeto que le pide a una 'cosa' externa que haga algo en su nombre, en detrimento de otras 'cosas', y la otra entidad acuerda hacerlo hasta que Aviso adicional. Si el objeto fuera abandonado y desapareciera sin dejar rastro, nada le diría a esa 'cosa' externa que ya no necesitaba alterar su comportamiento en nombre del objeto que ya no existía; en consecuencia, la utilidad de la cosa se reduciría permanentemente.

Un recurso no administrado, entonces, representa un acuerdo por parte de una 'cosa' externa para alterar su comportamiento en nombre de un objeto, lo que perjudicaría inútilmente la utilidad de esa 'cosa' externa si el objeto fuera abandonado y dejara de existir. Un recurso gestionado es un objeto que se beneficia de dicho acuerdo, pero que se ha registrado para recibir una notificación si se abandona, y que utilizará dicha notificación para poner sus asuntos en orden antes de que se destruya.


Bueno, en mi opinión, la definición de objeto no administrado es clara; cualquier objeto que no sea GC .
Eonil

1
@Eonil: Unmanaged Object! = Recurso no administrado. Cosas como los eventos se pueden implementar completamente usando objetos administrados, pero aún constituyen recursos no administrados porque, al menos en el caso de objetos de corta duración que se suscriben a eventos de objetos de larga duración, el GC no sabe nada sobre cómo limpiarlos .
supercat


2

Primero de la definición. Para mí, el recurso no administrado significa alguna clase, que implementa una interfaz IDisposable o algo creado con el uso de llamadas a dll. GC no sabe cómo lidiar con tales objetos. Si la clase tiene, por ejemplo, solo tipos de valor, entonces no considero esta clase como una clase con recursos no administrados. Para mi código sigo las siguientes prácticas:

  1. Si la clase creada por mí usa algunos recursos no administrados, significa que también debería implementar una interfaz IDisposable para limpiar la memoria.
  2. Limpie los objetos tan pronto como termine de usarlo.
  3. En mi método de disposición, itero sobre todos los miembros de clase IDisposable y llamo a Dispose.
  4. En mi método Dispose, llame a GC.SuppressFinalize (this) para notificar al recolector de basura que mi objeto ya se limpió. Lo hago porque llamar a GC es una operación costosa.
  5. Como precaución adicional, trato de hacer posible la llamada de Dispose () varias veces.
  6. En algún momento agregué el miembro privado _disposed y las llamadas al método de check in se limpiaron. Y si se limpió, generar la plantilla ObjectDisposedException
    siguiente muestra lo que describí en palabras como muestra de código:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"Para mí, el recurso no administrado significa alguna clase, que implementa una interfaz IDisposable o algo creado con el uso de llamadas a dll". Entonces, ¿estás diciendo que cualquier tipo que is IDisposabledebería considerarse un recurso no administrado? Eso no parece correcto. Además, si el tipo implícito es un tipo de valor puro, parece sugerir que no es necesario eliminarlo. Eso también parece estar mal.
Aluan Haddad

Todo el mundo juzga solo. No me gusta agregar al código de la mina algo solo por el bien de la adición. Significa que si agrego IDisposable, significa que he creado algún tipo de funcionalidad que GC no puede administrar o supongo que no podrá administrar su vida útil correctamente.
Yuriy Zaletskyy

2

Su ejemplo de código dado no es un buen ejemplo de IDisposableuso. La limpieza del diccionario normalmente no debería ir al Disposemétodo. Los elementos del diccionario se borrarán y eliminarán cuando salga del alcance. IDisposableSe requiere la implementación para liberar algunos controladores / memoria que no se liberarán / liberarán incluso después de que estén fuera de alcance.

El siguiente ejemplo muestra un buen ejemplo para el patrón IDisposable con algún código y comentarios.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

El caso de uso más justificable para la eliminación de recursos gestionados es la preparación para que el GC recupere recursos que de otra forma nunca se recopilarían.

Un buen ejemplo son las referencias circulares.

Si bien es una buena práctica usar patrones que eviten referencias circulares, si termina con (por ejemplo) un objeto 'hijo' que tiene una referencia de nuevo a su 'padre', esto puede detener la colección GC del padre si simplemente abandona la referencia y confíe en GC: además, si ha implementado un finalizador, nunca se llamará.

La única forma de evitar esto es romper manualmente las referencias circulares estableciendo las referencias principales en nulas en los elementos secundarios.

Implementar IDisposable en padres e hijos es la mejor manera de hacerlo. Cuando se llama a Dispose en el Parent, llame a Dispose en todos los Children y, en el método Child Dispose, establezca las referencias Parent en null.


44
En su mayor parte, el GC no funciona identificando objetos muertos, sino identificando los vivos. Después de cada ciclo gc, para cada objeto que se ha registrado para la finalización, se almacena en el montón de objetos grandes, o es el objetivo de un live WeakReference, el sistema verificará un indicador que indica que se encontró una referencia rooteada en vivo en el último ciclo GC , y agregará el objeto a una cola de objetos que necesitan una finalización inmediata, liberará el objeto del montón de objetos grandes o invalidará la referencia débil. Las referencias circulares no mantendrán vivos los objetos si no existen otras referencias.
supercat

1

Veo que muchas respuestas se han desplazado para hablar sobre el uso de IDisposable para recursos administrados y no administrados. Sugeriría este artículo como una de las mejores explicaciones que he encontrado sobre cómo realmente se debe usar IDisposable.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Para la pregunta real; Si usa IDisposable para limpiar objetos administrados que ocupan mucha memoria, la respuesta corta sería no . La razón es que una vez que deseche un ID desechable, debería dejarlo fuera de alcance. En ese momento, los objetos secundarios a los que se hace referencia también están fuera del alcance y se recopilarán.

La única excepción real a esto sería si tiene mucha memoria atada a objetos administrados y ha bloqueado ese hilo esperando que se complete alguna operación. Si esos objetos no se necesitarían después de que se completara esa llamada, establecer esas referencias en nulo podría permitir que el recolector de basura las recolecte antes. Pero ese escenario representaría un código incorrecto que debía ser refactorizado, no un caso de uso de IDisposable.


1
No entendí por qué alguien puso -1 en tu respuesta
Sebastian Oscar Lopez
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.