Bazı nesneler çöp toplayıcının yönetmediği kaynaklar tutar: açık dosyalar, ağ soketleri, veritabanı bağlantıları, işletim sistemi tanıtıcıları. Bunlar IDisposable uygular ve kaynağı Dispose() çağırarak serbest bırakırsınız. using ifadesi onu, bir istisna fırlatılsa bile, sabit bir noktada sizin için çağırır.
Bu sayfa using ifadesiyle ilgilidir. Bir dosyanın en üstündeki using System; satırları farklı bir özelliktir: bir namespace'i içe aktaran using yönergesi.
Bir using bloğu
using (declaration) { body } nesneyi oluşturur, gövdeyi çalıştırır, sonra Dispose'u çağırır. Dispose'tan yazdıran küçük bir sınıf bunun tam olarak ne zaman olduğunu gösterir:
Çıktı:
open db
db sends SELECT 1
close db
open api
api sends GET /orders
close api
error handled
İkinci blok istisna fırlatır ve "close api" catch istisnayı ele almadan önce yine de yazdırılır. Son satırda Dispose()'u kendiniz çağırmak yerine using kullanmanın nedeni bu garantidir: elle yapılan bir çağrı, ondan önce fırlatılan herhangi bir istisna tarafından atlanır.
using neye derlenir
Derleyici bir using bloğunu bir try/finally'ye genişletir. Bu ikisi eşdeğerdir:
using (var writer = new StreamWriter("log.txt"))
{
writer.WriteLine("started");
}
// is compiled as:
{
var writer = new StreamWriter("log.txt");
try
{
writer.WriteLine("started");
}
finally
{
if (writer != null) ((IDisposable)writer).Dispose();
}
}
Genişletmeden üç ayrıntı çıkar. Değişken bloğa kapsamlıdır ve içinde salt okunurdur (writer'ı yeniden atayamazsınız). Nesne IDisposable uygulamalıdır, yoksa kod derlenmez. Ve bir null değere izin verilir: Dispose sadece atlanır; bu, bir fabrika hiçbir şey döndürmeyebileceğinde kullanışlıdır.
Dosyalar ve stream'ler
Dosya ve stream sınıfları günlük durumdur. Bir StreamWriter metni bellekte tamponlar ve Dispose tamponu diske boşaltıp dosya tanıtıcısını kapatır:
Çıktı:
id,total
1001,59.90
1002,12.50
Dispose olmadan yazıcının son satırları hâlâ tamponunda duruyor olabilir ve Windows'ta açık tanıtıcı diğer programların (ve sizin sonraki File.Delete'inizin) dosyaya dokunmasını engeller. File.WriteAllText ve File.ReadAllLines gibi yardımcılar içeride açıp dispose eder, bu yüzden using gerektirmezler. Daha fazlası için dosyalar sayfasına bakın.
Birkaç kaynak: dispose sırası
Tek bir blokta birkaç kaynak açmak için using ifadelerini aralarına süslü parantez koymadan üst üste yazın. Oluşturulma sırasının tersiyle dispose edilirler:
Çıktı:
acquire file
acquire buffer
acquire writer
working
release writer
release buffer
release file
acquire a
acquire b
both open
release b
release a
Katmanlı kaynakların ihtiyaç duyduğu ters sıradır: bir yazıcı bir dosyayı saran bir tamponu sarar, bu yüzden yazıcı, tampon dosyaya yazmadan ve dosya kapanmadan önce tampona boşaltmalıdır.
using bildirimi (C# 8)
C# 8 süslü parantezi olmayan bir biçim ekledi. Değişken, çevreleyen kapsam bittiğinde, genellikle metodun sonunda dispose edilir:
static void ExportReport(string path, IEnumerable<string> rows)
{
using var writer = new StreamWriter(path);
writer.WriteLine("Report");
foreach (string row in rows)
{
writer.WriteLine(row);
}
} // writer.Dispose() runs here, at the end of the method
Bir girinti seviyesini kaldırır; bu, bir metot iki üç kaynak kullandığında birikir. Dispose sırası kuralı aynıdır: tek bir kapsamdaki bildirimler ters sırayla dispose edilir. Takas şudur: kaynak kapsam bitene kadar açık kalır, bu yüzden bir dosyanın metot daha fazla iş yapmadan önce kapanması gerekiyorsa blok biçimini koruyun ya da bildirimi kendi { } bloğuna koyun.
Temizliği asenkron olan nesneler için (IAsyncDisposable, örneğin birçok veritabanı bağlantısı ve stream), C# 8'de ayrıca aynı yerde DisposeAsync()'i await eden await using vardır.
Kendi sınıfınızda IDisposable uygulamak
Sınıfınız dispose edilebilir bir şeye sahipse (bir stream, bir timer, bir bağlantı oluşturduysa) ve bu yüzden onu sırayla dispose etmesi gerekiyorsa IDisposable uygulayın. Yaygın durum için bu kısa bir metottur:
Çıktı:
1. user ana logged in
2. order 1001 created
AuditLog disposed
Caught ObjectDisposedException
Bunun izlediği kurallar: Dispose nesnenin sahip olduğu her şeyi dispose eder; onu birden fazla kez çağırmak ikinci seferde hiçbir şey yapmaz; ve dispose edildikten sonra nesneyi kullanmak ObjectDisposedException fırlatır. using (log) ayrıca bloğun bir bildirim yerine mevcut bir değişkeni alabileceğini gösterir.
protected virtual void Dispose(bool disposing) metodu ve bir finalizer içeren daha uzun "dispose pattern" yalnızca bir sınıf doğrudan yönetilmeyen bir tanıtıcı (yerel koddan gelen ham bir pointer) tuttuğunda gerekir. Modern .NET'te bu tür tanıtıcıları bunun yerine bir SafeHandle alt sınıfına sarın; sınıfınızın ihtiyacı olan tek şey yukarıdaki basit sürümdür.
Yaygın hatalar
- Bir
usingbloğunda oluşturulan bir nesneyi döndürmek. Blok çıkarken dispose edilir, bu yüzden çağıran kapalı bir stream alır. Veriyi döndürün ya dausing'in sahipliğini çağırana bırakın. - Stream'lerde ve yazıcılarda
using'i unutmak. Tamponlanmış veri kaybolabilir ve dosyalar kilitli kalır. - Paylaşılan bir nesneyi dispose etmek. Yalnızca sahip olduğunuzu dispose edin. Örneğin uzun ömürlü bir
HttpClientpaylaşılıp yeniden kullanılmak üzere tasarlanmıştır, istek başına oluşturulup dispose edilmek için değil. - Bir nesneyi bloktan sonra kullanmak. Dispose edilmiş bir nesnedeki metotlar
ObjectDisposedExceptionfırlatır. - Çöp toplayıcıya güvenmek. Dosyaları ya da soketleri değil belleği serbest bırakır ve asla
Dispose'u çağırmaz.
Sıkça Sorulan Sorular
C#'ta using ifadesi ne yapar?
using (var x = ...) { ... } blok bittiğinde x.Dispose()'u çağırır; blok ister normal bitsin, ister return ile, ister bir istisna fırlatıldığı için. Derleyici onu Dispose çağrısı finally içinde olan bir try/finally'ye çevirir; böylece kaynak, çöp toplayıcı ne zaman çalışırsa değil, bilinen bir noktada serbest bırakılır.
C#'ta IDisposable nedir?
IDisposable, tek metodu void Dispose() olan bir interface'tir. Bir sınıf, açıkça serbest bırakılması gereken bir şey tuttuğunda onu uygular: bir dosya tanıtıcısı, bir ağ soketi, bir veritabanı bağlantısı, bir timer. Tipi IDisposable uygulayan her nesne, işiniz bittiğinde, genellikle using ile dispose edilmelidir.
using ile using var arasındaki fark nedir?
using (var x = ...) { } bloğunun sonunda dispose eder. using var x = ...; using bildirimi (C# 8 ve sonrası) kendine ait bir bloğa sahip değildir: çevreleyen kapsam bittiğinde, genellikle metodun sonunda dispose eder. Bir kaynağın metodun geri kalanı boyunca yaşaması gerektiğinde iç içeliği azaltır.
Birden fazla using nesnesi hangi sırayla dispose edilir?
Oluşturulma sırasının tersiyle. using (var a = ...) using (var b = ...) { } ile önce b, sonra a dispose edilir. b a'ya bağlı olduğunda, örneğin bir FileStream'i saran bir StreamWriter gibi, istediğiniz sıra budur.
Çöp toplayıcı Dispose'u çağırır mı?
Hayır. Çöp toplayıcı belleği serbest bırakır ve öngörülemeyen daha sonraki bir anda bir finalizer çalıştırabilir, ama asla Dispose'u çağırmaz. Dispose edilmemiş bir dosya kilitli kalabilir ve tampondaki verisi hiç yazılmayabilir. using'in var olma nedeni budur.