C# Language
Anweisung verwenden
Suche…
Einführung
Stellt eine praktische Syntax bereit , die die korrekte Verwendung von IDisposable- Objekten gewährleistet .
Syntax
- mit (Einweg) {}
- using (IDisposable disposable = new MyDisposable ()) {}
Bemerkungen
Das Objekt in der using
Anweisung muss die IDisposable
Schnittstelle implementieren.
using(var obj = new MyObject())
{
}
class MyObject : IDisposable
{
public void Dispose()
{
// Cleanup
}
}
Ausführlichere Beispiele für die IDisposable
Implementierung finden Sie in den MSDN-Dokumenten .
Statement-Grundlagen verwenden
using
syntaktischem Zucker ermöglicht es Ihnen sicherzustellen, dass eine Ressource bereinigt wird, ohne dass eine explizite try-finally
Blockierung erforderlich ist. Dies bedeutet, dass Ihr Code viel sauberer ist und Sie keine nicht verwalteten Ressourcen verlieren.
Standard Dispose
Bereinigungsmuster für Objekte, die die IDisposable
Schnittstelle implementieren (die der FileStream
-Basisklasse Stream
in .NET verwendet):
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
vereinfacht Ihre Syntax, indem Sie das explizite 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
}
Ebenso wie finally
Blöcke werden immer Fehler und Rückgaben immer ausgeführt, using
immer Dispose()
, auch im Fehlerfall:
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
}
Hinweis: Da garantiert ist, dass Dispose
unabhängig vom Code-Fluss Dispose
wird, sollten Sie sicherstellen, dass Dispose
niemals eine Ausnahme IDisposable
wenn Sie IDisposable
implementieren. Andernfalls würde eine tatsächliche Ausnahme von der neuen Ausnahme überschrieben, was zu einem Alptraum für das Debugging führt.
Rückkehr aus dem Block
using ( var disposable = new DisposableItem() )
{
return disposable.SomeProperty;
}
Wegen der Semantik von try..finally
, auf die die using
von Block übersetzt, die return
arbeitet Aussage als erwartete - der Rückgabewert ausgewertet wird , bevor finally
Block ausgeführt wird , und der Wert angeordnet ist . Die Reihenfolge der Bewertung lautet wie folgt:
- Bewerten Sie den
try
- Werten Sie den zurückgegebenen Wert aus und zwischenspeichern Sie ihn
- Führen Sie zum Schluss Block aus
- Gibt den zwischengespeicherten Rückgabewert zurück
Sie können jedoch nicht die Variable zurückgeben disposable
selbst, da sie ungültig enthalten würden, entsorgt reference - siehe verwandtes Beispiel .
Mehrere using-Anweisungen mit einem Block
Es ist möglich, mehrere verschachtelte using
Anweisungen zu verwenden, ohne mehrere geschachtelte geschweifte Klammern hinzuzufügen. Zum Beispiel:
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
Eine Alternative ist zu schreiben:
using (var input = File.OpenRead("input.txt"))
using (var output = File.OpenWrite("output.txt"))
{
input.CopyTo(output);
} // output and then input are disposed here
Was genau dem ersten Beispiel entspricht.
Hinweis: Verschachtelte using
Anweisungen können die Microsoft-Code-Analysis-Regel CS2002 auslösen (zur Erläuterung siehe diese Antwort ) und eine Warnung generieren. Wie in der verknüpften Antwort erläutert, ist das Schachteln using
Anweisungen im Allgemeinen sicher.
Wenn die Typen in der using
Anweisung vom gleichen Typ sind, können Sie sie durch Kommas voneinander trennen und den Typ nur einmal angeben (dies ist jedoch nicht üblich):
using (FileStream file = File.Open("MyFile.txt"), file2 = File.Open("MyFile2.txt"))
{
}
Dies kann auch verwendet werden, wenn die Typen eine gemeinsame Hierarchie haben:
using (Stream file = File.Open("MyFile.txt"), data = new MemoryStream())
{
}
Das Schlüsselwort var
kann im obigen Beispiel nicht verwendet werden. Ein Kompilierungsfehler würde auftreten. Sogar die durch Kommas getrennte Deklaration funktioniert nicht, wenn die deklarierten Variablen Typen aus unterschiedlichen Hierarchien haben.
Gotcha: Rückgabe der Ressource, die Sie zur Verfügung haben
Das Folgende ist eine schlechte Idee, da die db
Variable vor der Rückgabe beseitigt würde.
public IDBContext GetDBContext()
{
using (var db = new DBContext())
{
return db;
}
}
Dies kann auch subtilere Fehler verursachen:
public IEnumerable<Person> GetPeople(int age)
{
using (var db = new DBContext())
{
return db.Persons.Where(p => p.Age == age);
}
}
Das sieht in Ordnung aus, aber der Haken ist, dass die LINQ-Ausdrucksauswertung faul ist und möglicherweise erst dann ausgeführt wird, wenn der zugrunde liegende DBContext
bereits DBContext
wurde.
Kurz gesagt, der Ausdruck wird nicht ausgewertet, bevor die using
. Eine mögliche Lösung für dieses Problem, die immer noch verwendet using
, besteht darin, den Ausdruck sofort auszuwerten, indem eine Methode aufgerufen wird, die das Ergebnis auflistet. Zum Beispiel ToList()
, ToArray()
, etc. Wenn Sie die neueste Version von Entity Framework verwenden , können Sie die Verwendung async
Pendants wie ToListAsync()
oder ToArrayAsync()
.
Nachfolgend finden Sie das Beispiel in Aktion:
public IEnumerable<Person> GetPeople(int age)
{
using (var db = new DBContext())
{
return db.Persons.Where(p => p.Age == age).ToList();
}
}
Es ist jedoch wichtig anzumerken, dass durch den Aufruf von ToList()
oder ToArray()
der Ausdruck eifrig ausgewertet wird, was bedeutet, dass alle Personen mit dem angegebenen Alter in den Speicher geladen werden, auch wenn Sie sie nicht wiederholen.
Die Verwendung von Anweisungen ist nullsicher
Sie müssen das IDisposable
Objekt nicht auf null
überprüfen. using
löst keine Ausnahme aus und Dispose()
wird nicht aufgerufen:
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 in Dispose-Methode, die andere Fehler bei der Verwendung von Blöcken maskiert
Betrachten Sie den folgenden Code-Block.
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.");
}
}
Sie können erwarten, dass auf der Konsole die Meldung "Vorgang konnte nicht ausgeführt werden" angezeigt werden, tatsächlich würden Sie jedoch sehen, dass "Nicht erfolgreich entsorgt werden konnte". Die Dispose-Methode wird auch dann aufgerufen, wenn die erste Ausnahme ausgelöst wird.
Es ist empfehlenswert, sich dieser Subtilität bewusst zu sein, da sie möglicherweise den tatsächlichen Fehler maskiert, der die Beseitigung des Objekts verhindert und das Debuggen erschwert.
Verwenden von Anweisungen und Datenbankverbindungen
Das Schlüsselwort using
stellt sicher, dass die in der Anweisung definierte Ressource nur im Geltungsbereich der Anweisung selbst vorhanden ist. Alle in der Anweisung definierten Ressourcen müssen die IDisposable
Schnittstelle implementieren.
Diese sind beim Umgang mit Verbindungen, die die IDisposable
Schnittstelle implementieren, IDisposable
, da sie sicherstellen können, dass die Verbindungen nicht nur ordnungsgemäß geschlossen werden, sondern auch, dass ihre Ressourcen freigegeben werden, nachdem die using
Anweisung außerhalb des Gültigkeitsbereichs liegt.
Häufige IDisposable
Viele der folgenden Klassen sind IDisposable
Klassen, die die IDisposable
Schnittstelle implementieren und IDisposable
Kandidaten für eine using
Anweisung sind:
-
SqlConnection
,SqlCommand
,SqlDataReader
usw. -
OleDbConnection
,OleDbCommand
,OleDbDataReader
usw. -
MySqlConnection
,MySqlCommand
,MySqlDbDataReader
usw. -
DbContext
Alle diese Optionen werden im Allgemeinen für den Zugriff auf Daten über C # verwendet. Sie werden häufig in gebäudedatenzentrierten Anwendungen angetroffen. Von vielen anderen Klassen, die nicht erwähnt werden und die dieselben Klassen FooConnection
, FooCommand
und FooDataReader
FooCommand
, kann erwartet werden, dass sie sich genauso verhalten.
Allgemeines Zugriffsmuster für ADO.NET-Verbindungen
Ein allgemeines Muster, das beim Zugriff auf Ihre Daten über eine ADO.NET-Verbindung verwendet werden kann, könnte wie folgt aussehen:
// 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
}
}
}
Oder wenn Sie nur ein einfaches Update durchführen und keinen Leser benötigen, würde dasselbe Grundkonzept gelten:
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();
}
}
Anweisungen mit DataContexts verwenden
Viele ORMs wie Entity Framework machen Abstraktionsklassen verfügbar, die zur Interaktion mit darunterliegenden Datenbanken in Form von Klassen wie DbContext
. Diese Kontexte implementieren im Allgemeinen auch die IDisposable
Schnittstelle und sollten dies durch die using
Anweisungen nutzen, wenn möglich:
using(var context = new YourDbContext())
{
// Access your context and perform your query
var data = context.Widgets.ToList();
}
Verwenden von Dispose Syntax zum Definieren des benutzerdefinierten Bereichs
In einigen Anwendungsfällen können Sie mithilfe der Syntax using
einen benutzerdefinierten Bereich definieren. Sie können beispielsweise die folgende Klasse definieren, um Code in einer bestimmten Kultur auszuführen.
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;
}
}
Mit dieser Klasse können Sie dann Codeblöcke definieren, die in einer bestimmten Kultur ausgeführt werden.
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
Hinweis: CultureContext
wir die von uns erstellte CultureContext
Instanz nicht verwenden, weisen wir ihr keine Variable zu.
Diese Technik wird vom BeginForm
Helfer in ASP.NET-MVC verwendet.
Ausführen von Code im Kontext der Einschränkung
Wenn Sie Code (eine Routine ) haben, die Sie in einem bestimmten Kontext (Constraint) ausführen möchten, können Sie Abhängigkeitseinspritzung verwenden.
Das folgende Beispiel zeigt die Einschränkung der Ausführung unter einer offenen SSL-Verbindung. Dieser erste Teil würde sich in Ihrer Bibliothek oder Ihrem Framework befinden, die Sie dem Client-Code nicht zugänglich machen.
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);
}
}
}
}
Nun der Client-Code, der etwas unter SSL tun möchte, aber nicht alle SSL-Details verarbeiten möchte. Sie können jetzt innerhalb des SSL-Tunnels tun, was Sie möchten, beispielsweise einen symmetrischen Schlüssel austauschen:
public void ExchangeSymmetricKey(BinaryReader sslReader, BinaryWriter sslWriter)
{
byte[] bytes = new byte[8];
(new RNGCryptoServiceProvider()).GetNonZeroBytes(bytes);
sslWriter.Write(BitConverter.ToUInt64(bytes, 0));
}
Sie führen diese Routine wie folgt aus:
SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);
Dazu benötigen Sie die using()
Klausel, da dies die einzige Möglichkeit ist (abgesehen von einem try..finally
Block), dass der Client-Code ( ExchangeSymmetricKey
) niemals beendet wird, ohne die verfügbaren Ressourcen ordnungsgemäß zu entsorgen. Ohne die using()
Klausel using()
würden Sie niemals wissen, ob eine Routine die Einschränkung des Kontextes für die Entsorgung dieser Ressourcen durchbrechen könnte.