Ricerca…


introduzione

Fornisce una comoda sintassi che garantisce l'uso corretto di oggetti IDisposable .

Sintassi

  • usando (usa e getta) {}
  • utilizzando (IDisposable disposable = new MyDisposable ()) {}

Osservazioni

L'oggetto IDisposable using deve implementare l'interfaccia IDisposable .

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

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

Esempi più completi per l'implementazione IDisposable possono essere trovati nei documenti MSDN .

Utilizzo delle nozioni di base sulle istruzioni

using dello zucchero sintattico consente di garantire che una risorsa venga pulita senza bisogno di un blocco try-finally esplicito. Questo significa che il tuo codice sarà molto più pulito e non perderai risorse non gestite.

Standard Dispose cleanup pattern, per gli oggetti che implementano l'interfaccia IDisposable (che la classe base Stream FileStream esegue in .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 semplifica la sintassi nascondendo l'esplicito 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
}

Proprio come i blocchi finally eseguono sempre indipendentemente da errori o ritorni, using sempre chiamate Dispose() , anche in caso di errore:

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: Poiché si garantisce che Dispose sia chiamato indipendentemente dal flusso del codice, è una buona idea assicurarsi che Dispose non Dispose mai un'eccezione quando si implementa IDisposable . Altrimenti un'eccezione effettiva verrebbe sovrascritta dalla nuova eccezione che risulterebbe in un incubo di debug.

Ritornando dall'utilizzo del blocco

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

A causa delle semantica di try..finally cui il using blocco traduce, il return economico funziona come previsto - il valore di ritorno viene valutata prima finally viene eseguito blocco e il valore disposto. L'ordine di valutazione è il seguente:

  1. Valuta il corpo del try
  2. Valuta e memorizza il valore restituito
  3. Esegui infine il blocco
  4. Restituisce il valore di ritorno memorizzato nella cache

Tuttavia, non è possibile restituire la variabile disposable e disposable stessa, poiché essa conterrebbe un riferimento disposto non valido - vedere l' esempio correlato .

Molteplici utilizzo di istruzioni con un blocco

È possibile utilizzare più nidificate using dichiarazioni senza aggiunta di più livelli di parentesi nidificate. Per esempio:

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

Un'alternativa è scrivere:

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

Quale è esattamente equivalente al primo esempio.

Nota: le istruzioni nidificate using potrebbero attivare la regola CS2002 (vedere questa risposta per chiarimenti) e generare un avviso. Come spiegato nella risposta collegata, è generalmente sicuro annidare using istruzioni.

Quando i tipi all'interno dell'istruzione using sono dello stesso tipo, puoi separarli con virgole e specificare il tipo una sola volta (sebbene questo sia raro):

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

Questo può essere utilizzato anche quando i tipi hanno una gerarchia condivisa:

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

La parola chiave var non può essere utilizzata nell'esempio precedente. Si verificherebbe un errore di compilazione. Anche la dichiarazione separata da virgole non funzionerà quando le variabili dichiarate hanno tipi di gerarchie diverse.

Gotcha: restituire la risorsa che si sta smaltendo

La seguente è una cattiva idea perché eliminerebbe la variabile db prima di restituirla.

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

Questo può anche creare errori più sottili:

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

Questo sembra ok, ma il problema è che la valutazione delle espressioni LINQ è DBContext e probabilmente verrà eseguita solo in un secondo momento, quando il DBContext sottostante è già stato eliminato.

Quindi in breve l'espressione non viene valutata prima di lasciare l' using . Una possibile soluzione a questo problema, il che rende ancora uso di using , è di indurre l'espressione di valutare immediatamente chiamando un metodo che enumerare il risultato. Ad esempio ToList() , ToArray() , ecc. Se si utilizza la versione più recente di Entity Framework, è possibile utilizzare le controparti async come ToListAsync() o ToArrayAsync() .

Di seguito trovi l'esempio in azione:

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

È importante notare, tuttavia, che chiamando ToList() o ToArray() , l'espressione verrà valutata con entusiasmo, il che significa che tutte le persone con l'età specificata verranno caricate in memoria anche se non si esegue iterazione su di esse.

L'utilizzo delle istruzioni è sicuro

Non è necessario controllare l'oggetto IDisposable per null . using non genererà un'eccezione e Dispose() non verrà chiamato:

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: Eccezione nel metodo Dispose che maschera altri errori in Uso dei blocchi

Considera il seguente blocco di codice.

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.");
    }
}

Ci si può aspettare di vedere "Impossibile eseguire l'operazione" stampato sulla console ma si vedrebbe effettivamente "Impossibile eseguire correttamente l'operazione". poiché il metodo Dispose viene ancora chiamato anche dopo che la prima eccezione è stata lanciata.

Vale la pena essere consapevoli di questa sottigliezza poiché potrebbe nascondere l'errore reale che impediva la disposizione dell'oggetto e rendeva più difficile il debug.

Utilizzando le istruzioni e le connessioni al database

La parola chiave using assicura che la risorsa definita all'interno dell'istruzione esista solo nell'ambito dell'istruzione stessa. Qualsiasi risorsa definita all'interno dell'istruzione deve implementare l'interfaccia IDisposable .

Questi sono incredibilmente importanti quando si tratta di connessioni che implementano l'interfaccia IDisposable in quanto possono garantire che le connessioni non solo siano chiuse correttamente ma che le loro risorse siano liberate dopo che l'istruzione using è fuori dall'ambito.

Classi di dati comuni IDisposable

Molti dei seguenti sono classi correlate ai dati che implementano l'interfaccia IDisposable e sono candidati perfetti per una dichiarazione using :

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

Tutti questi sono comunemente usati per accedere ai dati tramite C # e verranno comunemente riscontrati durante la creazione di applicazioni incentrate sui dati. FooDataReader si può aspettare che molte altre classi che non sono menzionate che implementano le stesse FooConnection , FooCommand , FooDataReader si comportino allo stesso modo.

Modello di accesso comune per connessioni ADO.NET

Un modello comune che può essere utilizzato quando si accede ai dati tramite una connessione ADO.NET potrebbe essere il seguente:

// 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 se stavi semplicemente eseguendo un semplice aggiornamento e non avessi bisogno di un lettore, si applicherebbe lo stesso concetto di base:

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();
     }
}

Utilizzare le istruzioni con DataContexts

Molti ORM come Entity Framework espongono le classi di astrazione utilizzate per interagire con i database sottostanti sotto forma di classi come DbContext . Questi contesti generalmente implementano anche l'interfaccia IDisposable e dovrebbero trarne vantaggio attraverso l' using dichiarazioni quando possibile:

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

Utilizzare Dispose Syntax per definire l'ambito personalizzato

Per alcuni casi d'uso, è possibile utilizzare la sintassi using per aiutare a definire un ambito personalizzato. Ad esempio, è possibile definire la seguente classe per eseguire codice in una cultura specifica.

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;
    }
}

È quindi possibile utilizzare questa classe per definire blocchi di codice che vengono eseguiti in una cultura specifica.

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: poiché non utilizziamo l'istanza di CultureContext che creiamo, non assegniamo una variabile per questo.

Questa tecnica viene utilizzata dal BeginForm aiutante in ASP.NET MVC.

Esecuzione del codice nel contesto del vincolo

Se si dispone di codice (una routine ) che si desidera eseguire in un contesto specifico (vincolo), è possibile utilizzare l'iniezione della dipendenza.

L'esempio seguente mostra il vincolo di esecuzione in una connessione SSL aperta. Questa prima parte sarà nella libreria o nel framework, che non esporrai al codice 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);
            }
        }
    }
}

Ora il codice client che vuole fare qualcosa sotto SSL ma non vuole gestire tutti i dettagli SSL. Ora puoi fare tutto ciò che vuoi all'interno del tunnel SSL, ad esempio scambiare una chiave simmetrica:

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

Esegui questa routine come segue:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

Per fare ciò, è necessaria la clausola using() perché è l'unico modo (a parte un try..finally block) che puoi garantire che il codice client ( ExchangeSymmetricKey ) non esca mai senza disporre correttamente delle risorse usa e getta. Senza la clausola using() , non si potrebbe mai sapere se una routine potrebbe rompere il vincolo del contesto per smaltire tali risorse.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow