Uso adecuado de la interfaz IDisposable

Lo sé por la lectura la documentación de Microsoft que el uso "principal" del IDisposable La interfaz es limpiar los recursos no administrados.

Para mí, "no administrado" significa cosas como conexiones de base de datos, sockets, identificadores de ventana, etc. Pero he visto código donde el Dispose() el método se implementa para liberar gestionado recursos, lo que me parece redundante, ya que el recolector de basura debería encargarse de eso por usted.

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 libre de memoria utilizada por MyCollection más rápido de lo que normalmente sería?

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

preguntado el 11 de febrero de 09 a las 19:02

Me gusta la respuesta aceptada porque le dice el 'patrón' correcto de usar IDisposable, pero como dijo el OP en su edición, no responde a la pregunta que pretendía. IDisposable no 'llama' al GC, simplemente 'marca' un objeto como destruible. Pero, ¿cuál es la forma real de liberar memoria "ahora mismo" en lugar de esperar a que GC se active? Creo que esta pregunta merece más discusión. -

IDisposable no marca nada. La Dispose El método hace lo que tiene que hacer para limpiar los recursos utilizados por la instancia. Esto no tiene nada que ver con GC. -

@John. Entiendo IDisposable. . Y es por eso que dije que la respuesta aceptada no responde a la pregunta prevista del OP (y la edición de seguimiento) sobre si IDisposable ayudará a . Desde IDisposable no tiene nada que ver con liberar memoria, solo recursos, entonces, como dijiste, no hay necesidad de establecer las referencias administradas en 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 en absoluto a liberar memoria, solo recursos". Pero de todos modos, gracias por tu aporte. -

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

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

19 Respuestas

El punto de disponer is para 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() en una variable de tipo IntPtr, no sabe si o no necesita llamar DeleteHandle().

Nota:: Que es un recurso no administrado? Si lo encontró en Microsoft .NET Framework: está administrado. Si fue usted mismo a hurgar en MSDN, no está administrado. Todo lo que haya usado llamadas P / Invoke para salir del agradable y cómodo mundo de todo lo que está disponible para usted en .NET Framework no está administrado, y ahora usted es responsable de limpiarlo.

El objeto que ha creado debe exponerse algo método, que el mundo exterior puede llamar, con el fin de limpiar los recursos no administrados. El método puede tener el nombre que desee:

public void Cleanup()

or

public void Shutdown()

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

public void Dispose()

Incluso se creó una interfaz, IDisposable, que tiene solo ese método:

public interface IDisposable
{
   void Dispose()
}

Entonces haces que tu objeto exponga el IDisposable interfaz, y de esa manera promete que ha escrito ese único método para limpiar sus recursos no administrados:

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

Y ya está. Excepto que puedes hacerlo mejor.


¿Qué pasa si su objeto ha asignado 250 MB System.Drawing.Bitmap (es decir, la clase Bitmap administrada por .NET) como una especie de búfer de tramas? Claro, este es un objeto .NET administrado y el recolector de basura lo liberará. Pero, ¿realmente desea dejar 250 MB de memoria ahí, esperando a que el recolector de basura finalmente ven y libéralo? ¿Y si hay un 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 planean usar el objeto) ¿por qué no deshacerse de esos mapas de bits y conexiones de base de datos inútiles?

Así que ahora lo haremos:

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

Así que actualice nuestro Dispose() método para deshacerse 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 esta bien excepto que puedes hacerlo mejor!


¿Y si la persona olvidó llamar Dispose() en tu objeto? Entonces filtrarían algunos no administrado recursos!

Nota: Ellos no se filtrarán gestionado recursos, porque eventualmente el recolector de basura se ejecutará, en un subproceso en segundo plano, y liberará la memoria asociada con cualquier objeto no utilizado. Esto incluirá su objeto y cualquier objeto administrado que utilice (por ejemplo, el Bitmap y DbConnection).

