Zoeken…


Invoering

Biedt een handige syntaxis die zorgt voor het juiste gebruik van IDisposable- objecten.

Syntaxis

  • gebruik van (wegwerp) {}
  • using (IDisposable disposable = new MyDisposable ()) {}

Opmerkingen

Het object in de using moet de IDisposable interface IDisposable .

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

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

Meer complete voorbeelden voor IDisposable implementatie zijn te vinden in de MSDN-documenten .

Basisinformatie over verklaringen gebruiken

using is syntactische suiker waarmee u kunt garanderen dat een bron wordt opgeruimd zonder dat een expliciet try-finally blok nodig is. Dit betekent dat uw code veel schoner zal zijn en dat u geen niet-beheerde bronnen zult lekken.

Standard Dispose cleanup pattern, voor objecten die de IDisposable interface IDisposable (wat de FileStream basisklasse Stream doet in .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 vereenvoudigt uw syntaxis door de expliciete try-finally verbergen:

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
}

Net zoals finally blokken altijd worden uitgevoerd, ongeacht fouten of geretourneerde bestanden, using altijd aanroepen Dispose() , zelfs in het geval van een fout:

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
}

Opmerking: Aangezien Dispose gegarandeerd wordt aangeroepen ongeacht de codestroom, is het een goed idee om ervoor te zorgen dat Dispose nooit een uitzondering IDisposable wanneer u IDisposable . Anders zou een daadwerkelijke uitzondering worden opgeheven door de nieuwe uitzondering, wat resulteert in een debugging-nachtmerrie.

Terugkeren van het gebruik van blok

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

Vanwege de semantiek van try..finally waaraan het using blok vertaalt, de return statement werken zoals verwacht - de terugkeer waarde wordt geëvalueerd voordat finally blok wordt uitgevoerd en de waarde afgevoerd. De volgorde van evaluatie is als volgt:

  1. Evalueer het try lichaam
  2. Evalueer en bewaar de geretourneerde waarde
  3. Eindelijk uitvoeren blokkeren
  4. Retourneer de gecachte retourwaarde

U mag de variabele disposable zelf echter niet retourneren, omdat deze ongeldige, verwijderde referentie zou bevatten - zie gerelateerd voorbeeld .

Meerdere gebruik van verklaringen met één blok

Het is mogelijk om meerdere geneste using instructies te gebruiken zonder meerdere niveaus van geneste accolades toe te voegen. Bijvoorbeeld:

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

Een alternatief is om te schrijven:

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

Dat is precies gelijk aan het eerste voorbeeld.

Opmerking: Genest using instructies kan Microsoft Code Analysis-regel CS2002 activeren (zie dit antwoord voor verduidelijking) en een waarschuwing genereren. Zoals uitgelegd in het gekoppelde antwoord, is het over het algemeen veilig om te nestelen using statements.

Als de typen in de instructie using van hetzelfde type zijn, kunt u ze door komma's scheiden en het type slechts één keer opgeven (hoewel dit niet gebruikelijk is):

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

Dit kan ook worden gebruikt wanneer de typen een gedeelde hiërarchie hebben:

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

Het sleutelwoord var kan niet worden gebruikt in het bovenstaande voorbeeld. Er zou een compilatiefout optreden. Zelfs de door komma's gescheiden declaratie werkt niet wanneer de gedeclareerde variabelen typen uit verschillende hiërarchieën hebben.

Gotcha: retourneren van de bron die u weggooit

Het volgende is een slecht idee, omdat het de db variabele zou verwijderen voordat deze wordt geretourneerd.

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

Dit kan ook subtielere fouten veroorzaken:

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

Dit ziet er goed uit, maar de vangst is dat de evaluatie van de LINQ-expressie lui is en mogelijk pas later wordt uitgevoerd wanneer de onderliggende DBContext al is verwijderd.

Dus kort gezegd wordt de uitdrukking niet geëvalueerd voordat deze wordt using . Een mogelijke oplossing voor dit probleem, dat nog steeds gebruik maakt van using , is de uitdrukking onmiddellijk evalueren veroorzaken bij het aanroepen van een methode die het resultaat opsommen. Bijvoorbeeld ToList() , ToArray() , enz. Als u de nieuwste versie van Entity Framework gebruikt, kunt u de async tegenhangers zoals ToListAsync() of ToArrayAsync() .

Hieronder vindt u het voorbeeld in actie:

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

Het is echter belangrijk op te merken dat door ToList() of ToArray() aan te roepen, de uitdrukking gretig wordt geëvalueerd, wat betekent dat alle personen met de opgegeven leeftijd in het geheugen worden geladen, zelfs als u er niet op teruggaat.

