Pregunta Uso adecuado de la interfaz IDisposable


Lo sé por leer la documentación de MSDN que el uso "primario" de la IDisposable la interfaz es para limpiar recursos no administrados.

Para mí, "no administrado" significa cosas como conexiones de bases de datos, tomas de corriente, manejadores de ventanas, etc. Pero, he visto código donde el Dispose() método se implementa para liberar manejado 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 la memoria libre del recolector de basura utilizada por MyCollection más rápido de lo normal?

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 supongamos que _theList en el código anterior contenía un millón de cadenas, y querías liberar esa memoria ahora, en lugar de esperar al recolector de basura. ¿El código anterior lograría eso?


1378
2018-02-11 18:12


origen


Respuestas:


El punto de desechar es 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 fuiste a hurgar en MSDN, no está administrado. Cualquier cosa que hayas usado P / Invoca llamadas para salir del agradable y cómodo mundo de todo lo que está disponible para ti en .NET Framwork no es administrado, y ahora eres responsable de limpiarlo.

El objeto que has creado necesita exponer algunos método, que el mundo exterior puede llamar, para limpiar los recursos no administrados. El método se puede nombrar como prefiera:

public void Cleanup()

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 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 tu estas listo. Excepto que puedes hacerlo mejor.


¿Qué pasa si su objeto ha asignado un 250 MB System.Drawing.Bitmap (es decir, la clase de mapa de bits administrada por .NET) ¿como un 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 sentados allí, esperando que el recolector de basura finalmente ven y libera? ¿Qué pasa si hay un conexión de base de datos abierta? Seguramente no queremos que esa conexión se abra, 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 bases de datos derrochadores?

Entonces ahora vamos a:

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

Así que actualicémonos Dispose() método para deshacerse de esos objetos gestionados:

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() en tu objeto? Entonces ellos perderían algo no administrado recursos!

Nota: No van a tener fugas manejado recursos, porque finalmente el recolector de elementos no utilizados se ejecutará, en una cadena de fondo, y liberará la memoria asociada con los objetos no utilizados. Esto incluirá su objeto y cualquier objeto administrado que use (por ejemplo, Bitmap y el DbConnection)

Si la persona olvidó llamar Dispose(), podemos todavía ¡salva su tocino! Todavía tenemos una forma de llamarlo para ellos: cuando el recolector de basura finalmente consigue 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, o   preocuparse tu  Disponer método.   Ese era solo un nombre que elegimos para   un método que llamamos cuando queremos obtener   deshacerse de cosas no administradas.

La destrucción de nuestro objeto por el recolector de basura es la Perfecto tiempo 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.   Usted escribe un método que parece un Destructor C ++, y el   compilador toma eso para ser 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. Usted ve, el recolector de basura se ejecuta en una hilo de fondo; no sabes el orden en el que se destruyen dos objetos. Es completamente posible que en su Dispose() código, el manejado el objeto del que intenta deshacerse (porque quería ser útil) ya no existe:

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() decir Dispose() que debería no tocar ningún administrado recursos (porque podría no estar allí más), al mismo tiempo que libera recursos no administrados.

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

Esta interno método podría recibir un nombre arbitrario como "CoreDispose" o "MyInternalDispose", pero es tradicional 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 cambias tu 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 tu objeto desciende de un objeto que implementa Dispose, entonces no te olvides de llamar a su base Deseche el método cuando anule Dispose:

public 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() en su objeto, entonces todo se ha limpiado. Más adelante, cuando aparezca el recolector de basura y llame a Finalize, se llamará Dispose de nuevo.

No solo es un desperdicio, sino que si su objeto tiene referencias basura a los objetos que ya eliminó del último llamar a Dispose(), ¡tratarás de eliminarlos de nuevo!

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

Cuando el usuario llama Dispose(): el mango 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 arreglas esto es decirle al recolector de basura que no necesita molestarse en finalizar el objeto: sus recursos ya han sido limpiados, y no se necesita más trabajo. Usted hace esto llamando GC.SuppressFinalize() en el 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 el GC ejecute el finalizador; todo está solucionado.

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

La documentación para Object.Finalize dice:

El método Finalizar se utiliza para realizar operaciones de limpieza en recursos no administrados que posee el objeto actual antes de que se destruya el objeto.

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

Realiza tareas definidas por la aplicación asociadas con liberar, liberar o restablecer 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 tienes idea de cuándo el recolector de basura se ocupará de finalizar tu objeto. Sus recursos nativos no administrados, no necesarios y no utilizados se mantendrán hasta que el recolector de basura finalmente carreras. Luego llamará a su método de finalizador; limpiando 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 determinística de los recursos para las instancias de su clase, implemente un Cerca método o proporcionar un IDisposable.Dispose implementación.