Si la persona olvidó llamar Dispose(), Podemos posible ¡guarda su tocino! Todavía tenemos una forma de llamarlo para ellos: cuando el recolector de basura finalmente logra liberar (es decir, finalizar) 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 se preocupa por tu tiene método. Ese fue solo un nombre que elegimos para un método al que llamamos cuando queremos deshacernos de cosas no administradas.

La destrucción de nuestro objeto por el recolector de basura es el Perfecto Es hora de liberar esos molestos recursos no administrados. Hacemos esto anulando el Finalize() método.

Nota: En C #, no anula explícitamente el Finalize() método. Escribes un método que parece a Destructor de C ++, y el compilador lo toma como su implementación de la 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 hilo de fondo; no conoce el orden en el que se destruyen dos objetos. Es muy posible que en su Dispose() código, el gestionado El objeto del que intentas deshacerte (porque querías 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 necesitas es una forma de Finalize() para contar Dispose() que debería no tocar ninguna administrada recursos (porque ellos podría no estar allí más), sin dejar de liberar recursos no administrados.

El patrón estándar para hacer esto es tener Finalize() y Dispose() ambos llaman a tercera(!) método; donde pasas un dicho booleano si lo estás llamando desde Dispose() (Opuesto a Finalize()), lo que significa que es seguro liberar recursos administrados.

Este patrón de interno Método 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 para:

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

y su 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 implementa Dispose, entonces no olvides llamar a su base Deseche el método cuando anule Dispose:

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

Y todo esta bien excepto que puedes hacerlo mejor!


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

Esto no solo es un desperdicio, sino que si su objeto tiene referencias basura a objetos que ya eliminó del pasado llamar a Dispose(), intentarás desecharlos de nuevo.

Notarás que en mi código tuve cuidado de eliminar las referencias a los objetos que eliminé, así que no intento llamar Dispose en una referencia de objeto basura. Pero eso no impidió que apareciera un error sutil.

Cuando el usuario llama Dispose(): la manija CursorFileBitmapIconServiceHandle Esta destruido. Más tarde, cuando se ejecute el recolector de basura, intentará destruir el mismo identificador nuevamente.

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

La forma en que soluciona 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. Haces esto llamando GC.SuppressFinalize() en el objeto 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 liberados no administrados
  • recursos gestionados liberados

No tiene sentido que la GC ejecute el finalizador: todo está arreglado.

¿No puedo usar Finalize para limpiar recursos no administrados?

La documentación para Object.Finalize dice:

El método Finalize se usa para realizar operaciones de limpieza en recursos no administrados que tiene el objeto actual antes de que se destruya.

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, ¿cuál 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 eso es que no tiene idea de cuándo el recolector de basura terminará su objeto. Sus recursos nativos no administrados, no necesarios y no utilizados se quedarán hasta que el recolector de basura finalmente carreras. Luego llamará a su método de finalizador; limpieza de recursos no administrados. La documentación de Objeto.Finalizar señala esto:

La hora exacta en la que se ejecuta el finalizador no está definida. Para garantizar la liberación determinista de recursos para instancias de su clase, implemente un Cerrar método o proporcionar un IDisposable.Dispose puesta en práctica.

Esta es la virtud de usar Dispose para limpiar recursos no administrados; usted conoce y controla 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 para deshacerse de 530 MB de imágenes internas ahora mismo, ya que ya no son necesarios. Cuando no lo hacemos: la máquina se detiene rápidamente.

Lectura adicional

