miércoles, 30 de octubre de 2013

Receta C# No. 1-11: Firma de Assembly Postpuesta

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Enlaces & Literatura

0. Introducción

Ahora queremos que nuestros assemblies sean firmados en una etapa posterior. Esto con el fin de garantizar la posesión de la llave privada sólo por un grupo de miembros (por ejemplo, al personal encargado de hacer las entregas y/o despliegues de los assemblies).

1. Problema

El líder (arquitecto de software, o desarrollador jefe) encargado del proyecto ha decidido proteger los assemblies a través de nombres seguros, pero además, ha impuesto que la llave privada no sea entregada a miembros del equipo de desarrollo (programadores). Sólo se firmarán los assemblies con la llave privada en el momento de realizar integración sobre la infraestructura de producción, o simplemente se entregará al cliente el assembly por el personal autorizado.

2. Solución

.NET Framework cuenta con atributos de assembly que permiten especificar de forma explicita por medio de atributos la firma pospuesta (delay-sign) para un assembly. El atributo en particular para llevar a cabo esta tarea es AssemblyDelaySignAttribute [4]; y el uso de la opción /delaysign [5] del compilador de C#.

En [1] agregan la nota:
Delay sign en Visual Studio
Figura 1. Habilitación de delay-sign en Visual Studio [1].

3. Discusión

.NET provee un mecanismo conocido como firma postpuesta (o en inglés, delay-sign) que permite firmar un assembly una vez esté listo para distribución y/o despliegue. La idea bajo este mecanismo es reducir el riesgo de secuestro o piratería (utilizo este par de términos para referirme al acto de compilar un assembly o distribuirlo sin permiso para fines comerciales ilegales, o la creación de software malicioso que pueda afectar el sistema destino) por parte de un miembro inescrupuloso del equipo de desarrollo.

Continuando, en [6] se comenta sobre los riesgos potenciales de seguridad que se pueden mitigar a través de este mecanismo:
- A malicious user replaces an assembly in your program with a different assembly with the same file name, but which contains malicious code, and convinces your program to load and execute it.
 - A malicious user replaces an assembly in your program with a different version of the same assembly, but which has known bugs that have since been fixed.
El aspecto técnico de este mecanismo de firma es incluir en los propios assemblies la llave pública y dejar un espacio reservado para incluir los datos relacionados con la firma del assembly en una etapa posterior con la llave privada.

4. Práctica: Código C#

Archivo C# Usuario.cs [enlace alternativo]:
En la línea 5 especificamos el nombre del archivo (generalmente con extensión .snk) que contiene la llave pública. Luego, a resaltar, en la línea 6 utilizamos el atributo AssemblyDelaySignAttribute [4] con el cual indicamos que se trata de una firma pospuesta (delay-sign), para que esto sea así especificamos como argumento true en el constructor.

El siguiente comando consiste en compilar el assembly con la opción (switch) /delaysign, así:



A continuación, como paso final, procedemos a firmar nuestro assembly:


5. Conclusiones

Hemos aprendido acerca de un mecanismo que nos facilita .NET para firmar nuestros assemblies de manera postpuesta (delay-sign). Reconocimos que este mecanismo reduce el riesgo de manipulación de los componentes. Vimos un ejemplo muy sencillo donde usamos el atributo AssemblyDelaySignAttribute para postponer la firma del assembly y ejecutamos el compilador de C# con la opción /delaysign para indicar que la firma se realizará más adelante; finalmente firmamos el assembly usando la herramienta sn.exe y la opción -R.

6. Glosario


  • Delay-sign
  • Firma

7. Enlaces & Literatura