Esta es la virtud de usar Dispose para limpiar recursos no administrados; se llega a conocer y controlar cuando 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 cuándo el GC decide hacerlo? Tengo un software de reconocimiento facial que necesariamente deshacerse de 530 MB de imágenes internas ahora, ya que ya no son necesarios. Cuando no lo hacemos: la máquina se detiene bruscamente.

Lectura de bonificación

Para cualquier persona que le gusta el estilo de esta respuesta (explicando el por qué, entonces el cómo se vuelve obvio), le sugiero que lea el Capítulo Uno de COM esencial de Don Box:

En 35 páginas explica los problemas del uso de objetos binarios e inventa COM ante tus ojos. Una vez que te das cuenta de la 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 Bono

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 darte es no intentar.


2286
2018-02-11 18:20



IDisposable a menudo se usa para explotar el using declaración y aprovechar una forma fácil de hacer limpieza determinista de objetos gestionados.

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

54
2018-02-11 18:42



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 llama al método Dispose. En su ejemplo, el uso de Dispose no está realmente haciendo nada relacionado con la eliminación, ya que borrar una lista no tiene ningún impacto en que se elimine esa colección. Del mismo modo, las llamadas para establecer las variables a nulo tampoco tienen impacto en el GC.

Puedes echarle un vistazo a esto artículo para 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 el Dispose (bool), que en realidad se ejecuta en dos circunstancias diferentes:

  • disponer == verdadero: el método ha sido llamado directa o indirectamente por un código de usuario. Los recursos administrados y no administrados pueden ser eliminados.
  • disposing == false: el método ha sido llamado por el tiempo de ejecución desde el finalizador, y no debe hacer referencia a otros objetos. Solo los recursos no administrados pueden ser eliminados.

El problema de simplemente dejar que el GC se encargue de la limpieza es que no tiene control real sobre cuándo el GC ejecutará un ciclo de recopilación (puede llamar a GC.Collect (), pero realmente no debería) para que los recursos puedan permanecer. alrededor de más tiempo de lo necesario. Recuerde, llamar a Dispose () en realidad no causa un ciclo de recolección o de alguna manera causa 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 inmediatamente la memoria. La única vez que una llamada a Dispose realmente tendrá la posibilidad de liberar la memoria inmediatamente es cuando maneja el escenario de eliminación == falso y manipula los recursos no administrados. Para el código administrado, la memoria no se recuperará realmente hasta que el GC ejecute un ciclo de recopilación, sobre el que 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 ningún recurso no controlado y no implementan IDisposable, no hay forma de obligarlos a ser "limpiados".


36
2018-02-11 20:21



No debe haber más llamadas a los métodos de un objeto después de que se haya llamado a Dispose en él (aunque un objeto debería tolerar otras llamadas a Dispose). Por lo tanto, el ejemplo en la pregunta es tonto. Si se llama a Dispose, entonces el objeto mismo puede descartarse. Por lo tanto, el usuario debería 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 gestionado y la discusión en otras respuestas, creo que cualquier respuesta a esta pregunta debe comenzar con una definición de recurso no administrado.

Lo que se reduce a esto es 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 sacarla 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 que coincidan. Uno construye un estado, el otro lo derriba. Si el estado se ha construido pero no se ha derribado aún, entonces existe una instancia del recurso. Debe organizar el desmontaje en el momento correcto: el recurso no está gestionado 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 gestionados por la pila (o conectando un paseo dentro de los tipos de referencia), y los tipos de referencia son gestionados por el GC.

Estas funciones pueden causar cambios de estado que pueden intercalarse libremente o pueden necesitar estar perfectamente anidados. Los cambios de estado pueden ser peligrosos o pueden no serlo.

Mire el ejemplo en la pregunta de Justice. Los cambios en la sangría del archivo de registro deben estar perfectamente anidados, o todo va mal. Además, es poco probable que sean enhebrables.

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

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

Luego debes usar el using declaración para tener alguna posibilidad de garantizar que Dispose se llama. Esto es esencialmente como andar en bicicleta con la pila (así como el finalizador es para la GC, using es para la pila).

La parte que falta es que tienes que escribir Dispose manualmente y hacer que llame a tus campos y a tu 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.

Existe una alternativa, que prefiero para los estados que anidan perfectamente y que no son seguros (aparte de cualquier otra cosa, evitando los recambios descartables, 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 devolver la llamada a:

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

Y luego un simple ejemplo 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 tiene ningún peligro de que la persona que llama lo abuse. No hay manera 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 se desea poder construir el recurso A, luego el recurso B, luego matar el recurso A y luego matar el recurso B. No se puede hacer eso si has forzado 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).


17
2018-02-11 19:31



Escenarios Uso de IDisposable: limpiar recursos no administrados, cancelar suscripción para eventos, cerrar conexiones

El modismo que uso para implementar IDisposable (no es peligroso)

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
}

14
2018-02-11 18:20



Si MyCollection va a ser basura recogida de todos modos, entonces no debería necesitar deshacerse de ella. Si lo hace, simplemente agitará la CPU más de lo necesario e incluso puede invalidar algún análisis precalculado que el recolector de basura ya haya realizado.

yo suelo IDisposable hacer cosas como asegurar que los hilos se eliminen correctamente, junto con recursos no administrados.

EDITAR En respuesta al comentario de Scott:

La única vez que se ven afectadas las métricas de rendimiento del GC es cuando se realiza una llamada al [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 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 las páginas que es poco probable que cambien muy a menudo para evitar volver a explorar la página innecesariamente. El GC recibe notificación del kernel 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. Anecdóticamente, estos ganchos no estaban disponibles en Mac OS X para el equipo que portó el GC a Mac para poder utilizar el complemento de Silverlight en esa plataforma.

Otro punto en contra de la eliminación innecesaria de recursos: imagine una situación en la que un proceso se está descargando. Imagine también que el proceso ha estado funcionando por un tiempo. Lo más probable es que muchas de las páginas de memoria de ese proceso se hayan intercambiado en el disco. Por lo menos, ya no están en caché L1 o L2. En tal situación, no tiene sentido que una aplicación que está descargando intercambie todos esos datos y páginas de códigos en la memoria para 'liberar' los recursos que el sistema operativo va a liberar cuando el proceso finaliza. Esto se aplica a recursos administrados e incluso ciertos no administrados. Solo se deben eliminar los recursos que mantienen vivos los subprocesos que no son de fondo, 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 bases de datos, tomas de corriente, manejadores de ventanas) para evitar fugas de memoria no administradas. Estas son las clases de cosas que tienen que ser eliminadas. Si creas una clase que posee un hilo (y por propiedad quiero decir que lo creó y por lo tanto es responsable de asegurar que se detenga, al menos por mi estilo de codificación), entonces esa clase probablemente debe implementar IDisposable y derribar el hilo durante Dispose.

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


11
2018-02-11 18:19



Sí, ese código es completamente redundante e innecesario y no hace que el recolector de basura haga algo que de otro modo no haría (una vez que una instancia de MyCollection queda fuera del alcance, eso es). Especialmente el .Clear() llamadas.

Responda 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 propósitos 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 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.


10
2018-06-03 21:07



Si quieres eliminar ahora, utilizar memoria no administrada.

Ver:


7
2018-02-11 21:08



No voy a repetir lo de costumbre sobre el uso o la liberación de recursos no gestionados, que todo ha sido cubierto. Pero me gustaría señalar lo que parece una idea errónea común.
Dado el siguiente código

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

  'Un código extraño que significa _grande 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 entiendan la idea.
Ahora, cuando se llama Dispose, ¿cuánta memoria se libera?

Respuesta: ninguna.
Calling Dispose puede liberar recursos no administrados, NO PUEDE reclamar la memoria administrada, solo el GC puede hacerlo. Eso no quiere decir que lo anterior no es una buena idea, siguiendo el patrón anterior es 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 aún esté 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 volvería a reclamarse antes.
Sin embargo, no tiene sentido agregar un finalizador para llamar al método Dispose que se muestra arriba. Eso simplemente DEMORARá el reclamo de memoria para permitir que se ejecute el finalizador.


6
2018-02-11 21:07



En el ejemplo que publicó, todavía no "libera la memoria ahora". Toda la memoria es basura, pero puede permitir que la memoria se recopile en un Generacion. Tendrás que ejecutar algunas pruebas para estar seguro.


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

Una vez leí el código que era un simple RollBack () sobre la falla al utilizar IDisposable. La clase MiniTx a continuación verificaría una bandera en Dispose () y si el Commit llamada nunca sucedió, 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 comprender y mantener. El resultado se veía algo así como:

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

    tx.Commit();
} 

También he visto que el código de sincronización / 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 administrados, pero se usan con éxito IDisposable para crear un código más limpio.


5
2018-02-11 20:32