Para cualquiera a quien le guste el estilo de esta respuesta (explicando el el porqué, 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 de usar objetos binarios e inventa COM ante sus ojos. Una vez que te das cuenta de la el porqué de COM, las 300 páginas restantes son obvias y solo detallan la implementación de Microsoft.

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

Lectura extra extra

Cuando todo lo que sabes esta mal por Eric Lippert

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

respondido 01 mar '19, 19:03

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 deriva de una clase base que ya implementa Dispose. por ejemplo, habiendo leído aquí (msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx) también me he confundido acerca de lo que debo hacer al derivar de la clase que ya implementa Dispose (oye, soy nuevo en esto). - integra753

IDisposable se utiliza a menudo para explotar el using declaración y aproveche una forma sencilla de realizar una limpieza determinista de los 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.");
        }
    }
}

Respondido 11 Feb 09, 21:02

El propósito del patrón Dispose es proporcionar un mecanismo para limpiar tanto los recursos administrados como los no administrados, y cuándo eso ocurre depende de cómo se llame al método Dispose. En su ejemplo, el uso de Dispose no hace 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 ningún impacto en el GC.

Puedes echar un vistazo a esto 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 que es más importante aquí es Dispose (bool), que en realidad se ejecuta en dos circunstancias diferentes:

  • disposing == true: el método ha sido llamado directa o indirectamente por el código de un usuario. Se pueden eliminar los recursos administrados y no administrados.
  • disposing == false: el tiempo de ejecución ha llamado al método desde dentro del finalizador y no debes hacer referencia a otros objetos. Solo se pueden eliminar los recursos no administrados.

El problema de simplemente dejar que el GC se encargue de hacer la limpieza es que no tiene control real sobre cuándo el GC ejecutará un ciclo de recolección (puede llamar a GC.Collect (), pero realmente no debería) por lo que los recursos pueden quedarse 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 limpiar de manera más determinista los recursos utilizados e informar 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 de inmediato. La única vez que una llamada a Dispose tendrá la posibilidad de liberar memoria inmediatamente es cuando está manejando el escenario de disposing == false y manipulando recursos no administrados. Para el código administrado, la memoria en realidad no se recuperará hasta que el GC ejecute un ciclo de recolección, sobre el cual realmente no tienes 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 ningún recurso no administrado y no implementan IDisposable, no hay forma de forzarlas a "limpiarlas".

Respondido 12 Feb 09, 00:02

No debería haber más llamadas a los métodos de un objeto después de que se haya llamado a Dispose (aunque un objeto debería tolerar más llamadas a Dispose). Por lo tanto, el ejemplo de la pregunta es tonto. Si se llama a Dispose, el objeto en sí se puede descartar. Por lo tanto, el usuario debe descartar todas las referencias a ese objeto completo (establecerlas en nulo) 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 debe comenzar con una definición de 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 sacarlo 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, entonces existe una instancia del recurso. Debe hacer los arreglos necesarios para que el desmontaje ocurra en el momento adecuado; el recurso no está administrado por CLR. El único tipo de recurso administrado automáticamente es la memoria. Hay dos tipos: el GC y el stack. Los tipos de valor son administrados por la pila (o enganchando un paseo dentro de los tipos de referencia), y los tipos de referencia son administrados por el GC.

Estas funciones pueden provocar cambios de estado que se pueden intercalar libremente o que es posible que deban estar perfectamente anidados. Los cambios de estado pueden ser seguros para la ejecución de 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 saldrá mal. Además, es poco probable que sean seguras para subprocesos.

Es posible viajar 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 útiles que se superponen de alguna manera. ¡Así que el ejemplo de un recurso de Justice NO debe tener un finalizador! Simplemente no ayudaría a nadie.

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

Entonces tienes que usar el using declaración para tener alguna posibilidad de garantizar que Dispose se llama. Esto es esencialmente como engancharse con la pila (como finalizador es para el GC, using es a la pila).

La parte que falta es que debe escribir manualmente Dispose y hacer que llame a sus campos y 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 anidan perfectamente y no son seguros para subprocesos (aparte de cualquier otra cosa, evitar IDisposable te 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, escribe 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 creara su propia estructura de control para cumplir el mismo propósito que using, excepto que ya no corre el riesgo de que la persona que llama lo abuse. No hay forma de que no puedan 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 eliminar el recurso A y luego eliminar el recurso B. No puede hacer eso si ha obligado al usuario a anidar perfectamente así. Pero luego necesitas usar IDisposable (pero aún sin un finalizador, a menos que haya implementado threadsafety, que no es gratis).

Respondido 23 Feb 12, 14:02

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

El modismo que utilizo para implementar IDisposable (no seguro para subprocesos):

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
}

Respondido 18 Oct 13, 15:10

Esta es casi la implementación del patrón de Microsoft Dispose, excepto que ha olvidado hacer que DIspose (bool) sea virtual. El patrón en sí no es un patrón muy bueno y debe evitarse a menos que sea absolutamente necesario disponer como parte de una jerarquía de herencia. - MikeJ

If MyCollection va a ser recogida de basura de todos modos, entonces no debería necesitar tirarla. Si lo hace, solo agitará la CPU más de lo necesario e incluso puede invalidar algunos análisis precalculados que el recolector de basura ya ha realizado.

yo suelo IDisposable para hacer cosas como asegurarse de que los subprocesos se eliminen correctamente, junto con los recursos no administrados.

EDITAR En respuesta al comentario de Scott:

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

Conceptualmente, el GC mantiene una vista del gráfico de referencia del objeto y todas las referencias a él desde los marcos de pila de los hilos. Este montón puede ser bastante grande y ocupar 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 kernel cuando los datos de una página cambian, por lo que sabe que la página está sucia y requiere una nueva exploración. 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 en contra de la disposición innecesaria de recursos: imagine una situación en la que se está descargando un proceso. 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 intercambiado en el disco. Como mínimo, 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 intercambie todos esos datos y páginas de códigos de nuevo en 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 activos los subprocesos que no están en segundo plano; de lo contrario, el proceso permanecerá activo.

Ahora, durante la ejecución normal hay recursos efímeros que deben limpiarse correctamente (como señala @fezmonkey conexiones de base de datos, enchufes, manijas de ventana) para evitar pérdidas de memoria no administradas. Este es el tipo de cosas que hay que desechar. Si crea alguna clase que posee un hilo (y por propietario me refiero a 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 IDisposable y romper el hilo durante Dispose.

El marco .NET utiliza el IDisposable interfaz como una señal, incluso una advertencia, a los desarrolladores de que esta clase deber ser desechado. No puedo pensar en ningún tipo en el marco que implemente IDisposable (excluidas las implementaciones de interfaz explícitas) donde la eliminación es opcional.

Respondido 12 Feb 09, 12:02

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

Para muchas clases .net, la eliminación es "algo" opcional, lo que significa que abandonar las instancias "normalmente" no causará ningún problema siempre y cuando 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 crean instancias de los controles y las abandona cuando se eliminan los formularios; si uno crea y elimina miles de controles, esto podría inmovilizar miles de identificadores GDI, pero en la mayoría de los casos los controles no se crean ni destruyen tanto. No obstante, uno debería intentar evitar ese abandono. - Super gato

En el caso de las fuentes, sospecho que el problema es que Microsoft nunca definió realmente qué entidad es responsable de eliminar el objeto "fuente" asignado a un control; en algunos casos, los controles pueden compartir una fuente con un objeto de mayor duración, 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 la elimina, 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 identificador GDI de su fuente. - Super gato

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 (es decir, una vez que una instancia de MyCollection sale del alcance). .Clear() llamadas.

Respuesta a su edición: algo así. 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 necesitas liberar la memoria en este mismo instante, llama GC.Collect(). Sin embargo, no hay razón para hacer esto aquí. La memoria se liberará cuando sea necesario.

Respondido 12 Feb 09, 00:02

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 la memoria está verdaderamente necesario. Liberarlo ahora mismo puede no ser esencial, pero puede ser útil. - Jesse Chisholm

Hay algunos casos de esquina en los que anular las 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 necesario durante mucho tiempo después de eso, abandonar la matriz puede hacer que esos elementos se mantengan hasta el siguiente GC de nivel 2, mientras que ponerlo a cero primero puede hacer que los elementos 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 ... - Super gato

... poner a cero tales matrices antes de abandonarlas, a veces puede disminuir el impacto de GC. - Super gato

En el ejemplo que publicaste, todavía no "libera la memoria ahora". Toda la memoria se recolecta como basura, pero puede permitir que la memoria se recolecte en una Generacion. Tendrías que hacer algunas pruebas para estar seguro.


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

Una vez leí un código que era un simple RollBack () en caso de falla utilizando IDisposable. La clase MiniTx a continuación verificaría una bandera en Dispose () y si el Commit la llamada nunca sucedió, luego llamaría Rollback en sí mismo. 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 algo así:

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

    tx.Commit();
} 

