Recherche…


Introduction

Fournit une syntaxe pratique qui garantit l'utilisation correcte des objets IDisposables .

Syntaxe

  • en utilisant (jetable) {}
  • using (IDisposable disposable = new MyDisposable ()) {}

Remarques

L'objet dans l'instruction using doit implémenter l'interface IDisposable .

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

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

Des exemples plus complets d'implémentation IDisposable peuvent être trouvés dans la documentation MSDN .

Utilisation des principes de base de l'instruction

using du sucre syntaxique vous permet de garantir qu'une ressource est nettoyée sans avoir besoin d'un bloc try-finally explicite. Cela signifie que votre code sera beaucoup plus propre et que vous ne perdrez pas de ressources non gérées.

Standard Dispose motif de nettoyage, pour les objets qui mettent en œuvre l' IDisposable interface (dont le FileStream classe de base de Stream fait 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 simplifie votre syntaxe en masquant le try-finally explicite:

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
}

Tout comme finally blocs s'exécutent toujours indépendamment des erreurs ou des retours, en using toujours les appels Dispose() , même en cas d'erreur:

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
}

Remarque: L' appel de Dispose étant garanti quel que soit le flux de code, assurez-vous que Dispose ne lève jamais une exception lorsque vous implémentez IDisposable . Sinon, une exception réelle serait remplacée par la nouvelle exception, ce qui entraînerait un cauchemar de débogage.

Retourner de l'utilisation du bloc

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

En raison de la sémantique de try..finally à laquelle le bloc using traduit, l'instruction return fonctionne comme prévu - la valeur de retour est évaluée avant que finally bloc n'est exécuté et que la valeur ne soit éliminée. L'ordre d'évaluation est le suivant:

  1. Evaluer le corps d' try
  2. Évaluer et mettre en cache la valeur renvoyée
  3. Exécuter enfin le bloc
  4. Renvoie la valeur de retour mise en cache

Cependant, vous ne pouvez pas retourner la variable disposable lui - même, car elle non valide est disposé référence - voir exemple connexe .

Instructions d'utilisation multiples avec un bloc

Il est possible d'utiliser plusieurs imbriquées en using des déclarations sans ajout de plusieurs niveaux d'accolades imbriquées. Par exemple:

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

Une alternative est d'écrire:

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

Ce qui est exactement équivalent au premier exemple.

Note: Nichée en using des déclarations peuvent déclencher la règle d' analyse du code Microsoft CS2002 (voir cette réponse pour la clarification) et générer un avertissement. Comme expliqué dans la réponse liée, il est généralement préférable de nidifier à l' using instructions.

Lorsque les types de l'instruction using sont du même type, vous pouvez les délimiter par des virgules et spécifier le type une seule fois (bien que cela soit rare):

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

Cela peut également être utilisé lorsque les types ont une hiérarchie partagée:

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

Le mot-clé var ne peut pas être utilisé dans l'exemple ci-dessus. Une erreur de compilation se produirait. Même la déclaration séparée par des virgules ne fonctionnera pas lorsque les variables déclarées ont des types de différentes hiérarchies.

Gotcha: retourner la ressource que vous disposez

Ce qui suit est une mauvaise idée car cela éliminerait la variable de db avant de la renvoyer.

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

Cela peut également créer des erreurs plus subtiles:

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

Cela semble correct, mais le problème est que l'évaluation de l'expression LINQ est paresseuse et ne sera éventuellement exécutée que lorsque le DBContext sous-jacent aura déjà été supprimé.

Donc en bref, l'expression n'est pas évaluée avant de quitter l' using . Une solution possible à ce problème, qui fait encore l' utilisation de l' using , est de provoquer l'expression d'évaluer immédiatement en appelant une méthode qui énumérera le résultat. Par exemple ToList() , ToArray() , etc. Si vous utilisez la version la plus récente d'Entity Framework, vous pouvez utiliser les contreparties async telles que ToListAsync() ou ToArrayAsync() .

Vous trouverez ci-dessous l'exemple en action:

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

Il est important de noter qu'en appelant ToList() ou ToArray() , l'expression sera évaluée avec empressement, ce qui signifie que toutes les personnes ayant l'âge spécifié seront chargées en mémoire même si vous ne les parcourez pas.

L'utilisation d'instructions est null-safe

Vous n'avez pas à vérifier l'objet IDisposable pour null . using ne lancera pas une exception et Dispose() ne sera pas appelé:

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: Exception dans la méthode Dispose masquant d'autres erreurs dans Utilisation des blocs

Considérons le bloc de code suivant.

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

Vous pouvez vous attendre à voir "Impossible d'effectuer l'opération" imprimé sur la console mais vous verriez réellement "Impossible de s'en débarrasser". comme la méthode Dispose est encore appelée même après la première exception.

Cela vaut la peine d’être conscient de cette subtilité, car cela peut masquer la véritable erreur qui a empêché la destruction de l’objet et le rendre plus difficile à déboguer.

Utilisation des instructions et des connexions à la base de données

Le mot-clé using garantit que la ressource définie dans l'instruction existe uniquement dans la portée de l'instruction elle-même. Toutes les ressources définies dans l'instruction doivent implémenter l'interface IDisposable .

Celles-ci sont extrêmement importantes pour les connexions qui implémentent l'interface IDisposable , car elles permettent de garantir que les connexions sont non seulement correctement fermées mais que leurs ressources sont libérées après que l'instruction using est hors de portée.

Classes de données communes IDisposable

Bon nombre des éléments suivants sont des classes liées aux données qui implémentent l'interface IDisposable et sont des candidats parfaits pour une instruction using :

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

Tous ces éléments sont couramment utilisés pour accéder aux données via C # et seront couramment rencontrés lors de la création d'applications centrées sur les données. De nombreuses autres classes non mentionnées qui implémentent les mêmes FooConnection , FooCommand , FooDataReader peuvent se comporter de la même manière.

Modèle d'accès commun pour les connexions ADO.NET

Un modèle commun pouvant être utilisé pour accéder à vos données via une connexion ADO.NET peut se présenter comme suit:

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

Ou si vous réalisiez une simple mise à jour et que vous n’avez pas besoin de lecteur, le même concept de base s’appliquerait:

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

Utilisation des instructions avec DataContexts

De nombreux ORM, tels que Entity Framework, exposent des classes d'abstraction utilisées pour interagir avec les bases de données sous-jacentes sous la forme de classes telles que DbContext . Ces contextes implémentent généralement également l'interface IDisposable et devraient en tirer parti en using instructions lorsque cela est possible:

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

Utilisation de la syntaxe Dispose pour définir une étendue personnalisée

Pour certains cas d'utilisation, vous pouvez utiliser la syntaxe using pour définir une étendue personnalisée. Par exemple, vous pouvez définir la classe suivante pour exécuter du code dans une culture spécifique.

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

Vous pouvez ensuite utiliser cette classe pour définir des blocs de code qui s'exécutent dans une culture spécifique.

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

Note: comme nous n'utilisons pas l'instance de CultureContext nous créons, nous ne lui assignons pas de variable.

Cette technique est utilisée par l' assistant BeginForm dans ASP.NET MVC.

Exécuter du code dans un contexte de contrainte

Si vous avez du code (une routine ) à exécuter dans un contexte spécifique (contrainte), vous pouvez utiliser l'injection de dépendance.

L'exemple suivant montre la contrainte d'exécution sous une connexion SSL ouverte. Cette première partie serait dans votre bibliothèque ou votre framework, que vous n'exposeriez pas au code client.

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

Maintenant, le code client qui veut faire quelque chose sous SSL mais ne veut pas gérer tous les détails SSL. Vous pouvez maintenant faire ce que vous voulez dans le tunnel SSL, par exemple échanger une clé symétrique:

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

Vous exécutez cette routine comme suit:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

Pour ce faire, vous avez besoin de la clause using() car c'est le seul moyen (sauf un bloc try..finally ) de garantir que le code client ( ExchangeSymmetricKey ) ne se ferme jamais sans éliminer correctement les ressources disponibles. Sans la clause using() , vous ne sauriez jamais si une routine peut briser la contrainte du contexte pour disposer de ces ressources.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow