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:

  1. Bewerten Sie den try
  2. Werten Sie den zurückgegebenen Wert aus und zwischenspeichern Sie ihn
  3. Führen Sie zum Schluss Block aus
  4. 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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow