C# Language
Utiliser la déclaration
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:
- Evaluer le corps d'
try
- Évaluer et mettre en cache la valeur renvoyée
- Exécuter enfin le bloc
- 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.