Sök…


Introduktion

Ger en bekväm syntax som säkerställer korrekt användning av IDisponerbara objekt.

Syntax

  • använder (engångsbruk) {}
  • använder (IDisposable disposable = new MyDisposable ()) {}

Anmärkningar

Objektet i det using uttalandet måste implementera det IDisposable gränssnittet.

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

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

Mer kompletta exempel för IDisposable implementering kan hittas i MSDN-dokumenten .

Använda uttalande grunder

using är syntaktiskt socker som gör att du kan garantera att en resurs städas upp utan att behöva ett uttryckligt try-finally block. Detta betyder att din kod kommer att vara mycket renare och att du inte läcker resurser som inte hanteras.

Standard Dispose sanering mönster, för objekt som implementera IDisposable gränssnitt (som FileStream s basklass Stream gör i 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 förenklar din syntax genom att dölja det uttryckliga 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
}

Precis som att finally block alltid körs oavsett fel eller returer, using alltid samtal Dispose() , även i händelse av ett fel:

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
}

Obs: Eftersom Dispose garanteras att kallas oavsett kodflöde är det en bra idé att se till att Dispose aldrig kastar ett undantag när du implementerar IDisposable . Annars skulle ett faktiskt undantag åsidosättas av det nya undantaget som resulterar i en felsökningsmardröm.

Återvänder från att använda blocket

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

På grund av semantiken för try..finally till vilken using blocket översätter de return Statement verk som förväntat - utvärderas returvärdet innan finally blocket exekveras och värdet anordnad. Utvärderingsordningen är som följer:

  1. Utvärdera try
  2. Utvärdera och cache det returnerade värdet
  3. Kör slutligen blockera
  4. Returnera det cachade returvärdet

Däremot kan du inte tillbaka den rörliga disposable själv, eftersom det skulle innehålla ogiltig, placerade referens - se relaterade exempel .

Multipla med uttalanden med ett block

Det är möjligt att använda flera kapslade using uttalanden utan att lägga till flera nivåer av kapslade hängslen. Till exempel:

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

Ett alternativ är att skriva:

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

Vilket är exakt motsvarande det första exemplet.

Obs: Häckt using uttalanden kan utlösa Microsoft Code Analys regel CS2002 (se detta svar för att förtydliga) och generera en varning. Som förklarats i det länkade svaret är det i allmänhet säkert att bygga bo using uttalanden.

När typerna inom det using uttalandet är av samma typ kan du komma-avgränsa dem och ange typen endast en gång (även om detta är ovanligt):

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

Detta kan också användas när typerna har en delad hierarki:

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

Den var nyckelordet kan inte användas i ovanstående exempel. Ett kompilationsfel skulle uppstå. Till och med den kommaseparerade deklarationen fungerar inte när de deklarerade variablerna har typer från olika hierarkier.

Gotcha: returnera resursen som du kasserar

Följande är en dålig idé eftersom den skulle bortskaffa db variabeln innan du returnerar den.

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

Detta kan också skapa mer subtila misstag:

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

Detta ser ok ut, men fångsten är att utvärderingen av LINQ-uttrycket är lat och kommer kanske först att genomföras senare när den underliggande DBContext redan har bortskaffats.

Så kort sagt uttrycket inte utvärderas innan de lämnar using . En möjlig lösning på detta problem, som fortfarande använder sig av att using , är att få uttrycket att utvärderas omedelbart genom att anropa en metod som kommer att räkna upp resultatet. Till exempel ToList() , ToArray() osv. Om du använder den senaste versionen av Entity Framework kan du använda async motsvarigheter som ToListAsync() eller ToArrayAsync() .

Nedan hittar du exemplet i handling:

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

Det är dock viktigt att notera att uttrycket kommer att utvärderas ivrigt genom att ringa ToList() eller ToArray() , vilket innebär att alla personer med den angivna åldern kommer att laddas till minnet även om du inte upprepar dem.

