Buscar..


Introducción

Proporciona una sintaxis conveniente que asegura el uso correcto de los objetos IDisponibles .

Sintaxis

  • utilizando (desechable) {}
  • utilizando (IDisposable desechable = nuevo MyDisposable ()) {}

Observaciones

El objeto en la declaración de using debe implementar la interfaz IDisposable .

using(var obj = new MyObject())
{
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        // Cleanup
    }
}

Se pueden encontrar ejemplos más completos para la implementación IDisposable en los documentos de MSDN .

Uso de los fundamentos de la declaración

using azúcar sintáctica le permite garantizar que un recurso se limpie sin necesidad de un bloque explícito de try-finally . Esto significa que su código será mucho más limpio y no perderá recursos no administrados.

El patrón estándar de limpieza de Dispose , para los objetos que implementan la interfaz IDisposable (que la clase base Stream FileStream hace en .NET):

int Foo()
{
    var fileName = "file.txt";

    {
        FileStream disposable = null;

        try
        {
            disposable = File.Open(fileName, FileMode.Open);

            return disposable.ReadByte();
        }
        finally
        {
            // finally blocks are always run
            if (disposable != null) disposable.Dispose();
        }
    }
}

using simplifica su sintaxis al ocultar el try-finally explícito try-finally :

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        return disposable.ReadByte();
    }
    // disposable.Dispose is called even if we return earlier
}

Al igual que, finally bloques siempre se ejecutan independientemente de los errores o devoluciones, using siempre las llamadas Dispose() , incluso en caso de error:

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        throw new InvalidOperationException();
    }
    // disposable.Dispose is called even if we throw an exception earlier
}

Nota: dado que se garantiza que Dispose se llame independientemente del flujo de código, es una buena idea asegurarse de que Dispose nunca arroje una excepción cuando implemente IDisposable . De lo contrario, una excepción real quedaría anulada por la nueva excepción que resultaría en una pesadilla de depuración.

Volviendo de usar bloque

using ( var disposable = new DisposableItem() )
{
    return disposable.SomeProperty;
}

Debido a la semántica de try..finally a la que se traduce el bloque de using , la declaración de return funciona como se esperaba: el valor de retorno se evalúa antes de que finally se ejecute el bloque y se elimine el valor. El orden de evaluación es el siguiente:

  1. Evaluar el cuerpo de try
  2. Evaluar y almacenar en caché el valor devuelto
  3. Ejecutar finalmente el bloque
  4. Devuelve el valor de retorno en caché

Sin embargo, no puede devolver la variable disposable sí misma, ya que contendría una referencia desechada no válida; consulte el ejemplo relacionado .

Múltiples declaraciones usando un bloque

Es posible utilizar múltiples anidadas using declaraciones sin agregados múltiples niveles de tirantes anidados. Por ejemplo:

using (var input = File.OpenRead("input.txt"))
{
    using (var output = File.OpenWrite("output.txt"))
    {
        input.CopyTo(output);
    } // output is disposed here
} // input is disposed here

Una alternativa es escribir:

using (var input = File.OpenRead("input.txt"))
using (var output = File.OpenWrite("output.txt"))
{
    input.CopyTo(output);
} // output and then input are disposed here

Que es exactamente equivalente al primer ejemplo.

Nota: las declaraciones de using anidadas pueden activar la regla de análisis de código de Microsoft CS2002 (consulte esta respuesta para obtener una aclaración) y generar una advertencia. Como se explica en la respuesta vinculada, generalmente es seguro anidar using declaraciones.

Cuando los tipos dentro de la declaración de using son del mismo tipo, puede delimitarlos con comas y especificar el tipo solo una vez (aunque esto no es común):

using (FileStream file = File.Open("MyFile.txt"), file2 = File.Open("MyFile2.txt"))
{
}

Esto también se puede utilizar cuando los tipos tienen una jerarquía compartida:

using (Stream file = File.Open("MyFile.txt"), data = new MemoryStream())
{
}

La palabra clave var no se puede utilizar en el ejemplo anterior. Se produciría un error de compilación. Incluso la declaración separada por comas no funcionará cuando las variables declaradas tengan tipos de diferentes jerarquías.

Gotcha: devolviendo el recurso que estas tirando.

La siguiente es una mala idea porque eliminaría la variable db antes de devolverla.

