Inicio > .NET Framework, Apunte, C# > Gestión de memoria y recursos en .NET

Gestión de memoria y recursos en .NET


Una de las características más importantes de C# y .NET es que tanto lenguaje como plataforma gestionan la memoria en automático. Para quienes venimos de C++, estábamos muy acostumbrados a gestionar nuestros propios recursos.

class address {
    public:
        std::wstring street;
        int number;
        std::wstring city;
        std::wstring postal_code;

        address() { … }
        address(const address& copy) { … }
        ~address() { }

        bool full_address () const { … }
        bool validate() const;
};

class employee {
    public:
        std::wstring name;
        int id;
        double income;
        address* main_address;
        address* office_address;

        employee() { … }
        ~employee() {
            if (main_address != nullptr)  
                delete main_address;    // liberar memoria
            if (office_address != nullptr)
                delete office_address;  // liberar memoria
        }

    …
};

employee* e = new employe();
e->name = L"Fernando Gómez";
e->income  = 500.00;
e->id = 42;
e->main_address = new address();
e->main_address->street = "Odessa";
e->main_address->number = 42;
e->main_address->postal_code = "38102";
e->office_address = nullptr;

// hacer algo con e

delete e; // liberar memoria

Este es un ejemplo sencillo. La clase address tiene un destructor que no hace nada, pero la clase employee tiene un destructor que sí hace algo: libera la memoria de los punteros existentes main_address y office_address. En C++ estábamos acostumbrados a liberar la memoria de forma manual, invocando la sentencia delete, como se ha visto. También estábamos acostumbrados, por supuesto, a que a alguien se le olvidada liberar la memoria y por tanto teníamos fugas de memoria.

En .NET, por supuesto, todo esto cambió. La máquina virtual de .NET incorpora un componente, llamado el colector de basura (garbage collector, GC), cuyo propósito es liberar la memoria, previo análisis de recursos. En palabras sencillas, el programador ya no tiene que preocuparse por liberar los recursos de memoria, y por tanto ya nunca tendremos fugas de memoria.

Lo cuál, por supuesto, es algo falso.

Fugas de memoria en .NET

En .NET uno piensa que no hay fugas de memoria. En principio esto es cierto. Pero en general lo es sólo para las clases y componentes más sencillos. En efecto, cualquier componente complejo que use recursos de sistema, puede presentar fugas de memoria.

Para entender las fugas, debemos hacer algunas definiciones. Primero, diremos que cuando una liberación de recursos se ejecuta al instante, en cuanto se invoca, entonces ésta es una liberación determinista de recursos. Por ejemplo, la sentencia delete de C++ que vimos arriba es una liberación determinista: en cuanto llegamos a la sentencia, en ese momento se invocan destructores en orden, uno a uno, y se liberan los recursos. Una liberación no determinista es, por supuesto, cuando un recurso no se libera al instante. Es decir, se da la orden de liberar, pero no se hace al momento, e incluso puede tardar tiempo en ejecutarse. Se infiere que una liberación no determinista es totalmente asíncrona, respecto del proceso que contiene el elemento a liberar.

Pues bien, en .NET tenemos el colector de basura. Este colector libera los recursos, y lo hace de forma no determinista. Las reglas bajo las cuales esto funciona son algo complejas, pero básicamente se fija si un objeto es alcanzable o no. Por ejemplo:

class A
{
}

class B 
{
    public A a;
}

B b = new B();    // se ubica B en memoria.
b.a = new A();    // se ubica A en memoria.
…
b.a = null;       // ya no hay forma de recuperar a A. 

Este sencillo ejemplo es ilustrativo. B contiene un objeto A. Creamos un objeto B, y luego creamos un objeto A y se lo asignamos a B.a. Después de unas operaciones cualesquiera, asignamos B.a a null. En este momento, ya no hay forma de recuperar una referencia a A. Aunque el objeto A siga vivo, ya no puede recuperarse. A partir de este momento, el colector de basura puede decidirse a liberar o reclamar los recursos. Pero puede decidir no hacerlo, e incluso puede pasar mucho tiempo antes de que suceda. Es más, puede ser que el objeto B sea reclamado antes que A. Pero el punto es que a partir de ahí, como dicen en EE. UU., all bets are off.

Entonces, según lo anterior, ¿cómo puede haber fugas de memoria? La respuesta pasa por entender que una fuga de memoria nunca es permanente. En los sistemas operativos actuales, modernos, la memoria es liberada automáticamente por éstos, cuando el hilo ejecutor termina. Así, las fugas de memoria de hoy en día hacen más referencia a la pérdida temporal de memoria. Es decir: tenemos un objeto que debería ser liberado de memoria, y como no lo hacemos, vamos consumiendo más y más memoria. En casos extremos, ¡hasta podemos acabárnosla!

Es en este sentido que .NET permite las fugas de memoria. Considera este ejemplo. Tenemos una clase que utiliza un recurso de sistema (por ejemplo, usamos el sistema de geo-localización que viene con Windows 7). Estos recursos de sistema, como son provistos por el sistema operativo, no son administrados. Esto quiere decir que son responsabilidad del programador liberarlos (por supuesto, un recurso administrado es un recurso que el colector de basura libera en automático de forma no determinista).

class GeoLocalizador
{
    private IntPtr _manejadorSistema;

    public GeoLocalizador() {
        _manejadorSistema = InvocarApiDeSistema();
    }

    ...
}

Esto nos presenta un dilema, por supuesto. Cuando instanciamos nuestra clase GeoLocalizador adquirimos recursos no administrados, representados por la variable miembro _manejadorSistema de tipo IntPtr (usada para los famosos HANDLEs del API de Win32). Sin embargo, dado que el colector de basura se encarga de liberar la memoria administrada, pero no los recursos no administrados, los recursos de _manejadorSistema nunca serán liberados (hasta que termine el proceso, como pasaría con cualquier programa de Windows). Esto, mis estimados, es una fuga de memoria.

Liberación no determinista de recursos

¿Qué hacemos entonces para liberar estos recursos? En C++ tenemos el concepto de destructor. Un destructor es un método especial de una clase que SIEMPRE es invocado cuando el objeto deja de existir (bien porque sale de alcance, bien porque se invoca una sentencia delete). Es la contraparte del constructor.

En C# tenemos un concepto similar: los finalizadores. En principio, el finalizador es el mismo concepto de un destructor, y quienes venimos de C++ solemos llamarlos con este segundo nombre, probablemente por nostalgia. Pero gente fuera de C++ y C# los conoce como finalizadores (muy particularmente, la gente que viene de Visual Basic .NET).

En .NET, la clase Object tiene un método protegido virtual, llamado Finalize. ¿Qué te crees? Pues que ese método es, justamente, ¡el finalizador! Y como todas las clases heredan de Object, pues entonces todas las clases tienen su método Finalize. Para liberar recursos cuando el objeto sea reclamado, bastará con sobrescribir al método Finalize y colocar ahí el código para liberar.

class GeoLocalizador
{
    private IntPtr _manejadorSistema;

    public GeoLocalizador() {
        _manejadorSistema = InvocarApiDeSistema();
    }

    protected override void Finalize() {
        InvocarLiberación(ref _manejadorSistema);
        base.Finalize();
    }

    ...
}

Fácil, ¿no? Si escribes un código similar… éste no compilará. ¡Auch! Y es que resulta que por conveniencia, C# define una sintaxis especial para los finalizadores, y por tanto no puede sobrescribirse Finalize. Esto por un par de razones. Primero, para emular la sintaxis de C++. Segundo, para evitar que algún programador olvide llamar a base.Finalize, o que declaren como sealed al método y no sealed a la clase. Así, como decía, tenemos que usar una sintaxis similar a la de C++:

class GeoLocalizador
{
    private IntPtr _manejadorSistema;

    public GeoLocalizador() {
        _manejadorSistema = InvocarApiDeSistema();
    }

    ~GeoLocalizador() {
        InvocarLiberación(ref _manejadorSistema); // ok
    }

    ...

}

 

Como ves, el finalizador utiliza el símbolo ~ seguido del nombre de la clase. No puede tener modificadores, como public, protected, virtual, etc. Y ya no es necesario invocar a la clase base: el compilador lo hace por nosotros.

Una nota importante, sobre todo si vienes de C++. En este lenguaje, se recomienda siempre incluir un destructor, aunque éste esté vacío, por buena práctica. Todo lo contrario en C#: incluye destructores (i.e. finalizadores) sólo cuando vayas a liberar recursos. Si no, abstente. Esto, porque el colector de basura es más eficiente cuando no tiene que ejecutar finalizadores, por lo que incluir uno vacío sólo ralentiza el proceso de liberación de memoria.

Cabe resaltar que incluir finalizadores, aunque nos garantiza que nuestros recursos no administrados se liberen, no nos garantiza cuándo. Es decir, en un escenario tradicional como en nuestra clase GeoLocalizador, incluir el finalizador nos garantiza que no exista una fuga de memoria: después que la instancia muera, el objeto será reclamado y por tanto el recurso no administrado será liberado. Pero no nos garantiza que nuestra memoria sea óptima.

Considera esta situación: tienes una forma de Windows Forms donde capturas los datos de contacto de una persona. Entre esos datos, quieres capturar sus coordenadas geográficas, por lo que incluyes un botón que al hacer clic, utilice la clase GeoLocalizador para obtener las coordenadas. Ese método podría lucir así:

private _geolocButton_Click(object sender, EventArgs args)
{
    GeoLocalizador loc = new GeoLocalizador();
    loc.CalcularPosicion();
    _longText.Text = loc.Longitud.ToString();
    _latText.Text = loc.Latitud.ToString();
}

En el método creamos instancias de GeoLocalizador. Una vez que el flujo del programa sale del método, GeoLocalizador queda referenciada y por tanto es susceptible de ser reclamada por el colector de basura, y por tanto, cuando eso ocurra, se liberarán los recursos no administrados. Pero como ya vimos que esto puede no ser instantáneo, imaginemos: ¿qué pasa si el GC decide no colectar objetos, y el usuario hace clic 10 veces en el botón? ¡Tendremos 10 instancias con memoria sin liberar! Más aún: ¿qué pasa si el sistema operativo sólo permite una instancia del geo localizador? Tendríamos que esperarnos a que el GC colectara para poder volver a utilizar la clase GeoLocalizador, y no hay forma de saber cuándo ocurrirá eso.

En situaciones similares, vemos que a veces es necesario contar con liberación de recursos de forma determinista.

Liberación determinista de recursos

Una liberación determinista debería ser muy sencilla. O más o menos. Es decir, al final, si queremos liberar un recurso, podemos simplemente crear un método LiberarRecursos y ya, ¿no?

class GeoLocalizador
{
    private IntPtr _manejadorSistema;

    public GeoLocalizador() {
        _manejadorSistema = InvocarApiDeSistema();
    }

    ~GeoLocalizador() {
        InvocarLiberación(ref _manejadorSistema); // ok
    }

    public double Latitud { get; private set; }
    public double Longitud { get; private set; }

    public void CalcularPosicion() { 
        if (_manejadorSistema == null)
            throw new InvalidOperationException("No hay manejador. ");

        … // etc
    }

    public void LiberarRecursos() {
        if (_manejadorSistema != null) {
            InvocarLiberación(ref _manejadorSistema);
            _manejadorSistema = null;
        }
    }

    ...
}

GeoLocalizador loc = new GeoLocalizador();
loc.CalcularPosicion();
_longText.Text = loc.Longitud.ToString();
_latText.Text = loc.Latitud.ToString();
loc.LiberarRecursos();

Pues sí, hemos alcanzado una liberación determinista de esta forma. Al final, invocamos LiberarRecursos y pues ya liberamos los recursos, ¿no? Hay que tener cuidado, sin embargo, porque en teoría una vez que invocamos LiberarRecursos hemos dejado el objeto en un estado inválido. Es decir, en teoría una vez liberado, ya no debería ser posible ser utilizado. Sin embargo, C# no nos impide hacer esto:

GeoLocalizador loc = new GeoLocalizador();
…
loc.LiberarRecursos();
loc.CalcularPosicion(); // ¡oh-oh!

Después de LiberarRecursos, si invocamos a CalcularPosicion pues ya no tendría sentido y de hecho nuestro recurso no administrado ya está liberado y no podemos volver a utilizarlo. De hecho hemos puesto algunas salvaguardas para evitar que suceda: revisamos que si la variable interna en nula, lanzamos un InvalidOperationException. Sin embargo, nota que si se invoca a LiberarRecursos dos veces, no lanzará una excepción, simplemente la segunda vez no pasará nada.

He aquí una idea interesante:

~GeoLocalizador() {
    LiberarRecursos();
}

public void LiberarRecursos() {
    if (_manejadorSistema != null) {
        InvocarLiberación(ref _manejadorSistema);
        _manejadorSistema = null;
    }

}

Pues sí, parece lógico: el finalizador llamará a LiberarRecursos, así que ya tenemos implementada una liberación determinista y una no determinista.

Disponer objetos en .NET

La liberación determinista en .NET tiene un nombre en particular: disponer de un objeto (en inglés, to dispose an object). Y dado que .NET al final engloba muchas de las características del sistema operativo (Windows), es lógico ver que muchos componentes necesitan liberación determinista. Y por tanto es lógico suponer que hay alguna forma estándar de hacerlo.

En efecto, .NET tiene una interfaz muy muy muy importante: IDisposable. Esta interfaz implementa un método Dispose. Este método tiene la misma finalidad que el método LiberarRecursos que nos inventamos hace rato. Así, de esta forma en .NET se estandariza la liberación determinista. Podemos cambiar la clase para adaptarnos.

class GeoLocalizador : IDisposable
{
    private IntPtr _manejadorSistema;

    public GeoLocalizador() {
        _manejadorSistema = InvocarApiDeSistema();
    }

    ~GeoLocalizador() {
        Dispose();
    }

    public void Dispose() {
        if (_manejadorSistema != null) {
            InvocarLiberación(ref _manejadorSistema);
            _manejadorSistema = null;
        }
    }

    ...
}

Ahora sí, tenemos una regla a seguir: siempre siempre sieeeeeeeeeeeempre que veas que una clase en .NET implementa la interfaz IDisposable, lo más probable es que se pretenda que los recursos se liberen de forma determinista. Si tienes un objeto así, cuando ya no necesites el objeto, manda llamar a Dispose. Hazlo así muchcahón / muchachona, y evitarás tener fugas de memoria.

Nota: algunas clases implementan IDisposable, y por su naturaleza, también implementan un método Close. Semánticamente, Close puede significar lo mismo que Dispose (por ejemplo, el cerrar una conexión a base de datos implica liberar los recursos también). Por lo que los métodos Close suelen llamar al Dispose. No pasa nada si primero llamas a Close y luego a Dispose, pero bueno, no es necesario hacer la doble llamada de hecho.

Esto nos presenta un dilema interesante. Supongamos que nuestro método CalculaPosicion puede lanzar un InvalidOperationException cuando el localizador no pueda conectarse al satélite o no pueda hacer una triangulación por WiFi o por cualquier motivo. Entonces corremos el riesgo que el Dispose no se invoque. Pero eso lo podemos solucionar con un buen try-finally o try-catch-finally.

GeoLocalizador loc = null;

try {
    loc = new GeoLocalizador();
    …
} finally {
    if (loc != null)
        loc.Dispose();

}

Nota que para usar esta construcción, tienes que declarar la variable fuera del try-finally.

Los bloques try-catch-finally me gustan, de hecho, pero hay veces en que es medio tedioso hacer esto. Digo, el try-finally es entendible cuando hay un catch de por medio. Pero si no hay catch, puro try-fianlly, parece una construcción muy grande nada más para liberar un recurso. Pues bien, C# tiene un poco de azúcar sintáctica para nosotros: la sentencia using.

Esta sentencia tiene la siguiente forma:

