¿ADO.NET se excede con IDisposable?

Cuando salió .NET por primera vez, yo fui uno de los muchos que se quejaron de la falta de finalización determinista de .NET (se llamó a los destructores de clases en un horario impredecible). El compromiso que propuso Microsoft en ese momento fue la statement de using .

Aunque no es perfecto, creo que el uso es importante para garantizar que los recursos no administrados se limpien de manera oportuna.

Sin embargo, estoy escribiendo un código ADO.NET y noté que casi todas las clases implementan IDisposable . Eso lleva a un código que se ve así.

 using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlCommand command = new SqlCommand(query, connection)) using (SqlDataAdapter adapter = new SqlDataAdapter(command)) using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter)) using (DataSet dataset = new DataSet()) { command.Parameters.AddWithValue("@FirstValue", 2); command.Parameters.AddWithValue("@SecondValue", 3); adapter.Fill(dataset); DataTable table = dataset.Tables[0]; foreach (DataRow row in table.Rows) // search whole table { if ((int)row["Id"] == 4) { row["Value2"] = 12345; } else if ((int)row["Id"] == 5) { row.Delete(); } } adapter.Update(table); } 

Sospecho fuertemente que no necesito todas esas declaraciones de using . Pero sin entender el código para cada clase en detalle, es difícil estar seguro de cuáles puedo omitir. El resultado es algo feo y resta valor a la lógica principal de mi código.

¿Alguien sabe por qué todas estas clases necesitan implementar IDisposable ? (Microsoft tiene muchos ejemplos de código en línea que no se preocupan por desechar muchos de estos objetos). ¿Están otros desarrolladores escribiendo using declaraciones para todos ellos? Y, si no, ¿cómo decides cuáles puedes prescindir?

Una parte del problema aquí es que ADO.NET es un modelo de proveedor abstracto. No sabemos qué necesitará una implementación específica (un proveedor específico de ADO.NET) con respecto a la eliminación. Claro, podemos asumir razonablemente que la conexión y la transacción deben eliminarse, pero ¿el comando? Tal vez. ¿Lector? Probablemente, no solo porque una de las opciones de indicadores de comando le permite asociar la vida útil de una conexión al lector (de modo que la conexión se cierra cuando el lector lo hace, lo que lógicamente debería extenderse a disposición).

Así que en general, creo que probablemente está bien.

La mayoría de las veces, las personas no están jugando con ADO.NET a mano, y cualquier herramienta de ORM (o herramientas de micro-ORM, como “Dapper”) obtendrá esto para usted sin que tenga que preocuparse por eso.


Confesaré abiertamente que en las pocas ocasiones en las que he usado DataTable (seriamente, es 2018, ese no debería ser su modelo predeterminado para representar datos, excepto en algunos escenarios de nicho): no los he eliminado. Que un poco no tiene sentido 🙂

Si una clase implementa IDisposable , siempre debe asegurarse de que se elimine, sin hacer suposiciones sobre su implementación.

Creo que esa es la cantidad mínima de código que podrías escribir para asegurar que tus objetos se eliminarán. No veo ningún problema con ello, y no me distrae en absoluto de la lógica principal de mi código.

Si reducir este código es de sum importancia para usted, entonces puede crear su propio reemplazo (envoltorio) de SqlConnection cuyas clases secundarias no implementan IDisposable y en su lugar, la conexión las destruye automáticamente una vez que se cierra. Sin embargo, eso supondría una enorme cantidad de trabajo y perdería cierta precisión con respecto a cuándo se dispone algo.

Sí. casi cualquier cosa en ADO.Net está implementando IDisposable , ya sea que sea realmente necesario, en el caso de, digamos, SqlConnection ( debido a la agrupación de conexiones ) o no, en el caso de, digamos, DataTable ( ¿Debo Eliminar () DataSet y DataTable? ).

El problema es, como se señala en la pregunta:

sin entender el código de cada clase en detalle, es difícil estar seguro de cuáles puedo omitir.

Creo que esto solo es una razón suficiente para mantener todo dentro de una statement de using , en una palabra: encapsulación.

En muchas palabras: no debería ser necesario familiarizarse íntimamente, o incluso familiarizarse remotamente con la implementación de cada clase con la que esté trabajando. Solo necesita conocer el área de superficie, es decir, métodos públicos, propiedades, eventos, indexadores (y campos, si esa clase tiene campos públicos). desde el usuario de un punto de vista de clase, cualquier otra cosa que no sea su superficie pública es un detalle de implementación.

Con respecto a todas las declaraciones de using en su código: puede escribirlas solo una vez creando un método que acepte una instrucción SQL, un Action y una matriz de parámetros de parámetros. Algo como esto:

 void DoStuffWithDataTable(string query, Action action, params SqlParameter[] parameters) { using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlCommand command = new SqlCommand(query, connection)) using (SqlDataAdapter adapter = new SqlDataAdapter(command)) using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter)) using (var table = new DataTable()) { foreach(var param in parameters) { command.Parameters.Add(param); } // SqlDataAdapter has a fill overload that only needs a data table adapter.Fill(table); action(); adapter.Update(table); } } 

Y lo usa así, para todas las acciones que necesita hacer con su tabla de datos:

 DoStuffWithDataTable( "Select...", table => { // of course, that doesn't have to be a lambda expression here... foreach (DataRow row in table.Rows) // search whole table { if ((int)row["Id"] == 4) { row["Value2"] = 12345; } else if ((int)row["Id"] == 5) { row.Delete(); } } }, new SqlParameter[] { new SqlParameter("@FirstValue", 2), new SqlParameter("@SecondValue", 3) } ); 

De esta manera, su código es “seguro” con respecto a la eliminación de cualquier IDisposable , mientras que solo ha escrito el código de plomería para eso solo una vez.