public IDBContext GetDBContext()
{
    using (var db = new DBContext())
    {
        return db;
    }
}

Esto también puede crear errores más sutiles:

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age);
    }
}

Esto se ve bien, pero el problema es que la evaluación de la expresión LINQ es perezosa, y posiblemente solo se ejecutará más adelante cuando el DBContext subyacente ya haya sido eliminado.

En resumen, la expresión no se evalúa antes de dejar de using . Una posible solución a este problema, que aún utiliza el using , es hacer que la expresión se evalúe de inmediato llamando a un método que enumere el resultado. Por ejemplo, ToList() , ToArray() , etc. Si está utilizando la versión más reciente de Entity Framework, podría usar los equivalentes async como ToListAsync() o ToArrayAsync() .

A continuación encontrará el ejemplo en acción:

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age).ToList();
    }
}

Sin embargo, es importante tener en cuenta que al llamar a ToList() o ToArray() , la expresión se evaluará con entusiasmo, lo que significa que todas las personas con la edad especificada se cargarán en la memoria incluso si no las itera.

Las declaraciones de uso son nulas seguras

No es necesario comprobar el objeto IDisposable para null . using no lanzará una excepción y no se llamará Dispose() :

DisposableObject TryOpenFile()
{
    return null;
}

// disposable is null here, but this does not throw an exception 
using (var disposable = TryOpenFile())
{
    // this will throw a NullReferenceException because disposable is null
    disposable.DoSomething(); 

    if(disposable != null)
    {
        // here we are safe because disposable has been checked for null
        disposable.DoSomething();
    }
}

Gotcha: Excepción en el método de Disposición que enmascara otros errores en el uso de bloques

Considere el siguiente bloque de código.

try
{
    using (var disposable = new MyDisposable())
    {
        throw new Exception("Couldn't perform operation.");
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

class MyDisposable : IDisposable
{
    public void Dispose()
    {
        throw new Exception("Couldn't dispose successfully.");
    }
}

Puede esperar ver "No se pudo realizar la operación" impresa en la Consola, pero en realidad vería "No se pudo desechar con éxito". ya que aún se llama al método Dispose incluso después de que se lanza la primera excepción.

Vale la pena ser consciente de esta sutileza, ya que puede estar ocultando el error real que evitó que el objeto se eliminara y dificultara la depuración.

Uso de declaraciones y conexiones de base de datos

La palabra clave using garantiza que el recurso definido dentro de la declaración solo exista dentro del alcance de la declaración. Cualquier recurso definido dentro de la declaración debe implementar la interfaz IDisposable .

Estos son increíblemente importantes cuando se trata de conexiones que implementan la interfaz IDisposable , ya que puede garantizar que las conexiones no solo se cierren correctamente, sino que sus recursos se liberen después de que la declaración de using esté fuera del alcance.

Clases de datos IDisposable comunes

Muchas de las siguientes son clases relacionadas con datos que implementan la interfaz IDisposable y son candidatos perfectos para una declaración de using :

  • SqlConnection , SqlCommand , SqlDataReader , etc.
  • OleDbConnection , OleDbCommand , OleDbDataReader , etc.
  • MySqlConnection , MySqlCommand , MySqlDbDataReader , etc.
  • DbContext

Todos estos se usan comúnmente para acceder a los datos a través de C # y se encontrarán comúnmente en las aplicaciones de creación de datos centradas. Se puede esperar que muchas otras clases que no se mencionan y que implementan las mismas FooConnection , FooCommand , FooDataReader se comporten de la misma manera.

Patrón de acceso común para conexiones ADO.NET

Un patrón común que se puede usar al acceder a sus datos a través de una conexión ADO.NET puede tener el siguiente aspecto:

// This scopes the connection (your specific class may vary)
using(var connection = new SqlConnection("{your-connection-string}")
{
    // Build your query
    var query = "SELECT * FROM YourTable WHERE Property = @property");
    // Scope your command to execute
    using(var command = new SqlCommand(query, connection))
    {
         // Open your connection
         connection.Open();

         // Add your parameters here if necessary

         // Execute your query as a reader (again scoped with a using statement)
         using(var reader = command.ExecuteReader())
         {
               // Iterate through your results here
         }
    }
}

O si solo estuvieras realizando una actualización simple y no necesitaras un lector, se aplicaría el mismo concepto básico:

using(var connection = new SqlConnection("{your-connection-string}"))
{
     var query = "UPDATE YourTable SET Property = Value WHERE Foo = @foo";
     using(var command = new SqlCommand(query,connection))
     {
          connection.Open();
          
          // Add parameters here
          
          // Perform your update
          command.ExecuteNonQuery();
     }
}

Uso de declaraciones con DataContexts

Muchos ORM, como Entity Framework, exponen las clases de abstracción que se utilizan para interactuar con las bases de datos subyacentes en forma de clases como DbContext . Estos contextos generalmente implementan también la interfaz IDisposable y deberían aprovechar esto mediante el using declaraciones cuando sea posible:

using(var context = new YourDbContext())
{
      // Access your context and perform your query
      var data = context.Widgets.ToList();
}

Usando la sintaxis de Dispose para definir el alcance personalizado

Para algunos casos de uso, puede usar la sintaxis de using para ayudar a definir un ámbito personalizado. Por ejemplo, puede definir la siguiente clase para ejecutar código en una cultura específica.

public class CultureContext : IDisposable
{
    private readonly CultureInfo originalCulture;

    public CultureContext(string culture)
    {
        originalCulture = CultureInfo.CurrentCulture;
        Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
    }

    public void Dispose()
    {
        Thread.CurrentThread.CurrentCulture = originalCulture;
    }
}

Luego puede usar esta clase para definir bloques de código que se ejecutan en una cultura específica.

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

using (new CultureContext("nl-NL"))
{
    // Code in this block uses the "nl-NL" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25-12-2016 00:00:00
}

using (new CultureContext("es-ES"))
{        
    // Code in this block uses the "es-ES" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25/12/2016 0:00:00
}

// Reverted back to the original culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 12/25/2016 12:00:00 AM

Nota: como no usamos la instancia de CultureContext que creamos, no le asignamos una variable.

Esta técnica es utilizada por el ayudante BeginForm en ASP.NET MVC.

Ejecución de código en contexto de restricción

Si tiene un código (una rutina ) que desea ejecutar en un contexto específico (restricción), puede usar la inyección de dependencia.

El siguiente ejemplo muestra la restricción de ejecutar bajo una conexión SSL abierta. Esta primera parte estaría en su biblioteca o marco, que no expondrá al código del cliente.

public static class SSLContext
{
    // define the delegate to inject
    public delegate void TunnelRoutine(BinaryReader sslReader, BinaryWriter sslWriter);

    // this allows the routine to be executed under SSL
    public static void ClientTunnel(TcpClient tcpClient, TunnelRoutine routine)
    {
        using (SslStream sslStream = new SslStream(tcpClient.GetStream(), true, _validate))
        {
            sslStream.AuthenticateAsClient(HOSTNAME, null, SslProtocols.Tls, false);

            if (!sslStream.IsAuthenticated)
            {
                throw new SecurityException("SSL tunnel not authenticated");
            }

            if (!sslStream.IsEncrypted)
            {
                throw new SecurityException("SSL tunnel not encrypted");
            }

            using (BinaryReader sslReader = new BinaryReader(sslStream))
            using (BinaryWriter sslWriter = new BinaryWriter(sslStream))
            {
                routine(sslReader, sslWriter);
            }
        }
    }
}

Ahora el código de cliente que quiere hacer algo bajo SSL pero no quiere manejar todos los detalles de SSL. Ahora puede hacer lo que quiera dentro del túnel SSL, por ejemplo, intercambiar una clave simétrica:

public void ExchangeSymmetricKey(BinaryReader sslReader, BinaryWriter sslWriter)
{
    byte[] bytes = new byte[8];
    (new RNGCryptoServiceProvider()).GetNonZeroBytes(bytes);
    sslWriter.Write(BitConverter.ToUInt64(bytes, 0));
}

Ejecuta esta rutina de la siguiente manera:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

Para hacer esto, necesita la cláusula using() porque es la única manera (aparte de un bloque try..finally ) puede garantizar que el código del cliente ( ExchangeSymmetricKey ) nunca salga sin disponer de los recursos desechables. Sin using() cláusula using() , nunca sabría si una rutina podría romper la restricción del contexto para disponer de esos recursos.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow