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 retienen recursos que el recolector de basura no puede limpiar automáticamente: identificadores de archivos, conexiones a bases de datos, 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é pasa si ocurre una excepción antes de llegar a esa línea? La sentencia using garantiza que la limpieza ocurra, incluso cuando las cosas salen mal:
using (FileHandler handler = new FileHandler())
{
// Trabajar con el manejador
} // Dispose() se llama automáticamente aquíEl C# moderno ofrece una sintaxis más limpia con la declaración using, que libera el objeto cuando sale del alcance:
void ProcessFile()
{
using FileHandler handler = new FileHandler();
// Trabajar con el manejador
} // Se llama a Dispose() cuando el método finalizaLa sentencia using funciona con cualquier tipo que implemente IDisposable. 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ácilVamos a construir un simulador de conexión a base de datos que demuestre la gestión adecuada de recursos utilizando la interfaz IDisposable y la sentencia using. Crearás una clase que rastree cuándo se abren y cierran las conexiones, asegurando que los recursos siempre se liberen 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(string, de solo lectura) establecida a través del constructor - Rastrear si la conexión ha sido eliminada (disposed) utilizando un campo booleano privado
- Imprimir
Connection opened: {ConnectionName}cuando se crea el objeto (en el constructor) - Implementar el método
Dispose()para imprimirConnection closed: {ConnectionName}, pero solo si no ha sido eliminada previamente (evitar la doble eliminación) - Tener un método
Query(string sql)que devuelvaExecuting on {ConnectionName}: {sql}
- Tener una propiedad
Program.cs: En tu archivo principal, demuestra la gestión adecuada de recursos utilizando la sentenciausingcon tu conexión de base de datos. Crea una conexión, ejecuta una consulta y deja que el bloqueusingmaneje la limpieza automáticamente.
Recibirás dos entradas:
- El nombre de la conexión (por ejemplo,
MainDB) - La consulta SQL a ejecutar (por ejemplo,
SELECT * FROM Users)
Usa una sentencia using para crear tu conexión. Dentro del bloque, llama al método Query e imprime su resultado. Cuando el bloque using termine, se llamará a Dispose() 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 a Dispose() manualmente. ¡Este patrón asegura que los recursos siempre se limpien, incluso si ocurre 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: Usar una sentencia 'using' para crear una DatabaseConnection
// Dentro del bloque using:
// 1. Llamar al método Query con el sqlQuery
// 2. Imprimir 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 estudiantes