Akış nesneleri için Close () veya Dispose () çağırmalı mıyım?


163

Gibi sınıfları Stream, StreamReader, StreamWritervb uygular IDisposablearayüz. Bu Dispose(), bu sınıfların nesneleri üzerinde yöntem çağırabileceğimiz anlamına gelir . Ayrıca publicadında bir yöntem de tanımladılar Close(). Şimdi, nesnelerle işim bittiğinde ne aramalıyım diye kafamı karıştırıyor? Ya ikisini de ararsam?

Mevcut kodum şu:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

Gördüğünüz gibi , her nesnede using()otomatik olarak Dispose()yöntemi çağıran yapılar yazdım . Ama aynı zamanda Close()yöntemleri de çağırıyorum . Doğru mu?

Lütfen akış nesnelerini kullanırken bana en iyi uygulamaları önerin. :-)

MSDN örneği, using()yapıları ve çağrı Close()yöntemini kullanmaz:

İyi mi?


ReSharper kullanıyorsanız, bunu kalıp kataloğu içinde bir "anti-model" olarak tanımlayabilirsiniz. ReSharper, tanımınıza göre her bir kullanımı hata / ipucu / uyarı olarak işaretleyecektir. ReSharper'ın böyle bir durum için bir QuickFix'i nasıl uygulaması gerektiğini tanımlamak da mümkündür.
Thorsten Hans

3
Sadece bir ipucu: Birden fazla tek kullanımlık itens için şu şekilde using ifadesini kullanabilirsiniz: (Stream responseStream = response.GetResponseStream ()) kullanarak (StreamWriter writer = new StreamWriter (filename)) kullanarak (StreamReader reader = new StreamReader (responseStream)) kullanarak {//...Some code}
Latrova


Kullandığınız ifadeleri, bunları birbirinin üzerine istifleyebileceğiniz ve bir dizi paranteziniz olacak şekilde iç içe geçirmenize gerek yoktur. Başka bir gönderide, "kod okunuza" bakmak ve düzeltmek istiyorsanız, bu teknikle ifadeler kullanması gereken bir kod parçacığı için bir düzenleme önerdim: stackoverflow.com/questions/5282999/…
Timothy Gonzalez

2
@ Suncat2000 Birden fazla kullanım ifadesine sahip olabilirsiniz, ancak onları iç içe geçiremez ve bunun yerine istifleyemezsiniz. Ben türünü kısıtlayan böyle sözdizimi anlamına gelmez: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Türü yeniden tanımlayabileceğiniz bu şekilde demek istiyorum:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Yanıtlar:


111

Reflector.NET'e hızlı bir atlama, Close()yöntemin StreamWriterşu şekilde olduğunu gösterir :

public override void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

Ve StreamReader:

public override void Close()
{
    this.Dispose(true);
}

Dispose(bool disposing)İçinde geçersiz kılma StreamReadergeçerli:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

StreamWriterYöntem benzerdir.

Yani, kod okuma açıktır ki Arayabileceğin o Close()& Dispose()gibi ve herhangi bir sırada senin kadar sık akışlarında. Davranışı hiçbir şekilde değiştirmez.

Yani bunun kullanımına daha okunaklı olup olmadığına aşağı gelir Dispose(), Close()ve / veya using ( ... ) { ... }.

Kişisel tercihim, using ( ... ) { ... }"makasla koşmamanıza" yardımcı olduğu için mümkün olduğunda her zaman kullanılması gerektiğidir.

Ancak bu, doğruluğa yardımcı olurken okunabilirliği azaltır. C # 'da zaten çok sayıda kapanış küme parantezimiz var, bu yüzden hangisinin akışta kapanışı gerçekleştirdiğini nasıl bileceğiz?

Bu yüzden bunu yapmanın en iyisi olduğunu düşünüyorum:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

Kodun davranışını etkilemez, ancak okunabilirliğe yardımcı olur.


20
" C #'da zaten çok sayıda kapatma küme parantezimiz var, bu yüzden hangisinin akışta kapanışı gerçekten gerçekleştirdiğini nasıl bileceğiz? " Bunun büyük bir sorun olduğunu düşünmüyorum: Akış "doğru zamanda" kapatılır, yani, değişken kapsam dışına çıktığında ve artık gerekli olmadığında.
Heinzi

111
Hmm, hayır, bu "neden iki kez kapatıyor ??" okurken hız artışı.
Hans Passant

59
Gereksiz Close()aramaya katılmıyorum . Daha az deneyimli biri koda bakarsa ve bilmediği usingtakdirde: 1) kodu araştırıp öğrenecek veya 2) körü körüne Close()manuel olarak ekleyecektir . Eğer 2) 'yi seçerse, belki başka bir geliştirici fazlalığı görecek Close()ve "kıkırdama" yerine daha az deneyimli geliştiriciye talimat verecektir . Deneyimsiz geliştiriciler için hayatı zorlaştırmaktan yana değilim, ancak onları deneyimli geliştiricilere dönüştürmekten yanayım.
R. Martinho Fernandes

14
+ Close () kullanırsanız ve açarsanız / analiz ederseniz, "uyarı: CA2202: Microsoft.Usage: Nesne 'f', 'Foo (dize)' yönteminde birden çok kez atılabilir. Bir Sistem oluşturmayı önlemek için. ObjectDisposedException bir nesnede Dispose'u birden fazla kez çağırmamalısınız .: Lines: 41 "Bu nedenle, mevcut uygulama Close and Dispose çağırmak için uygun olsa da, dokümantasyon ve / analiz'e göre, sorun değil ve öğesinin gelecekteki sürümlerinde değişebilir. ağ.
marc40000

