C# Language
Använda uttalande
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:
- Utvärdera
try
- Utvärdera och cache det returnerade värdet
- Kör slutligen blockera
- 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.