IDisposable y la sentencia using
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy. Lección 45 de 70.
Algunos objetos conservan recursos que el recolector de basura no puede limpiar automáticamente: identificadores de archivo, conexiones de base de datos y sockets de red. La interfaz IDisposable proporciona una forma estándar de liberar estos recursos cuando hayas terminado de usarlos.
Una clase implementa IDisposable proporcionando un método Dispose():
public class FileHandler : IDisposable
{
private bool disposed = false;
public void Dispose()
{
if (!disposed)
{
Console.WriteLine("Releasing resources...");
disposed = true;
}
}
}Podrías llamar a Dispose() manualmente, pero ¿qué ocurre si se produce una excepción antes de llegar a esa línea? La instrucción using garantiza que la limpieza se realice, incluso cuando algo sale mal:
using (FileHandler handler = new FileHandler())
{
// Trabajar con el handler
} // Dispose() se llama automáticamente aquíEl C# moderno ofrece una sintaxis más limpia con la declaración using, que elimina el objeto cuando sale del ámbito:
void ProcessFile()
{
using FileHandler handler = new FileHandler();
// Trabajar con handler
} // Dispose() se llama cuando el método saleLa instrucción using funciona con cualquier tipo que implemente IDisposable. Las clases integradas como StreamReader, SqlConnection y HttpClient implementan esta interfaz, lo que hace que la gestión adecuada de recursos sea sencilla y fiable.
Desafío
FácilConstruyamos un simulador de conexión a una base de datos que demuestre una gestión adecuada de los recursos mediante la interfaz IDisposable y la instrucción using. Crearás una clase que registre cuándo se abren y se cierran las conexiones, garantizando que los recursos se liberen siempre correctamente.
Organizarás tu código en dos archivos:
DatabaseConnection.cs: Crea una claseDatabaseConnectionen el espacio de nombresDataAccessque implementeIDisposable. Tu conexión debe:- Tener una propiedad
ConnectionName(de tipo string y de solo lectura) establecida mediante el constructor - Registrar si la conexión se ha liberado mediante un campo booleano privado
- Imprimir
Connection opened: {ConnectionName}cuando se cree el objeto (en el constructor) - Implementar el método
Dispose()para imprimirConnection closed: {ConnectionName}, pero solo si aún no se ha liberado (para evitar una liberación doble) - Tener un método
Query(string sql)que devuelvaExecuting on {ConnectionName}: {sql}
- Tener una propiedad
Program.cs: En tu archivo principal, demuestra una gestión adecuada de los recursos utilizando la instrucciónusingcon tu conexión a la base de datos. Crea una conexión, ejecuta una consulta y deja que el bloqueusinggestione automáticamente la limpieza.
Recibirás dos entradas:
- El nombre de la conexión (por ejemplo,
MainDB) - La consulta SQL que se ejecutará (por ejemplo,
SELECT * FROM Users)
Usa una instrucción using para crear la conexión. Dentro del bloque, llama al método Query e imprime su resultado. Cuando termine el bloque using, Dispose() se llamará automáticamente.
Por ejemplo, si las entradas son MainDB y SELECT * FROM Users, la salida debería ser:
Connection opened: MainDB
Executing on MainDB: SELECT * FROM Users
Connection closed: MainDBObserva cómo la conexión se cierra automáticamente cuando termina el bloque using: nunca tienes que acordarte de llamar manualmente a Dispose(). ¡Este patrón garantiza que los recursos se limpien siempre, incluso si se produce una excepción dentro del bloque!
Pruébalo tú mismo
using System;
using DataAccess;
class Program
{
public static void Main(string[] args)
{
string connectionName = Console.ReadLine();
string sqlQuery = Console.ReadLine();
// TODO: Usa una sentencia 'using' para crear un DatabaseConnection
// Dentro del bloque using:
// 1. Llama al método Query con el sqlQuery
// 2. Imprime el resultado del método Query
// El método Dispose se llamará automáticamente cuando termine el bloque using
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantesPractica por tu cuenta: Compilador de C# online