[1]: Visual C# 2010 Recibes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Delay Signing an Assembly - http://msdn.microsoft.com/en-us/library/t07a3dye.aspx
[3]: How to: Delay Sign an Assembly (Visual Studio) - http://msdn.microsoft.com/en-us/library/9fas12zx(v=vs.100).aspx
[4]: AssemblyDelaySignAttribute Class (System.Reflection) - http://msdn.microsoft.com/en-us/library/system.reflection.assemblydelaysignattribute.aspx
[5]: /delaysign (C# Compiler Options) - http://msdn.microsoft.com/en-us/library/vstudio/ta1sxwy8(v=vs.110).aspx
[6]: c# - What exactly are Delay signing and strong names in .net? - Stack Overflow - http://stackoverflow.com/questions/8394357/what-exactly-are-delay-signing-and-strong-names-in-net


J

Receta C# No. 1-10: Verificación de Nombre Seguro Ante Intentos de Modificación de un Assembly

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Continuo con recetas relacionadas con la generación y administración de nombres seguros para las librearías que creemos. Un mecanismo que proporciona .NET Framework para validar y versionar los assemblies. En esta oportunidad vamos a incursionar en el tema de verificación o comprobación de la firma de un assembly una vez haya sido construido.

1. Problema

El equipo de integración necesita verificar que los assemblies recibidos corresponden con copias auténticas creadas por el propio equipo de desarrollo.

2. Solución

La utilidad de nombres seguros -sn.exe- permitirá al equipo de integración comprobar la autencidad del assembly. Esto con el fin de proteger la infraestructura y las aplicaciones ante intentos de ataques.

3. Discusión de la Solución

3.1 Ciclo de Verificación de Assemblies

Cada vez que se carga un assembly en memoria, .NET Framework se encarga de forma automática de realizar las siguientes tareas iterativas:
  1. La CLR extrae el código hash encriptado (el cual se encuentra al interior del assembly) y lo desencripta a través de la llave pública del assembly (que también se encuentra embebida en el mismo assembly).
  2. Ahora la CLR se encarga de calcular el código hash inscrito en el manifiesto e inmediatamente lo compara con el hash desencriptado (obtenido en el paso anterior).
Estos dos pasos, en general, se encargan de verificar si el assembly ha sido modificado después del proceso de construcción (build, en inglés).

3.2 Excepciones en la Verificación

En caso que el proceso iterativo de verificación de assemblies falle, la máquina virtual -CLR- arrojará una excepción que corresponde con System.IO.FileLoadException [2]. En la consola o diálogo (dependiendo del tipo aplicación) se adjuntará un mensaje de excepción que identifica la causa de la falla:
Strong name validation failed.

3.3 Opciones (switches) de sn.exe para la verificación

La herramienta sn.exe también provee opciones para la verificación de assemblies. Por ejemplo, en la receta anterior (Receta C# No. 1-9: Asignar un Nombre Seguro a un Assembly desde un Contenedor de Llaves | Experiencias Construcción Software) se creó un programa de demostración llamado Programa.exe, para verificar si corresponde con un assembly auténtico, entonces realizamos lo siguiente:


Con la opción -v indicamos que se debe realizar una verificación sobre el assembly pasado como argumento (Programa.exe); adicionalmente, con -f forzamos la verificación del assembly.

4. Práctica

[Nota: En la receta anterior -Receta C# No. 1-9: Asignar un Nombre Seguro a un Assembly desde un Contenedor de Llaves-, se encuentra la implementación en código fuente C# del assembly Programa.exe. Por lo tanto se omite su reescritura en esta sección.]

Cuando ejecutemos sn.exe con las opciones descritas en 3.3, en pantalla (usando la consola), recibiremos mensajes de notificación, tanto cuando la verificación sea satisfactoria, como cuando se generé la excepción descrita en 3.2. En la Figura 1, se presnta el mensaje de satisfactorio.
Verificación assembly.
Figura 1. Verificación de un assembly con sn.exe.
Por otro lado, en caso de que la verificación sea fallida se motrará el mensaje:
Failed to verify assembly --

5. Conclusiones

Hemos aprendido a verificar assemblies con el fin de comprobar que se trata de un assembly genuino y no una copia modificada por terceros. Con esto garantizamos al equipo de integración la autoría nuestra de los assemblies que hemos consturido.

6. Glosario

  • Encriptación
  • Desencriptación
  • Hash

7. Enlaces & Literatura

[1]: Visual C# 2010 Recibes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: FileLoadException Class (System.IO) - http://msdn.microsoft.com/en-us/library/system.io.fileloadexception.aspx


O

viernes, 25 de octubre de 2013

Receta C# No. 1-9: Asignar un Nombre Seguro a un Assembly desde un Contenedor de Llaves

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Enlaces & Literatura

0. Introducción

En la receta anterior tuvimos la oportunidad de introducirnos en el mundo de los nombres seguros para nuestros assemblies: una característica de autenticación y versionamiento propia del .NET Framework. En esta ocasión vamos a crear un pequeño programa al que le vamos a especificar ciertos atributos para establecer el versionamiento y el nombre seguro a usar para nuestro assembly. Además, de presentar la forma de firmar postergada.

[Nota: Esta receta será mucho más corta que la anterior [1], debido a que estoy asumiendo que el lector ya tiene conocimiento acerca de los nombres seguros.]

1. Problema

Se posee una base de código existente, ahora se necesita que el código fuente C# de la solución quede firmado usando un nombre seguro desde un contenedor de llaves.

2. Solución

La utilidad sn.exe nos va a permitir firmar nuestros assemblies a partir de llaves generadas con esta misma herramienta. Se usará varios atributos para la versión, y el modo de firma postergado.

3. Discusión de la Solución

Vamos a crear una clase con una funcionalidad básica (para propósitos de demostración), agregaremos los siguientes atributos:
  • AssemblyVersion [3],
  • AssemblyKeyName [4]
Con AssemblyVersion especificamos la versión de nuestro assembly en el mismo archivo de código fuente C#. De forma análoga, también podemos especificar el nombre del archivo de llave o el proveedor de servicios de criptografía CSP [5][6].

En la implementación de la solución veremos que es posible utilizar otro atributo que nos hará más fácil el proceso de firmado de assemblies: AssemblyDelaySign [7]; la idea básica con este atributo es postponer la firma del atributo después de que el assembly haya sido compilado.

4. Práctica: Código C#

Empecemos por crear una clase muy sencilla.

Obsérvese el uso de los atributos mencionados en la sección de Discusión de Solución. Aquí especificamos la versión para nuestro assembly y el nombre del archivo que contiene el par de llave (CSP): MiLlave. El cuerpo del programa, Main, precisamente sólo consiste de una sentencia para la salida de texto estático: "Bienvenido al blog «OrtizOL Experiencias Construcción Software»".

Ahora procedamos con la creación del par de llave y su colocación en el contenedor CSP:




Creación de par de llaves y colocación en CSP
Figura 1. Creación de par de llaves y colocación en CSP.
[Nota: Recuerde que estos archivos deben ser colocados en el mismo directorio donde compilaremos nuestro assembly para su posterior firma.]

Pasamos a compilar el assembly (especificando la opción (o switch) de compilador) /keycontainer:



Verificamos el assembly:


Compilación y Verificación del Assembly.
Compilación y verificación del assembly.

Generación de firma postpuesta (Delay)

Como mencioné antes, el atributo AssemblyDelaySign [7] marcará la firma del assembly para un momento posterior. Así es su uso:

En el archivo de código fuente Programa.cs agregamos la siguiente línea de código:

[assembly:AssemblyDelaySign(true)]

Compilación:



Firma:



Esto es todo lo que tenemos que hacer para firmar nuestro assembly de forma postpuesta.

5. Conclusiones

Espero que a través de esta receta haya quedado claro el concepto de creación de nombres seguro (a pesar de que en la anterior se introdujo con más detalles tal concepto). Segimos el paso a paso de especificación de atributos para la versión de un assembly, como también el CSP que contiene las llaves.

6. Glosario

  • Cryptography Service Provider
  • Delay
  • Nombre Seguro
  • Strong Name

7. Enlaces & Literatura

[1]: R1-8 Cómo Crear y Gestionar Pares de Llaves para Ensamblados con Nombres Seguros | Experiencias Construcción Software - http://ortizol.blogspot.com/2013/10/r1-8-como-crear-y-gestionar-pares-de.html
[2]: Visual C# 2010 Recibes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[3]: AssemblyVersionAttribute Class (System.Reflection) - http://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute.aspx
[4]: AssemblyKeyNameAttribute Class (System.Reflection) - http://msdn.microsoft.com/en-us/library/system.reflection.assemblykeynameattribute.aspx
[5]: Cryptographic Service Provider - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Cryptographic_Service_Provider
[6]: Cryptographic Service Providers (Windows) - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380245(v=vs.85).aspx
[7]: AssemblyDelaySignAttribute Class (System.Reflection) - http://msdn.microsoft.com/en-us/library/system.reflection.assemblydelaysignattribute.aspx


O

miércoles, 23 de octubre de 2013

Receta C# No.1-8: Cómo Crear y Gestionar Pares de Llaves para Ensamblados con Nombres Seguros

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código C#
5. Conclusiones
6. Glosario
7. Enlaces y Literatura

0. Introducción

Esta receta va a ser muy interesante porque aprenderemos cómo firmar nuestros assemblies para demostrar nuestra autoría, y gestionar el versionamiento. Los nombres seguros (o inglés, strong name) hacen referencia a la identidad de un assembly. Esto nos va a permitir crear diferentes versiones de nuestras librerías y desplegarlas en le GAC (Goblal Assembly Cache). Manos a la obra.

1. Problema

En la actualidad el grupo de desarrollo entrega los assemblies para despliegue en los servidores de producción de la compañía donde laboran, pero ha emergido un nuevo requerimiento funcional por parate del departamento de TI en donde se explícita el uso de firmas sobre cada iteración de despliegue que se lleve a cabo. Básicamente, se está exigiendo el uso de criptografía asimétrica [8], es decir, el uso de una llave pública y privada para la firma de los assemblies de despliegue, e inclusive, la asignación de nombres seguros a las unidades lógicas mencionadas.

2. Solución

El arquitecto de la solución ha optado por implementar el uso definitivo de nombres seguros para la creación de pares de llaves pública y privada.

3. Discusión

La plataforma .NET desde sus primeras versiones provee el mecanismo de generación de nombres seguros que no son más que cadenas que identifican a un assembly en el GAC, de tal manera que el colapso de nombres y versiones de assemblies quede remediado asignando una llave pública que le identifique en el repositorio.

La utilidad asociada a este mecanismo de firmado de assemblies se conoce como sn.exe [2] (Strong Name Tool). Nos permite administrar llaves, generar, y verificar firmas. Está disponible en la consola de desarrollador de Visual Studio.


Esta utilidad la encontramos en el menú Inicio -> Todos los Programas -> Visual Studio Tools.
Ejecución de la Developer Command Prompt for VS2012
Figura 1. Developer Command Prompt for VS2012.
[Nota: También podemos hacer que la utilidad sn.exe esté disponible en la consola estándar y en PowerShell [9] agregando una variable de entorno que apunte al directorio donde se encuentra instalada (en el momento de escribir esta receta la ruta que agregué corresponde con C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools).]

El uso básico de la utilidad sn.exe es como sigue:

sn [-quiet][option [parameter(s)]]

En particular:

sn -k MiLibreria.snk

Con el comando previo hemos creado la llave privada y pública.

GAC

El Caché de Ensamblados Global (o en inglés, Global Assembly Cache) es el respositorio de assemblies de alcance de máquina, es decir, de disponibilidad para todas las aplicaciones que requieren acceso a recursos (librerías) comunes.

En Wikipedia [3] está definido así:
The Global Assembly Cache (GAC) is machine-wide CLI assembly cache for the Common Language Infrastructure (CLI). The approach of having a specially controlled central repository addresses the flaws in the shared library concept and helps to avoid pitfalls of other solutions that led to drawbacks like DLL hell.
De aquí se puede inferir que además de servir como repositorio, el GAC provee el soporte necesario para evitar la colisión o dependencia de librerías [4] (esto se logra a través de nombres seguros, y que será ampliado con detalle cuando entremos en la parte de implementación de esta receta).

Global Assembly Cache Tool

La utilidad Gacutil.exe [5] es la que nos permita gestionar el despliegue, eliminación, o visualización de los assemblies en el GAC. También está disponible desde la consola de desarrollador de Visual Studio, en el Kit de Desarrollo de Microsoft Windows [6]. Veremos ejemplos de uso en el desarrollo de la solución que viene a continuación.

4. Práctica: Código C#

Vamos a crear una librearía a partir de la siguiente clase de utilidades aritmética.

La clase estática Aritmetica es sencilla (para propósitos de demostración de las utilidades que viene enseguida), posee métodos para operaciones artiméticas básicas.

4.1 Creación de la llave pública y privada

Para la creación de la llave pública para nuestro assembly utilizamos la siguiente lista de comandos:


En pantalla tendremos algo similar a los resultados que se muestran en la Figura 2:
Creación de pares de llaves a través de sn.exe
Figura 2. Creación de llaves pública y privada con sn.exe.

Podemos ver la llave pública con el siguiente comando:



En el prompt se genera un resultado como el que se visualiza en la
Figura 3:
Visualización de la llave pública.
Figura 3. Visualización de la llave pública.
El comando resaltada en amarillo es el que nos generará los dos siguientes resultados:
  • La llave pública usando el algoritmo SHA-1 [7], y
  • El token de llave pública: 8621c8261fa91dfa.
[Nota: A diferencia de la llave pública generada, el token corresponde con los últimos 8 bytes del código generado por el algoritmo SHA-1. Esto es con propósitos de hacer más compacto la referenciación a los assemblies.]

Ahora ha llegado el momento de desplegar nuestro assembly en el repositorio común GAC. Lo que tenemos que hacer desde la línea de comandos es lo siguiente:

1. Compilación y asociación de la llave MatematicaKey.snk:


2. Instalación del assembly Matematica.dll en el GAC:



En el prompt mostrará:
Mensaje satisfactorio de instalación de assembly.
Figura 4. Instalación satisfactoria de assembly en GAC.

Para validar que la instalación se ha realizada correctamente nos dirigiamos al directorio C:\Windows\Microsoft.NET\assembly\GAC_MSIL y comprabamos la existencia del assembly:
Vista del assembly recién instalado.
Figura 5. Vista del assembly recién instalado.

5. Conclusiones

Hemos explorado el uso de herramientas de .NET que nos permite firmar nuestros assemblies y así demostrar la autoría (autenticación) sobre ellos. Se presentó una receta siguiendo unos pasos sencillos desde crear la libraría, generar la llave con la que vamos a asociar la librería, y finalmente el paso más importante, la instalación de la librería sobre el repositorio común GAC.

6. Glosario

  • Assembly
  • Criptografía
  • Ensamblado
  • Llave privada
  • Llave pública
  • Nombre seguro
  • Token

7. Enlaces & Literatura

[1]: Visual C# 2010 Recibes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Sn.exe (Strong Name Tool) - http://msdn.microsoft.com/en-us/library/k5b5tt23.aspx
[3]: Global Assembly Cache - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Global_Assembly_Cache
[4]: DLL Hell - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/DLL_hell
[5]: Gacutil.exe (Global Assembly Cache Tool) - http://msdn.microsoft.com/en-us/library/ex0ss12c.aspx
[6]: Microsoft Windows SDK - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Microsoft_Windows_SDK
[7]: SHA-1 - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/SHA-1
[8]: Criptografía asimétrica - Wikipedia, la enciclopedia libre - http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica
[9]: Windows PowerShell - Wikipedia, the free encyclopedia - http://es.wikipedia.org/wiki/Criptograf%C3%ADa_asim%C3%A9trica


O

domingo, 20 de octubre de 2013

Liberación de Recursos y Finalizadores en C#

Tabla de Contenido

0. Introducción
1. Finalizadores en C#
1.1 El Método Object.Finalize
1.1.1 Limitaciones del método Finalize
2. La Clase Estática GC
3. Implementación del Patrón Desechable con IDisposable
4. Conclusiones
5. Glosario
6. Enlaces & Literatura

0. Introducción

El programador en algún su momento de su profesión se enfrentará a situaciones que requieren la implementación o uso de recursos del sistema operativo: manipulación de archivos, manejo de recursos visuales (ventanas), o el establecimiento de una conexión de red, entre muchos otros. Veremos que es necesario entender el concepto de gestión automática de recursos que provee la máquina virtual -CLR- de C#; exactamente el manejo de memoria automática para el uso eficiente de los recursos; además, es relevante antes de empezar que C# provee un mecanismo que nos apropia de esta gestión para controlar y manejar adecuadamente el uso de recursos no gestionados.

1. Finalizadores en C#

Los finalizadores son miembros de clase que entran en juego (es decir, se ejecutan) antes de que el recolector de basura (garbage collector, en inglés) inicie su rutina de descubrimiento o búsqueda de objetos no referenciados y los marque como candidatos a ser eliminados.

En C# la declaración sigue este sencillo esquema:

Archivo C# ConexionBD.cs [enlace alternativo]:
Obsérvese que basta con anteponer el símbolo ~ (símbolo crema) al nombre del tipo, como en: ~Conexion (). Y en el cuerpo se ha de especificar el grupo lógico de sentencias para finalizar o reclamar los recursos usados por el tipo de objeto.

La CLR se encarga de la gestión automática de memoria. Sin embargo, hay escenarios donde nos enfrentamos al uso de recursos que a ciencia cierta el garbage collector desconoce sobre cómo eliminarlos. A este tipo de recursos los llamamos no gestionados (unmanaged resources), y comprenden: manipulación de archivos, gestión de GUI (i.e.: ventanas), establecimiento de una conexión a un recurso remoto (i.e.: un archivo), etc.

Todos estos tipos recursos enlistados son adyacentes al sistema operativo. .NET introduce un mecanismo de liberación de recursos no gestionados a través de un método especial común a todos las clases de objetos predefinidos y personalizados: Object.Finalize(), que será el programador quien tenga el poder de implementar la lógica necesaria para llegar a cabo la liberación de los recursos usados.

1.1 El Método Object.Finalize

Implícitamente este método se encarga de realizar tareas de limpieza sobre recursos no gestionados antes de que el garbage collector se encargue de destruir la instancia que tiene asociada todos los recursos usados en el ciclo de vida del objeto. Se dice que es implícita, porque es ejecutado de manera automática por la CLR cuando la instancia de clase ya no es accesible.

En línea con lo anterior, si el objeto ha sido excluído a través del método SuppressFinalize [4] (clase estática GC [5] en namespace System), entonces no se invocará este método (Finalize, desde luego). Sin embargo, Finalize se invocará cuando la aplicación finalice su ejecución sobre aquellos que aún se encuentren accesibles y no eximidos de finalización.

La sintaxis de definición de este método en C# [3] es:

protected virtual void Finalize()

La firma de este método evidencia que se trata de un recurso heredable pero no accesible desde código cliente.

A través de la nota expuesta en [3] nos dicen:
Acerca de la implementación del método finalize.
Figura 1. Nota acerca de la implementación del método Finalize [3].

1.1.1 Limitaciones del método Finalize

De acuerdo con [3], el método Finalize posee las siguientes limitaciones en C#:
  • Se desconoce el momento en que la CLR invoca este método. Es decir, que los recursos no se liberarán en un momento específico. Puede ocurrir lo contrario si se hace una llamada a cualquiera de los métodos Close (suspensión del recurso con posible reuso futuro en el ciclo de vida, por ejemplo en una conexión de base de datos) o Dispose [6] (implementación concreta del contrato de IDisposable [7]).
  • El orden de finalización de un conjunto de objetos no es determinístico; inclusive cuando existe una relación de dependencia referencial entre dos objetos.
  • No hay un hilo (en inglés, thread) de ejecución para el método Finalize asociado.
[Nota: En [8] se aclara con más detalle la diferencia entre Close and Dispose desde la perspectiva de conexiones a bases de datos.]

1.1.2 Ejemplo de uso de Finalize en C#

Recordemos que la llamadas al método Finalize ocurren desde el destructor; es por eso que en el siguiente ejemplo se implementa uno de estos para demostrar en qué momento sucede la destrucción del objeto.

Es de notar que en las líneas 19 a 24, se implementa el miembro destructor ~Contador (). El cual está encargado de llamar de forma implicita al método Finalize de la clase padre. Aunque pudimos haber sobrescrito este método, por motivos de simplicidad se ha ignorado.

En la Figura 2 se muestra una prueba de ejecución de esta implementación.
Ejecución de Contador.
Figura 2. Demostración de la inicialización y finalización de un objeto Contador.

2. La Clase Estática GC

En [5] explicitan la definición de esta clase así:
Controls the system garbage collector, a service that automatically reclaims unused memory.
Entonces podemos decir que esta clase estática provee al programador la posibilidad de controlar la finalización o recuperación de los recursos utilizados por las instancias creadas, pero que ya no están en uso.


Adicionalmente, la recolección de basura consiste en los 3 pasos siguientes [5]:
  1. El recolector de basura busca por objetos gestionados en las referencias creadas.
  2. El recolector de basura intenta finalizar a aquellos objetos no referenciados.
  3. El recolector de basura reclama la memoria de los recursos liberados.
Entrando en más detalles con la clase GC, ésta posee el método SuppressFinalize [4], con firma:

public static void SuppressFinalize (Object obj)

Este metodo permite decirle al recolector de basura que excluya la instancia pasada como argumento de invocar su finalizador asociado. Internamente, la CLR generará esta exclusión marcando un valor bandera (o bit) para evitar la eliminación del objeto de la memoria.

Un posible ejemplo del uso de este método se presenta a continuación:

Esta implementación es similar a la de clase Contador, sin embargo, en la línea 14 -GC.SuppressFinalize (this);- se explicita que la instancias actuales de Contador serán omitidas del ciclo de recolección de basura (el que se describió arriba en los tres pasos).

Demostración de SuppressFinalize en ContadorIndestructible.
Figura 3. Demostración de SuppressFinalize en ContadorIndestructible.

Aparte, otro de los métodos sobresalientes de GC es Collect [10], con firma:


public static void Collect()


Este método estático permite forzar la recolección de basura. No obstante, debe ser usado con cuidado debido a que la recolección de basura es un proceso de altos costes de tiempo de procesador y espacio en memoria.

Quiero agregar la aclaración explicita encontrada en [10]:
All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to try to reclaim the maximum amount of available memory.
Para decir que independientemente de la existencia de un objeto (al menos que pertenezca a la categoría de código administrado) en memoria, también será marcado para recolección. También, que Collect forzará al sistema a reclamar la mayor cantidad de memoria disponible.

Adapto el ejemplo encontrado en [10] para demostración de la utilidad de este método en C#:

Archivo C# Coleccionable.cs [enlace alternativo]:

En las líneas 7-16 creamos hasta 1000 objetos Version [11] de prueba para cargar la memoria con objetos que no se usarán pero que sí ocuparan en espacio. En la línea 24 del código cliente se muestra la cantidad de memoria asignada a la aplicación actual, además se le pasa el argumento false no forzar la recolección. Ocurre lo contrario, en la la línea 28 con true como argumento para el método GetTotalMemory [12].


Cuando hayamos compilado y ejecutada esta clase, tendremos el resultado que se muestra en la Figura 4.
Ejecución de la clase Coleccionable.
Figura 4. Resultados de la ejecución de la clase Coleccionable.

3. Implementación del Patrón Desechable con IDisposable

Tradicionalmente para la manipulación de recursos no gestionados (unmanaged resources) implementamos lógica try..catch..finally;es decir, con el manejo de excepciones. A continuación un ejemplo básico:

// Recurso no gestionado que requiere la manipulación prográmtico
// para la asignación y liberación de recursos (espacio en memoria)
TextReader textReaderObj = null;

try
{
// asignación de recurso
textReaderObj = new StreamReader (@"Archivos\datoscuentas.txt");
// conjunto de sentencias de lógica de lectura del flujo creado
string lineaActual = textReaderObj.ReadToEnd ();
Console.WriteLine (lineaActual);
}
catch (Exception e)
{
// Lógica de implementación para el manejo de la excepción generada
}
finally
{
// liberación de recursos asignados al flujo
if (textReaderObj != null)
{
textReaderObj.Dispose ();
}
}

Análogamente, podemos hacer uso de using [13], construcción que permite al programador pensar sólo en la asignación de recursos; implícitamente esta construcción se encargará de liberar los recursos asignados y a los objetos en memoria. Véamoslo con un ejemplo:

// Recurso no gestionado que requiere la manipulación prográmtico
// para la asignación y liberación de recursos (espacio en memoria)
// VERSIÓN RECOMENDADA
TextReader textReaderObj = null;

try
{
using (txtReaderObj = new StreamReader(@"Archivos\datoscuentas.txt"))
{
// conjunto de sentencias de lógica de lectura del flujo creado
string lineaActual = textReaderObj.ReadToEnd ();
Console.WriteLine (lineaActual);
}
}
catch (Exception e)
{
// Lógica de implementación para el manejo de la excepción generada
}

using permite, además, omitir el uso de la sentencia finally [14]. Este enfoque es mucho más intuitivo y expresivo comparado con el primer caso.

Hasta aquí hemos visto dos enfoques para asignación y liberación de recursos no gestionados usando las construcciones clásicas del lenguaje. Ahora es el turno de comprender un mecanismo más sofisticado: el patrón desechable, el cual es posible implementar en nuestros proyectos a través de la interfaz IDisposable [7].

La interfaz IDisposable define un método para la liberación de recursos, con el que el programador podrá crear su propia implementación lógica para liberar los recursos no manejados. Entendamos esto con un ejemplo práctico (adaptación de [15]):

A través de Dispose (bool marca) especificamos la lógica necesaria para terminar con los recursos adyacentes utilizados por la clase ManejadorAperturaDocumentos.

Podemos utilizar este recurso con la sentencia using:

using (ManejadorAperturaDocumentos mAD = new ManejadorAperturaDocumentos())
{
// sentencias asociadas con el uso de la funcionalidad de la clase ManejadorAperturaDocumentos
}

4. Conclusiones

Hemos aprendido acerca de los conceptos fundamentales de liberación de recursos y finalizadores en C#. Conceptos útiles cuando nos enfrentamos a proyectos que midan su calidad por el uso eficiente de los recursos disponibles por el ambiente de ejecución y la infraestructura adyacente. En particular, conocimos los básicos de la clase System.GC, que nos permite controlar la liberación de recursos (espacio en memoria); lo vimos a través de un ejemplo sencillo en el que se creaban 1000 instancias). Al final comprendimos los beneficios de implementar la interfaz IDisposable.

5. Glosario

  • CLR
  • Finalizadores
  • Gestión Automática de Memoria
  • Recolector de basura
  • Recurso gestionado
  • Recurso no-gestionado
  • Sistema operativo

6. Enlaces & Literatura

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Finalize Methods and Destructors - http://msdn.microsoft.com/en-us/library/0s71x931.aspx
[3]: Object.Finalize Method (System) - http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx
[4]: GC.SuppressFinalize Method (System) - http://msdn.microsoft.com/en-us/library/system.gc.suppressfinalize(v=vs.110).aspx
[5]: GC Class (System) - http://msdn.microsoft.com/en-us/library/system.gc.aspx
[6]: IDisposable.Dispose Method (System) - http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx
[7]: IDisposable Interface (System) - http://msdn.microsoft.com/en-us/library/System.IDisposable(v=vs.110).aspx
[8]: .net - Close and Dispose - which to call? - Stack Overflow - http://stackoverflow.com/questions/61092/close-and-dispose-which-to-call
[9]: Gestión de Memoria en C# | Experiencias Construcción Software - http://ortizol.blogspot.com/2013/06/gestion-de-memoria-en-c.html
[10]: GC.Collect Method (System) - http://msdn.microsoft.com/en-us/library/xe0c2357.aspx
[11]: Version Class (System) - http://msdn.microsoft.com/en-us/library/system.version.aspx
[12]: GC.GetTotalMemory Method (System) - http://msdn.microsoft.com/en-us/library/system.gc.gettotalmemory(v=vs.110).aspx
[13]: using Statement (C# Reference) - http://msdn.microsoft.com/en-us/library/yh598w02.aspx
[14]: try-finally (C# Reference) - http://msdn.microsoft.com/en-us/library/vstudio/zwc8s4fz.aspx
[15]: Como implementar correctamente IDisposable… | Can you hear the bits? - http://canyouhearthebits.wordpress.com/2008/08/08/como-implementar-correctamente-idisposable/


O