Att använda uttalanden är noll-säkert

Du behöver inte kontrollera det IDisposable objektet för null . using kommer inte att kasta ett undantag och Dispose() kommer inte att kallas:

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: Undantag i avyttringsmetod som maskerar andra fel i att använda block

Tänk på följande kodblock.

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

Du kan förvänta dig att se "Det gick inte att utföra operationen" skrivs ut på konsolen men du skulle faktiskt se "Kunde inte kassera framgångsrikt." eftersom avfallsmetoden fortfarande kallas även efter att det första undantaget kastats.

Det är värt att vara medveten om denna subtilitet eftersom det kan maskera det verkliga felet som förhindrade att objektet kastades och gör det svårare att felsöka.

Använda uttalanden och databasanslutningar

Det using nyckelordet säkerställer att resursen som definieras i uttalandet endast finns inom ramen för själva uttalandet. Alla resurser som definieras i uttalandet måste implementera det IDisposable gränssnittet.

Dessa är oerhört viktiga när du hanterar alla anslutningar som implementerar det IDisposable gränssnittet, eftersom det kan säkerställa att anslutningarna inte bara är ordentligt stängda utan att deras resurser frigörs efter att using är utanför räckvidden.

Gemensamma IDisposable

Många av följande är IDisposable klasser som implementerar det IDisposable gränssnittet och är perfekta kandidater för ett using uttalande:

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

Alla dessa används vanligtvis för att få åtkomst till data via C # och kommer ofta att stöta på i byggande av datacentriska applikationer. Många andra klasser som inte nämns som implementerar samma FooConnection , FooCommand , FooDataReader klasser kan förväntas bete sig på samma sätt.

Gemensamt åtkomstmönster för ADO.NET-anslutningar

Ett vanligt mönster som kan användas när du kommer åt dina data via en ADO.NET-anslutning kan se ut enligt följande:

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

Eller om du bara utförde en enkel uppdatering och inte krävde en läsare, skulle samma grundkoncept gälla:

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

Använda uttalanden med DataContexts

Många ORM: er, som Entity Framework, visar abstraktionsklasser som används för att interagera med underliggande databaser i form av klasser som DbContext . Dessa sammanhang implementerar i allmänhet också det IDisposable gränssnittet och bör dra nytta av detta genom att using uttalanden när det är möjligt:

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

Använd Dispose Syntax för att definiera anpassat omfattning

För vissa användningsfall kan du använda using syntax för att definiera en anpassad omfattning. Du kan till exempel definiera följande klass för att köra kod i en specifik kultur.

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

Du kan sedan använda denna klass för att definiera kodblock som körs i en specifik kultur.

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

Obs: eftersom vi inte använder den CultureContext instans som vi skapar tilldelar vi inte en variabel för den.

Den här tekniken används av BeginForm hjälparen i ASP.NET MVC.

Utför kod i begränsningssammanhang

Om du har kod (en rutin ) som du vill köra under ett specifikt (begränsat) sammanhang kan du använda beroendeinjektion.

Följande exempel visar begränsningen för att köra under en öppen SSL-anslutning. Den första delen skulle finnas i ditt bibliotek eller ramverk, som du inte utsätter för klientkoden.

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 är klientkoden som vill göra något under SSL men inte vill hantera alla SSL-detaljer. Du kan nu göra vad du vill i SSL-tunneln, till exempel byta ut en symmetrisk nyckel:

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

Du kör denna rutin enligt följande:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

För att göra detta måste du using() klausulen using() eftersom det är det enda sättet (bortsett från ett try..finally blockera) kan du garantera att klientkoden ( ExchangeSymmetricKey ) aldrig går ut utan att disponera de disponibla resurserna ordentligt. Utan att using() klausul, skulle du aldrig veta om en rutin kan bryta sammanhangets begränsning för att bortskaffa dessa resurser.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow