C# Language
Verklaring gebruiken
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:
- Evalueer het
try
lichaam - Evalueer en bewaar de geretourneerde waarde
- Eindelijk uitvoeren blokkeren
- 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.