Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 21 de mayo de 2019
.NET Core La palabra clave using, utilizada tanto en forma de directiva como de instrucción, es una de las más sobrecargadas del lenguaje C#. Es útil para bastantes cosas, como la importación de espacios de nombres, definición de alias de namespaces o tipos, simplificar el acceso a miembros de tipos estáticos, o para especificar bloques o ámbitos de uso de recursos (objetos IDisposable) que deben ser liberados automáticamente.

Centrándonos en este último caso de uso, seguro que en muchas ocasiones habéis escrito código como el siguiente, donde vamos anidando objetos IDisposable para asegurar que al finalizar la ejecución de cada bloque los recursos sean liberados de forma automática:
void DoSomething()
{
    using(var conn = new SqlConnection(...))
    {
        connection.Open();
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = "...";
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    // ...
                }
            }
        }
    }
}    
Al final, lo que encontramos es código con un nivel de indentación muy alto, y que resulta muy extenso, básicamente porque una gran parte de las líneas las dedicamos sólo a abrir y cerrar llaves. A la postre, esto sólo hace que nuestro código crezca a lo ancho, lo cual no es bueno desde el punto de vista de la simplicidad y facilidad de lectura.

Pues bien, C# 8 introduce una pequeña mejora destinada a simplificar la codificación de este tipo de ámbitos de uso de recursos: el ámbito local implícito. A partir de esta versión del lenguaje, podremos escribir un código como el anterior de la siguiente manera:
void DoSomething()
{
    using var conn = new SqlConnection(...);
    connection.Open();

    using var cmd = conn.CreateCommand();
    cmd.CommandText = "...";

    using var reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        // ...
    }   
}
Utilizando la construcción using var ... que vemos en el código anterior, estamos indicando al compilador que la liberación del recurso declarado debe realizarse de forma automática cuando finalice el ámbito en el que nos encontramos. En el ejemplo anterior, la llamada al método Dispose() se realizará al finalizar la ejecución de `DoSomething()'.
Recuerda que a día de hoy ya puedes probar C# 8 en Visual Studio 2019 o directamente desde la interfaz de línea de comandos de .NET Core.
Por supuesto, esto aplica a cualquier tipo de ámbito, por lo que podríamos utilizar esta sintaxis en cualquier tipo de bloque, como un if u otros de los permitidos por el lenguaje:
if (expr)
{
    using var fileStream = File.Create(...);
    ...
    // Aquí se liberará fileStream automáticamente
}
Como detalle de implementación, pero que tiene su sentido, la liberación se realizará en orden inverso a como han sido declarados, de forma que el resultado sería muy similar a si hubiéramos optado por utilizar bloques using tradicionales.

Mmmm... no sé, no sé....

La verdad es que tengo sensaciones raras respecto a esta novedad del lenguaje. Por una parte es cierto que todo lo que nos ahorre teclear más es una ventaja y que evitar la verbosidad hace que todo sea más simple. Desde este punto de vista, la idea me convence :)

Sin embargo, el hecho de eliminar los bloques explícitos hace menos visible la intención de liberar los recursos utilizados... y sobre todo, no sé si podría hacer más difícil detectar cuándo se nos ha olvidado hacerlo.

Por ejemplo, echando un vistazo al código siguiente, no es fácil detectar que se nos olvidó añadir el using en una de las declaraciones:
void DoSomething()
{
    using var conn = new SqlConnection(...);
    connection.Open();

    var cmd = conn.CreateCommand();
    cmd.CommandText = "...";

    using var reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        // ...
    }
    ... // Aquí se liberarán conn, cmd y reader... ¿o no?
}
También, aunque entiendo que la excesiva indentación que podría darse por la utilización de bloques using convencionales puede ser perjudicial para facilitar la comprensión y legibilidad de el código, es fácilmente salvable extrayendo bloques a métodos, funciones locales o incluso otros componentes. Incluso en muchos casos estaríamos diseñando mejor nuestros componentes en términos de aplicación de niveles de abstracción correctos en cada lugar.

En fin, supongo que iré teniendo una opinión más formada cuando empiece a ver la utilización que le damos a esta característica y los problemas o beneficios que nos vaya trayendo su uso :)

Publicado en Variable not found.

4 Comentarios:

MontyCLT dijo...

Hola José María.

Como alternativa intermedia para evitar una alta identacción sin necesidad de hacer uso de esta nueva directiva (que por cierto, a mi me encanta) y manteniendo los bloques, se puede utilizar varias sentencias using antes de abrir las llaves:

using (var stream = GetStream())
using (var reader = new StreamReader(stream))
{
Task bytes = reader.ReadAsBytesAsync();
}

En vez de anidar uno dentro de otro todos los bloques using, se pueden poner todos juntos y luego abrir una llave que afecte a todos.

José María Aguilar dijo...

Hola!

Efectivamente :) Incluso se pueden introducir varias variables dentro del mismo using si son del mismo tipo. Opciones tenemos a día de hoy.

A mi también me gusta esta nueva opción, como cualquier otra característica que nos ahorre pulsaciones de teclas. Lo que no veo claro es si su uso puede dar lugar a problemas difíciles de encontrar, pero claro, es otra historia.

Muchas gracias por comentar :)

FAGF dijo...

Hola,

"Sin embargo, el hecho de eliminar los bloques explícitos hace menos visible la intención de liberar los recursos utilizados... y sobre todo, no sé si podría hacer más difícil detectar cuándo se nos ha olvidado hacerlo."

Creo que eso mismo decíamos quienes programábamos en C++ allá por el 2001, cuando nos enteramos que C# no tenía la palabra reservada delete para eliminar recursos de forma determinista. Tras 18 años, he aprendido que el buen programador sabe que los recursos se tienen que liberar, y diseñará su código para usar delete, using o cualquier otra forma que la documentación del objeto indiqque. El mal programador seguirá sin usar el using de cualquier forma.

Además, si quieres hacer explícita la liberación, pues mejor hacer uso del try-catch-finally:


Foo foo;
try {
...
} finally {
foo?.Dispose();
}

Ello se me hace más legible que poner usings cuando hay más de un IDisposable en el método (por ejemplo, en las llamadas a base de datos).

Por último, hasta donde entiendo el using con llaves seguirá siendo válido, así que podremos organizar una transición lenta con nuestros equipos de desarrollo, si así fuese necesario.

¡Saludos!

José María Aguilar dijo...

Hola, Fernando!

Pues sí, igual esta sensación es sólo un poco de desconfianza ante algo nuevo... o señales de que me voy haciendo viejo y me gustan las cosas "como se han hecho toda la vida" ;DD

Pero creo que no es sólo cuestión de programadores buenos o malos, o de los que saben o no saben (todavía) que hay que liberar recursos, mi inquietud viene también por el simple despiste, es decir, que se nos pase ponerla o eliminemos por error la palabra "using" y no nos daremos cuenta de la fuga que estamos creando porque visualmente no tendremos pistas.

Pero vaya, es cierto lo que dices: los bloques using seguirán siendo válidos, por lo que siempre se podría optar por la solución clásica.

Muchas gracias por aportar!