También he visto que el código de cronometraje / 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 realizan ninguna limpieza de recursos no administrados, pero sí usaron con éxito IDisposable para crear un código más limpio.

Respondido 12 Feb 09, 00:02

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

Si deseas eliminar ahora mismo, Utilizar memoria no administrada.

Ver:

Respondido el 04 de junio de 13 a las 01:06

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

Public Class LargeStuff implementa IDisposable Private _Large as string () 'Algún código extraño que significa que _Large ahora contiene varios millones de cadenas largas. Public Sub Dispose () implementa IDisposable.Dispose _Large = Nothing End Sub

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

Respuesta: Ninguno.
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, seguir el patrón anterior sigue siendo una buena idea de hecho. Una vez que se ha ejecutado Dispose, no hay nada que impida que el GC vuelva a reclamar la memoria que estaba siendo utilizada por _Large, aunque la instancia de LargeStuff todavía puede estar dentro del alcance. Las cadenas en _Large también pueden estar en la generación 0, pero la instancia de LargeStuff podría ser la generación 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 solo DEMORA la recuperación de la memoria para permitir que el finalizador se ejecute.

Respondido 12 Feb 09, 00:02

Si una instancia de LargeStuff ha existido el tiempo suficiente para llegar a la Generación 2, y si _Large contiene una referencia a una cadena recién creada que está en la Generación 0, entonces si la instancia de LargeStuff se abandona sin anular _Large, luego cadena referida por _Large se mantendrá hasta la próxima colección Gen2. Poniendo a cero _Large puede permitir que la cadena se elimine en la próxima colección Gen0. En la mayoría de los casos, anular las referencias no es útil, pero hay casos en los que puede ofrecer algún beneficio. - Super gato

Aparte de su uso principal como una forma de controlar el toda la vida of recursos del sistema (completamente cubierto por la asombrosa respuesta de Ian, felicitaciones!), el IDisposable / usando combo también se puede utilizar para alcance el cambio de estado de los recursos globales (críticos): el consolar,la hilos,la Procesos, alguna objeto global como un instancia de aplicación.

Escribí 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 en un Reutilizable y legible manera: colores de la consolaactual cultura del hilo, Propiedades del objeto de aplicación de Excel...

Respondido el 15 de junio de 13 a las 17:06

En todo caso, espero que el código sea menos eficiente que al dejarlo fuera.

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

Respondido 11 Feb 09, 23:02

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

Si los objetos referenciados por _theList or _theDict son referidos por otros objetos, entonces que List<> or Dictionary<> El objeto no estará sujeto a colección, pero de repente no tendrá contenido. Si no hubiera una operación Dispose () como en el ejemplo, esas colecciones aún tendrían su contenido.

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

Respondido 11 Feb 09, 21:02

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, por lo 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 de captador, tal vez demasiado (algunas personas consideran que los captadores / definidores son un poco antipatrones). - Michael Burr

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

