Le problème : Les ressources oubliées
Dans nos applications dotnet, le nombre de ressources externes à gérer explose avec les avancées technologiques,
la gestion manuelle des ressources peut rapidement devenir un cauchemar :
- Fichiers non fermés qui bloquent l'accès
- Connexions base de données qui restent ouvertes
- Objets HttpClient non libérés
- Fuites mémoire sur les applications qui s'exécutent longuement
- Exceptions qui interrompent le flux et empêchent la libération
Exemple :
// ❌ Code dangereux - risque de fuite
var fileStream = new FileStream("data.txt", FileMode.Open);
var data = ReadData(fileStream);
// Oubli de fermer le stream !
// Le fichier reste verrouillé...
La solution magique & pratique : Le pattern using
Le pattern using garantit la libération automatique des ressources, même en cas d'exception.
C'est votre femme de ménage personnelle pour les ressources !
// ✅ Code sécurisé avec using
using var fileStream = new FileStream("data.txt", FileMode.Open);
var data = ReadData(fileStream);
// La ressource est automatiquement libérée en fin de scope
Comment ça fonctionne ?
Le pattern using fonctionne avec toute classe implémentant IDisposable
ou IAsyncDisposable
:
// Syntaxe classique
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://api.example.com");
// client.Dispose() appelé automatiquement
}
// Syntaxe moderne C# 8+ (using declaration)
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com");
// client.Dispose() appelé en fin de bloc
Les différentes syntaxes
Le pattern using est un outil puissant pour gérer les ressources de manière efficace et sécurisée.
Il permet de s'assurer que les ressources sont libérées correctement, même en cas d'erreur.
Plusieurs syntaxes existent pour s'adapter à vos besoins et préférences.
Using statement (classique) : Contrôle précis du scope de libération
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// Utilisation de la connexion
} // connection.Dispose() appelé ici
Using declaration (moderne) : Plus concis, libération en fin de méthode
public async Task ProcessDataAsync()
{
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
connection.Open();
// Utilisation...
// Toutes les ressources libérées ici automatiquement
}
Cas d'usage fréquents
// Lecture sécurisée
using var reader = new StreamReader("config.json");
var content = await reader.ReadToEndAsync();
// Écriture sécurisée
using var writer = new StreamWriter("output.txt");
await writer.WriteLineAsync("Hello World!");
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com");
var json = await response.Content.ReadAsStringAsync();
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand("SELECT * FROM Users", connection);
connection.Open();
using var reader = command.ExecuteReader();
// Toutes les ressources SQL libérées automatiquement
Using asynchrone avec await using
Pour les ressources asynchrones, utilisez await using
:
await using var stream = new FileStream("large-file.txt", FileMode.Open);
await using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
// DisposeAsync() appelé automatiquement à la sortie du bloc
Implémentation personnalisée
Pour certains besoins de gestion de ressources, créez vos propres classes disposables en implémentant IDisposable
ou IAsyncDisposable
.
Cela peut être utile pour encapsuler des transactions, des connexions ou d'autres ressources complexes.
public class DatabaseTransaction : IDisposable
{
private readonly SqlTransaction _transaction;
private bool _disposed = false;
public DatabaseTransaction(SqlTransaction transaction)
{
_transaction = transaction;
}
public void Commit() => _transaction.Commit();
public void Rollback() => _transaction.Rollback();
public void Dispose()
{
if (!_disposed)
{
_transaction?.Dispose();
_disposed = true;
}
}
}
// Utilisation
using var transaction = new DatabaseTransaction(sqlTransaction);
try
{
// Opérations...
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
Bonnes pratiques
- Préférez
using var
pour plus de lisibilité
- Utilisez
await using
pour les ressources asynchrones
- N'oubliez pas le pattern dans vos API : laissez l'appelant gérer la ressource
- Testez vos implémentations IDisposable avec des using
Implémenté correctement, il devient impossible d'oublier de libérer une ressource avec le pattern using !
Conclusion
Le pattern using transforme la gestion des ressources en automatisme sécurisé, éliminant les fuites mémoire !
En adoptant systématiquement le pattern using dans vos applications .NET, vous garantissez une gestion des ressources
robuste et prévisible. C'est un investissement minimal pour un gain énorme en stabilité et en maintenabilité.