using (declaración = asignación)
{
    // código aquí
}

Dentro del using debe haber una variable, la cual puede estar seguida de una asignación o no. El tipo de dicha variable DEBE implementar la interfaz IDisposable. Si no lo hace, tendrás un error de compilación (i.e. using (int i = 42) { } generará error de compilación). Luego, entre las llaves se pone el código que utiliza la variable en cuestión. Lo interesante sucede cuando alcanzamos la llave de cierre: en ese momento el compilador, tras bambalinas, ¡INVOCA al método Dispose! Precisamente por eso es que el tipo de la variable debe implementar IDisposable. Veamos como quedaría nuestro ejemplo:

using (GeoLocalizador loc = new GeoLocalizacion())
{
    loc.CalcularPosicion();
    _longText.Text = loc.Longitud.ToString();
    _latText.Text = loc.Latitud.ToString();
} // loc.Dispose() llamado tras bambalinas

// aquí loc ya no está disponible

Si estamos seguro que el constructor no lanza excepción, podemos hacerle así:

GeoLocalizador loc = new GeoLocalizacion();
using (loc)
{
    loc.CalcularPosicion();
    _longText.Text = loc.Longitud.ToString();
    _latText.Text = loc.Latitud.ToString();
} // loc.Dispose() llamado tras bambalinas

loc.CalcularPosicion(); // ¡aquí ya lanza una excepción!

Por cierto, en CalcularPosicion pusimos que se lanzara un InvalidOperationException si ya habíamos depuesto el objeto, ¿verdad? Hay una excepción mejor: ObjectDisposedException.

 

public void CalcularPosicion() { 
    if (_manejadorSistema == null)
        throw new ObjectDisposedException();

    … // etc

}

Probablemente también sea mejor cambiar la condición y llevar una variable interna, bool _disposed, que indique si el objeto ya fue depuesto o no. Pero bueno, ya dependerá de ti cómo manejar el estado de tu objeto.

Liberando recursos en objetos compuestos

Hasta ahora hemos visto cómo liberar recursos no administrados vía el destructor, y cómo forzar liberación de recursos vía el método IDisposable.Dispose. Ya vimos también que es una buena práctica que cada que creemos un objeto que implemente IDisposable, invoquemos su método Dispose una vez que dejemos de utilizarlo. Eso está bien cuando creamos objetos en un bloque de código como un método. Pero ¿qué pasa si son miembros de otra clase?

Para ilustrar esto, imaginemos que crearemos una clase llamada Mapa. Esta clase tiene un miembro: GeoLocalizador, a partir del cual pinta la ubicación actual de la persona en el mapa. Asumamos que la clase Mapa tiene que cargar imágenes como recursos no administrados, aprovechando recursos del sistema operativo. Esta clase podría lucir similar a esta.

class Map : IDisposable
{
    private GeoLocalizador _loc; // recurso administrado
    private IntPtr _mapaBits; // recurso no administrado

    public Map() {
        _loc = new GeoLocalizador();
        _mapaBits = InvocarApiDeSistema();
    }

    ~Map() {
        ???
    }

    public void Dispose() {
        ???
    }

    … 
}

Aquí el punto es importante. Tenemos un miembro administrado y uno no administrado. Sí, _loc es administrado desde el punto de vista de Map, puesto que es una clase de .NET. El colector de basura reclamará a _loc en algún momento y ésta liberará recursos, por tanto se considera que es un recurso administrado. ¿Cómo implementamos Dispose en este caso?

Pues bien, primero pensemos en el recurso no administrado. Éste tiene que estar en el finalizador para asegurar que de una u otra forma se liberen los recursos. Ahora bien, la liberación del recurso administrado no tiene sentido hacerse en el finalizador. ¿Por qué? Pues porque puede ser que para cuando se invoque el finalizador ya se haya liberado el objeto GeoLocalizador. Recordemos que los destructores son no determinados y por tanto asíncronos, así que no podemos hacer esto:

~Map() {
    LiberarMapaBitsApi(ref _mapaBits);
    _loc.Dispose(); // en la mauser
}

Eso nos da en la torre, pues si _loc ya fue liberado, no podemos invocar _loc.Dispose toda vez que el objeto ya no existe. Por tanto, regla del mundo: en el finalizador, nunca intentes liberar recursos administrados.

Sin embargo, para la liberación determinista de Map, debemos invocar necesariamente a _loc.Dispose. Esto nos lleva al siguiente dilema: el destructor debería invocar a Dispose para liberar recursos no administrados, y la invocación directa de Dispose debería liberar tanto recursos administrados como no administrados. Es decir, tenemos que diferenciar cuándo estamos liberando de forma determinista y cuando no. Esto podemos hacerlo de esta forma: creamos un método Dispose(bool) protegido, al cuál le pasamos una variable: bool indica que estamos liberando de forma determinsta y por tanto liberamos recursos administrados y no administrados. Un false indica que estamos liberando de forma no determinista, y por tanto sólo liberamos recursos no administrados. Entonces sí: desde Dispose(), invocamos a Dispose(true), y desde el destructor, invocamos a Dispose(false).

class Map : IDisposable
{
    private GeoLocalizador _loc; // recurso administrado
    private IntPtr _mapaBits; // recurso no administrado

    public Map() {
        _loc = new GeoLocalizador();
        _mapaBits = InvocarApiDeSistema();
    }

    ~Map() {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool isDeterministic)
    {
        if (isDeterministic) {
            _loc.Dispose();
        }

        LiberarMapaBitsApi(ref _mapaBits);
        _loc = null;
        _mapaBits = null;
    }

    … 

}

¡¡Braaaaaaaaavo!! ¡Qué inteligente eres Fernando, guaaaau! Ajem… bueno, a decir verdad… esto que hemos hecho es en realidad un patrón de diseño. Ya sabes, como el singleton o el factory. Es un patrón de diseño de .NET, y se llama "Disposable Object Pattern". Es en realidad un patrón de diseño muy utilizado, y es la forma en la que Microsoft recomienda que se gestionen recursos administrados y no administrados.

Por supuesto, esta es una versión incompleta. Hay algunas cosas que decir. En primer lugar, para el nombre de variable yo emplee isDeterministic. Esto, para mantenerlo en concordancia con los términos que hemos utilizado. En la literatura de .NET, sin embargo, suelen utilizar otro nombre: disposing, queriendo decir que disposing == true significa que el método Dispose fue invocado. En fin, por supuesto puedes ponerle el nombre que quieras a la variable, no importa.

En segundo lugar, omitimos en el ejemplo el marcar que el objeto ya ha sido depuesto. Esto es muy importante, no lo olvides: un objeto que ha sido depuesto debe quedar en estado inválido y no debe poder volver a utilizarse. Es la convención, por supuesto. Puedes ir contra ella, pero le estarías dando al traste con la semántica de IDisposable.

Otra cuestión importante: un método Dispose debería ser posible llamarse en varias ocasiones. La implementación debería ser consciente de esto, y NO LANZAR EXCEPCIONES. Por favor. No lances excepciones desde un destructor. No lances excepciones desde el método Dispose. Es buena práctica y sentido común. Esto nos da una razón más para llevar el control sobre si el recurso ha sido liberado o no. ¡Ala pues!

class Map : IDisposable
{
    private GeoLocalizador _loc;
    private IntPtr _mapaBits;
    private bool _disposed;
    private Size _size;

    public Map() {
        _loc = new GeoLocalizador();
        _mapaBits = InvocarApiDeSistema();
        _disposed = false; // naturalmente…
        ...
    }

    ~Map() {
        Dispose(false);
    }

    public void Dispose() {
        Dispose(true);
        GC.SupressFinalize(this); // ¿¡¿y ahora?!?
    }

    protected virtual void Dispose(bool isDeterministic)
    {
        if (!_disposed) {
            if (isDeterministic) {
                _loc.Dispose();
            }

            LiberarMapaBitsApi(ref _mapaBits);

            _loc = null;
            _mapaBits = null;
            _disposed = true;
        } 
        // si ya fue depuesto, no hacemos nada. 
    }

    private void EnsureDisposed()
    {
        if (_disposed)
            throw new ObjectDisposedException();
    }

    public Size Size
    {
        get { 
            EnsureDisposed();
            return _size;
        }
        set {
            EnsureDisposed();
            _size = value;
        }
    }

    public void DrawMap()
    {
        EnsureDisposed();

        … // etc
    }

    … 
}

Bien, podemos ver que creamos una variable _disposed que indica si el objeto ha sido depuesto o no. En nuestro método Dispose(bool), revisamos el valor de esta variable para determinar si liberamos los recursos o no. Si no los liberamos (i.e. _disposed == true) simplemente no hacemos nada, en lugar de lanzar una excepción. Sin embargo, en cualquier otra propiedad o método, revisamos si _disposed es true. De serlo, entonces lanzamos un ObjectDisposedException. Así dejamos súper claro que el objeto ya no sirve.

Por último, vemos que en el método Dispose(), tras la llamada al método interno Dispose(bool), hemos puesto una sentencia que no habíamos visto antes: GC.SupressFinalize. ¿De qué se trata?

En realidad es una forma de mejorar el rendimiento (performance) de la aplicación. System.GC es en realidad una clase que se encarga de exponer algunos métodos estáticos para que podamos dar ciertas instrucciones al colector de basura (GC == Garbage Collector). SupressFinalize es uno de sus métodos, que tiene un parámetro de tipo object. Invocar a ese método es decirle al colector de basura que cuando reclame la memoria del objeto que se pasa como parámetro, ya no se invoque al finalizador (destructor, método Finalize). En este caso, el destructor invoca a Dispose(false). Pero Dispose invoca a Dispose(true), por lo que si ya se liberaron los recursos, no tiene caso que se invoque a Dispose(false) de nueva cuenta. Entonces por eso le decimos al colector de basura que ya ni se moleste. Esto mejorará el rendimiento de la aplicación un poco, porque como hemos dicho, la invocación de finalizadores puede ser costosa.

Naturalmente hay que tener cuidado y no invocar a lo güey SupressFinalize, pues es fácil provocar problemas.

GeoLocalizador loc = new GeoLocalizador();
GC.SupressFianlize(loc); // fuga de memoria asegurada

So say we all

Sean dichas unas últimas palabras antes de cerrar esta entrada. De verdad ten mucho cuidado con la liberación de recursos. En repetidas ocasiones me he topado con programadores que no se preocupan por los recursos, pues .NET los libera. Y claro, cuando liberas una aplicación para 10,000 usuarios y 100,000 transacciones, nos preguntamos por qué está tan lenta. O una aplicación web, nos preguntamos por qué se consume tanta memoria. De verdad, te salvará muchos problemas. Cuando entrevisto programadores, es una de las preguntas que hago, y si no la contestan bien, no continúan. Ya he tenido suficientes problemas por ello como para hacerlo así.

So say we all.

Anuncios
Categorías:.NET Framework, Apunte, C# Etiquetas: , ,
  1. Idebenone
    diciembre 5, 2012 en 10:06 pm

    No supone ninguna ventaja sobre el rendimiento implementar el método Dispose en tipos que utilizan sólo recursos administrados (como matrices) porque el recolector de elementos no utilizados los reclama automáticamente. Utilice principalmente el método Dispose en los objetos administrados que utilizan los recursos nativos y en los objetos COM que se exponen a .NET Framework. Los objetos administrados que utilizan recursos nativos (como la clase FileStream ) implementan la interfaz IDisposable .

  2. diciembre 8, 2012 en 12:30 am

    Por supuesto que no, de rendimiento no. El método Dispose aplica para aquellos objetos que implementan IDisposable. Si un objeto lo implementa, y ese objeto es un campo de tu clase, entonces tienes que implementar IDisposable, preferentemente usando el patrón aquí expuesto. No tienes forma de saber si dicha clase tiene recursos no administrados o no. Es buena práctica que, ante la duda (y la carencia de información en la documentación) se implemente así.

  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s