Het gebruik van statements is null-safe

U hoeft het IDisposable object niet op null . using genereert geen uitzondering en Dispose() wordt niet genoemd:

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: Uitzondering in de methode Dispose maskeert andere fouten in Blokken gebruiken

Overweeg het volgende codeblok.

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

U kunt verwachten dat "Kan bewerking niet uitvoeren" wordt afgedrukt naar de console, maar u zou eigenlijk zien "Kan niet succesvol beschikken". omdat de Dispose-methode nog steeds wordt aangeroepen, zelfs nadat de eerste uitzondering is gegenereerd.

Het is de moeite waard om je bewust te zijn van deze subtiliteit, omdat het de echte fout kan maskeren die verhinderde dat het object werd verwijderd en het moeilijker maakte om te debuggen.

Statements en databaseverbindingen gebruiken

Het sleutelwoord using zorgt ervoor dat de resource die in de instructie is gedefinieerd, alleen bestaat binnen het bereik van de instructie zelf. Alle bronnen die in de instructie zijn gedefinieerd, moeten de IDisposable interface IDisposable .

Deze zijn ongelooflijk belangrijk bij het omgaan met verbindingen die de IDisposable interface IDisposable , omdat deze ervoor kan zorgen dat de verbindingen niet alleen goed worden gesloten, maar dat hun bronnen worden vrijgegeven nadat de using buiten bereik is.

Algemene IDisposable gegevensklassen

Veel van de volgende zijn gegevensgerelateerde klassen die de IDisposable interface IDisposable en perfecte kandidaten zijn voor een using :

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

Al deze worden vaak gebruikt om toegang te krijgen tot gegevens via C # en komen vaak voor in het bouwen van datacentra-applicaties. Van vele andere klassen die niet worden genoemd en die dezelfde FooConnection , FooCommand , FooDataReader klassen FooCommand , kan worden verwacht dat ze zich op dezelfde manier gedragen.

Gemeenschappelijk toegangspatroon voor ADO.NET-verbindingen

Een gemeenschappelijk patroon dat kan worden gebruikt bij het openen van uw gegevens via een ADO.NET-verbinding kan er als volgt uitzien:

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

Of als u alleen een eenvoudige update zou uitvoeren en geen lezer nodig had, zou hetzelfde basisconcept van toepassing zijn:

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

Statements gebruiken met DataContexts

Veel ORM's zoals Entity Framework leggen abstractieklassen bloot die worden gebruikt voor interactie met onderliggende databases in de vorm van klassen zoals DbContext . Deze contexten over het algemeen de uitvoering van de IDisposable -interface zo goed en moet profiteren van deze door middel van using verklaringen indien mogelijk:

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

Dispose Syntax gebruiken om een aangepast bereik te definiëren

Voor sommige use cases, kunt u het gebruiken using syntax om te helpen een aangepaste draagwijdte ervan. U kunt bijvoorbeeld de volgende klasse definiëren om code in een specifieke cultuur uit te voeren.

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

U kunt vervolgens deze klasse gebruiken om codeblokken te definiëren die in een specifieke cultuur worden uitgevoerd.

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

Opmerking: omdat we de door ons CultureContext instantie niet gebruiken, wijzen we er geen variabele aan toe.

Deze techniek wordt gebruikt door de BeginForm helper in ASP.NET MVC.

Code uitvoeren in constraintcontext

Als u code (een routine ) hebt die u onder een specifieke (beperkende) context wilt uitvoeren, kunt u afhankelijkheidsinjectie gebruiken.

Het volgende voorbeeld toont de beperking van het uitvoeren onder een open SSL-verbinding. Dit eerste deel zou zich in uw bibliotheek of raamwerk bevinden, dat u niet blootstelt aan de klantcode.

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

Nu de clientcode die iets wil doen onder SSL maar niet alle SSL-details wil verwerken. Je kunt nu doen wat je wilt in de SSL-tunnel, bijvoorbeeld een symmetrische sleutel uitwisselen:

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

U voert deze routine als volgt uit:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

Om dit te doen, hebt u de clausule using() , omdat dit de enige manier is (afgezien van een try..finally blok) dat u kunt garanderen dat de clientcode ( ExchangeSymmetricKey ) nooit wordt afgesloten zonder de wegwerpbronnen op de juiste manier te verwijderen. Zonder de clausule using() te gebruiken, zou je nooit weten of een routine de beperking van de context om deze bronnen te verwijderen zou kunnen doorbreken.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow