Usando using

lunes 28 de agosto de 2006

El día de hoy aprendí un tip bastante bueno de como usar using gracias a Eber Irigoyen.

Todo comenzó porque él hizo un comentario en el último artículo recalcando la importancia de usar using para TODOS los objetos relacionados con la base de datos (conexiones, comandos, transacciones, etcétera). A mí se me hacía un poco de overkill, pero ahora comprendo que en realidad sí es un buen consejo.

Por si no sabes ni de qué hablo, esta instrucción define un alcance (scope) explícito para un objeto, fuera del cual el objeto es automáticamente "dispuesto" (manda llamar su método Close() o Dispose()), incluso si se levanta una excepción dentro de ese bloque.

Aunque estrictamente hablando no es necesario, ya que hay un scope implícito cada vez que abres y cierras llavecitas en C#, para recursos limitados como conexiones de red, el scope explícito que crea el using ayuda a que el garbage collector reclame esos recursos más fácil y rápidamente.

Si no lo tuvieras, tendrías que hacer todo a punta de bloques try/catch/finally. Eso era algo que no me gustaba de VB 7--bueno VB .NET o "VB 2003", como le quieran llamar. Afortunadamente VB 8 (2005) ya lo trae.

En el artículo original no la quise complicar mucho con esto, pero así quedaría el "patrón de uso de un DataReader" usando try/catch/finally:

string miStringDeConexion =
    @"Data Source=.\SQLEXPRESS;Initial Catalog=AdoNetDemo;" +
    "Integrated Security=True";
string miQuery = "SELECT * FROM Peliculas;";
 
SqlDataReader speedy = null;
SqlConnection miConexion = null;
SqlCommand miCommando = null;
try
{
    // en cualquiera de las instrucciones de este bloque se podría
    // levantar una excepción
 
    miConexion = new SqlConnection(miStringDeConexion);
    miCommando = new SqlCommand(miQuery, miConexion);
    miConexion.Open();
    speedy = miCommando.ExecuteReader();
    while (speedy.Read())
    {
        // hacer mi desmater 
    }
}
catch (SqlException)
{
    // hacer algo para intentar recuperarme o re-lanzar exception
 // si no pienso hacer nada, es mejor omitir el catch
}
catch
{
    // hacer algo para intentar recuperarme o re-lanzar exception
 // si no pienso hacer nada, es mejor omitir el catch
}
finally
{
    // haya o no haya ocurrido excepción, siempre debemos asegurarnos
    // de cerrar las conexiones, DataReaders, etc.
    if (!speedy.IsClosed)
        speedy.Close();
    if (miConexion != null)
        miConexion.Close();
    if (miCommando != null)
        miCommando.Dispose();
}

Como puedes ver, es medio engorroso. Por eso el using es tan cool.

Pero, yo tenía mis reservas, porque cuando tienes varios objetos, es fácil--pensaba yo--que acabes con un chorro de usings anidados, lo cual no me gustaba:

// asumiendo que TipoA, TipoB y TipoC son clases que
// implementan IDisposable
using (TipoA a = new TipoA())
{
    using (TipoB b = new TipoB())
    {
        using (TipoC c = new TipoC())
        {
            // hacer mi desmater
        } // using dispone c
    } // using dispone b
} // using dispone a

Ahora, también sabía que using soporta declarar varios objetos del mismo tipo dentro del paréntesis. Algo así como esto:

// De nuevo, asumiendo que TipoA implementa IDisposable
using (TipoA a1 = new TipoA(), a2 = new TipoA(), a3 = new TipoA())
{
    // hacer mi desmater
} // using dispone a1, a2 y a3

Lo cual no es muy útil cuando tienes objetos de distintos tipos, como en el patrón de uso de un DataReader--donde tienes que usar SqlCommand, SqlConnection y SqlDataReader.

Así que por eso se me hizo tan chido cuando Eber me comentó que using soporta una sintaxis más o menos así:

// asumiendo que TipoA, TipoB y TipoC son clases que
// implementan IDisposable
using (TipoA a = new TipoA())
using (TipoB b = new TipoB())
using (TipoC c = new TipoC())
{
   // hacer mi desmater
} // using dispone a, b y c

De esa forma podría re-escribir el ejemplo de esta forma:

string miStringDeConexion =
    @"Data Source=.\SQLEXPRESS;Initial Catalog=AdoNetDemo;" +
    "Integrated Security=True";
string miQuery = "SELECT * FROM Peliculas;";
 
using(SqlConnection miConexion = new SqlConnection(miStringDeConexion))
using(SqlCommand miCommando = new SqlCommand(miQuery, miConexion))
{
    miConexion.Open();
    using (SqlDataReader speedy = miCommando.ExecuteReader())
    {
        while (speedy.Read())
        { // hacer mi desmater 
        }
    } // using manda llamar speedy.Close()
} // using manda llamar miConexion.Close() y miComando.Dispose()

Ahora, nomás de payaso modifiqué un poco el código para verificar que en realidad se estuvieran mandando llamar los Dispose(), agregando manejadores para el evento Disposed tanto de la conexión como del comando de la siguiente forma:

public static void UsandoUsing() {
 
    string miStringDeConexion =
        @"Data Source=.\SQLEXPRESS;Initial Catalog=AdoNetDemo;" +
        "Integrated Security=True";
    string miQuery = "SELECT * FROM Peliculas;";
 
    using(SqlConnection miConexion = new SqlConnection(miStringDeConexion))
    using(SqlCommand miCommando = new SqlCommand(miQuery, miConexion))
    {
        miConexion.Disposed += new EventHandler(miConexion_Disposed);
        miCommando.Disposed += new EventHandler(miCommando_Disposed);
 
        miConexion.Open();
        using (SqlDataReader speedy = miCommando.ExecuteReader())
        {
            while (speedy.Read())
            { // hacer mi desmater 
            }
        } // using manda llamar speedy.Close()
    } // using manda llamar miConexion.Close() y miComando.Dispose()
}
 
static void miConexion_Disposed(object sender, EventArgs e)
{
    Console.WriteLine("miConexion liberada");
}
 
static void miCommando_Disposed(object sender, EventArgs e)
{
    Console.WriteLine("miComando liberado");
}

¿El resultado?

Oh yeah.

categorías: , , , ,

5 comentarios:

  1. sigfrido2006@hotmail.com dijo:

    perfecto... lo que estaba buscando... por que la idea de anidar los using no me gustaba nada... bueno...
    Pero una pregunta, al salir de bloque encerrado por el using, por ejemplo para un objeto de tipo sqlconnection, se hace un .close() , un .dispose() o ambos... ahora que diferencia hay entre hacer uno o el otro, basta con solo hacerle un .dispose()...

  2. KaMiKaZe aka Carlos dijo:

    Hasta donde sé, no hay diferencia, en términos prácticos, entre Close() y Dispose(). No sé si Close simplemente es un alias para Dispose en algunos objetos (como que se oye más natural decir "cerrar" conexión" en lugar de "disponer conexión") o si simplemente lo manda llamar. En realidad no importa. Using de todas maneras les da killer :)

  3. Anónimo dijo:

    usin solo te asegura de hacer dispose a tus objetos.
    pero como manejas las excepciones ¿? o como envías mensajes de error, o comunicas al user que ocurrió un error combinado las dos. La sol podría estar en la siguiente dir ...
    http://www.thescripts.com/forum/thread669411.html

  4. KaMiKaZe aka Carlos dijo:

    En realidad no puedes manejar las excepciones dentro del bloque local de código con Using, tendrías que manejarlo en el código que manda llamar tu bloque.

    Es como si fuera un try/finally (sin catch), excepto que te ahorras la talacha de terner que hacer dispose o close de los objetos. Using no se "traga" las excepciones (osea las excepciones aún son "burbujeadas" hacia arriba).

    Esto puede o no ser lo que quieras, según el tipo de aplicación que manejes. Yo por ejemplo desarrollo principalmente aplicaciones Web, y ahí normalmente tengo un manejador de errores global para mi aplicación que puede enviarme una notificación o registrar el error en un log o base de datos para poder obtener métricos de los errores más frecuentes, etc. Como ese componente es global a mi aplicación, yo no quiero que mis bloques locales atrapen las excepciones, yo quiero que se vayan hasta que lo atrape mi manejador y me provea el stack trace completito.

    Pero en fin, esto ya más bien es una pregunta para una discusión sobre exception-handling... creo que me acabas de dar una idea para mi próximo artículo :o)

  5. Anónimo dijo:

    En estos ejemplos estas usando el using como en los casos de los if cuando solo tienen una instruccion, entonces no es necesario poner las eiquetas de abrir y cerrar; Es correcto? asi funciona?

    Y tienes algun articulo sobre lo que comentabas de manejador de errores global y el stack trace ?