En cambio, uno debe reconocer lo que todos los recursos administrados tienen en común: todos implican que un objeto le pide a una 'cosa' externa que haga algo en su nombre, en detrimento de algunas otras 'cosas', y la otra entidad acepta 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 necesita alterar su comportamiento en nombre del objeto que ya no existe; en consecuencia, la utilidad de la cosa se vería disminuida permanentemente.

Un recurso no administrado, entonces, representa un acuerdo por parte de alguna "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 administrado es un objeto que es el beneficiario de dicho acuerdo, pero que se ha suscrito para recibir una notificación si se abandona, y que utilizará dicha notificación para poner sus asuntos en orden antes de que sea destruido.

Respondido 22 Feb 12, 10:02

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

@Eonil: ¡Objeto no administrado! = Recurso no administrado. Cosas como los eventos se pueden implementar por completo utilizando 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. . - Super gato

IDisposable es bueno para cancelar la suscripción a eventos.

respondido 22 nov., 12:19

Primero de definición. Para mí, recurso no administrado significa alguna clase, que implementa la 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 la interfaz IDisposable para limpiar la memoria.
  2. Limpiar los objetos tan pronto como termine de usarlos.
  3. En mi método de disposición, itero sobre todos los miembros IDisposable de la clase 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 agrego un miembro privado _disposed y verifico las llamadas al método si el objeto se limpió. Y si se limpió, genere ObjectDisposedException
    La siguiente plantilla demuestra 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;
        }
    }

Respondido 31 ago 15, 15:08

"Para mí, recurso no administrado significa alguna clase, que implementa la interfaz IDisposable o algo creado con el uso de llamadas a dll". Entonces estás diciendo que cualquier tipo que is IDisposable ¿Debería considerarse en sí mismo un recurso no gestionado? 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 incorrecto. - Aluan Haddad

Todo el mundo juzga por sí mismo. No me gusta agregar algo al código mío solo por agregar. 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

Su muestra de código dada no es un buen ejemplo para IDisposable uso. Borrar diccionario normalmente no debería ir a la Dispose método. Los elementos del diccionario se borrarán y eliminarán cuando salgan de su alcance. IDisposable Se 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 de patrón IDisposable con 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.
    }
}

contestado el 26 de mayo de 17 a las 08:05

Veo que muchas respuestas han cambiado 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 debería usarse 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 se deshaga de un IDisposable, 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 en objetos administrados y ha bloqueado ese hilo esperando que se complete alguna operación. Si esos objetos no serán necesarios después de que se complete la llamada, establecer esas referencias en nulo podría permitir que el recolector de basura los recopile antes. Pero ese escenario representaría un código incorrecto que necesita ser refactorizado, no un caso de uso de IDisposable.

Respondido 03 Oct 18, 20:10

No entendí por qué alguien puso -1 en tu respuesta. Sebastián Oscar López

El caso de uso más justificable para la eliminación de recursos administrados es la preparación para que el GC recupere recursos que de otro modo 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 'secundario' que tiene una referencia a su 'padre', esto puede detener la recopilación de GC del padre si simplemente abandona la referencia y confiar 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 de los padres en nulo en los hijos.

Implementar IDisposable en padres e hijos es la mejor manera de hacer esto. Cuando se llama a Dispose en el padre, llame a Dispose en todos los hijos y, en el método Dispose del hijo, establezca las referencias de padre en nulo.

Respondido el 14 de Septiembre de 16 a las 08:09

En su mayor parte, el GC no funciona identificando objetos muertos, sino identificando los vivos. Después de cada ciclo de 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 una WeakReference, el sistema verificará una bandera que indica que se encontró una referencia enraizada en vivo en el último ciclo de GC, y agregará el objeto a una cola de objetos que necesitan finalización inmediata, liberará el objeto del montón de objetos grandes o invalidará el débil referencia. Las referencias circulares no mantendrán vivos los objetos si no existen otras referencias. - Super gato

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.