4
İyi cevap için +1. Dikkate alınması gereken başka bir şey. Neden kapanış ayracından sonra // Kapat veya benim yaptığım gibi bir yorum eklemeyelim, acemi olarak, net olmayan herhangi bir kapanış ayracından sonra bir satır ekliyorum. Örneğin, uzun bir sınıfta son kapanış ayracından sonra // Bitiş Ad Alanı XXX ve ikinci son kapanış ayracından sonra // Son Sınıf YYY eklerim. Yorumlar bunun için değil mi? Sadece merak. :) Bir acemi olarak böyle bir kod gördüm, bu yüzden buraya neden geldiğimi anladım. "Neden ikinci kapanışa ihtiyaç var" sorusunu sordum. Ekstra kod satırlarının netliğe katkıda bulunmadığını hissediyorum. Afedersiniz.
Francis Rodgers

53

Hayır, bu yöntemleri manuel olarak çağırmamalısınız. usingBloğun sonunda, Dispose()yönetilmeyen kaynakları serbest bırakmaya özen gösteren yöntem otomatik olarak çağrılır (en azından akışlar, okuyucular / yazarlar, ... gibi standart .NET BCL sınıfları için). Böylece kodunuzu şu şekilde de yazabilirsiniz:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

Close()Yöntem için, Dispose().


1
Okuyucu elden çıkarıldığında kapandığından emin olacak şekilde sarıldığı usingiçin ilk olmanıza gerek olmadığından oldukça eminim . +1 yine deresponseStreamreader
Isak Savo

Söylediğinizde bu kafa karıştırıcı The Close method calls Dispose... ve yazınızın geri kalanında, Dispose()bunun arayacağını ima ediyorsunuz Close(), ikincisini manuel olarak aramamalıyım. Birbirlerini aradıklarını mı söylüyorsunuz?
Nawaz

@Nawaz, yazım kafa karıştırıcıydı. Close yöntemi sadece Dispose'u çağırır. Sizin durumunuzda, yönetilmeyen kaynakları serbest bırakmak için Dispose'a ihtiyacınız var. Kodunuzu using deyimine sararak Dispose yöntemi çağrılır.
Darin Dimitrov

3
Korkunç cevap. Bir usingblok kullanabileceğinizi varsayar . Zaman zaman yazan ve bu nedenle yazamayan bir sınıf uyguluyorum.
Jez

5
@Jez Sınıfınız daha sonra IDisposable arabirimini uygulamalı ve muhtemelen close () alandaki standart terminoloji ise Close () , böylece sınıfınızı kullanan sınıflar kullanabilir using(veya yine Dispose Pattern'e gidebilir).
Dorus

14

Belgeler, bu iki yöntemin eşdeğer olduğunu söylüyor:

StreamReader.Close : Bu Close uygulaması gerçek bir değer geçiren Dispose yöntemini çağırır.

StreamWriter.Close : Bu Close uygulaması gerçek bir değer geçiren Dispose yöntemini çağırır.

Stream.Close : Bu yöntem Dispose çağırır ve tüm kaynakları serbest bırakmak için true öğesini belirtir .

Dolayısıyla, her ikisi de eşit derecede geçerlidir:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

Kişisel olarak, daha az "gürültü" içerdiği için ilk seçeneğe bağlı kalırım.


5

Her iki destekleyen birçok sınıflar üzerinde Close()ve Dispose()yöntemleri, iki çağrıları eşdeğer olacaktır. Ancak bazı sınıflarda kapatılmış bir nesneyi yeniden açmak mümkündür. Bu tür bazı sınıflar, yeniden açılmaya izin vermek için Kapanıştan sonra bazı kaynakları canlı tutabilir; diğerleri herhangi bir kaynağı canlı tutmayabilir Close(), ancak Dispose()yeniden açmayı açıkça yasaklamak için bir bayrak koyabilir .

Sözleşme, IDisposable.Disposeaçıkça, bir daha asla kullanılmayacak bir nesnede çağrılmasının en kötü ihtimalle zararsız olmasını gerektirmektedir, bu nedenle , biri de çağırsın ya IDisposable.Disposeda çağırmasın Dispose(), her IDisposablenesnede çağrılan bir yöntemi veya bir yöntemi çağırmanızı tavsiye ederim Close().


Bilginize, İşte Kapat ve Atma eğlencesini açıklayan MSDN bloglarında bir makale. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
Jamie Bkz

2

Bu eski bir sorudur, ancak artık her birini engellemenize gerek kalmadan ifadeleri kullanarak yazabilirsiniz. İçeren blok bittiğinde bunlar ters sırada imha edileceklerdir.

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using


0

Değeri ne olursa olsun, kaynak koduStream.Close neden iki yöntem olduğunu açıklar:

// Stream used to require that all cleanup logic went into Close(),
// which was thought up before we invented IDisposable.  However, we
// need to follow the IDisposable pattern so that users can write
// sensible subclasses without needing to inspect all their base
// classes, and without worrying about version brittleness, from a
// base class switching to the Dispose pattern.  We're moving
// Stream to the Dispose(bool) pattern - that's where all subclasses
// should put their cleanup now.

Kısacası, Closeyalnızca önceden olduğu için orada Disposeve uyumluluk nedeniyle silinemez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.