Должен ли я вызвать Close () или Dispose () для потоковых объектов?

151

Такие классы, как Stream, StreamReaderи StreamWriterт. Д. Реализует IDisposableинтерфейс. Это означает, что мы можем вызывать Dispose()метод для объектов этих классов. Они также определили publicметод с именем Close(). Теперь это смущает меня, что я должен назвать, как только я закончу с объектами? Что если я позвоню обоим?

Мой текущий код такой:

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

Как видите, я написал using()конструкции, которые автоматически вызывают Dispose()метод для каждого объекта. Но я также называю Close()методы. Это правильно?

Пожалуйста, предложите мне лучшие практики при использовании потоковых объектов. :-)

MSDN пример не использует using()конструкции, а вызов Close()метода:

Это хорошо?

Наваз
источник
Если вы используете ReSharper, вы можете определить это как «антипаттерн» в каталоге паттернов. ReSharper помечает каждое использование как ошибку / подсказку / предупреждение относительно вашего определения. Также возможно определить, как ReSharper должен применять QuickFix для такого случая.
Торстен Ханс,
3
Просто подсказка: Вы можете использовать оператор using для нескольких одноразовых итентов: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = new StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (filename)) {//...Некоторый код}
Латрова
Вам не нужно вкладывать ваши операторы использования таким образом, вы можете сложить их друг над другом и иметь один набор скобок. В другом посте я предложил изменить фрагмент кода, который должен был использовать операторы с этой техникой, если вы хотите посмотреть и исправить свою «стрелку кода»: stackoverflow.com/questions/5282999/…
Тимоти Гонсалес
2
@ Suncat2000 Вы можете иметь несколько операторов using, но не вкладывать их и вместо этого составлять их. Я не имею в виду синтаксиса , как это который ограничивает тип: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Я имею в виду, как это, где вы можете переопределить тип:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Тимоти Гонсалес

Ответы:

101

Быстрый переход в Reflector.NET показывает, что Close()метод on StreamWriter:

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

И StreamReaderэто:

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

Dispose(bool disposing)Переопределение в StreamReaderIS:

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

StreamWriterМетод подобен.

Итак, читая код, становится ясно, что вы можете вызывать Close()& Dispose()в потоках так часто, как вам нравится, и в любом порядке. Это никак не изменит поведение.

Так что сводится к тому, является ли она более читаемой для использования Dispose(), Close()и / или using ( ... ) { ... }.

Мое личное предпочтение заключается в том, что using ( ... ) { ... }всегда следует использовать, когда это возможно, так как это помогает вам «не бегать с ножницами».

Но, хотя это помогает правильности, это уменьшает читабельность. В C # у нас уже есть множество закрывающих фигурных скобок, так как же мы узнаем, какая из них фактически выполняет закрытие в потоке?

Поэтому я думаю, что лучше всего сделать это:

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

    stream.Close();
}

Это не влияет на поведение кода, но способствует удобочитаемости.

Enigmativity
источник
20
« В C # у нас уже есть множество закрывающих фигурных скобок, так как же мы узнаем, какой из них на самом деле выполняет закрытие потока? » Я не думаю, что это большая проблема: поток закрывается «в нужное время», т.е. когда переменная выходит из области видимости и больше не нужна.
Хайнци
110
Хм, нет, вот почему, черт возьми, он закрывает это дважды ?? скорость удара во время чтения.
Ганс Пассант
57
Я не согласен с избыточным Close()вызовом. Если кто-то менее опытный смотрит на код и не знает о нем, usingон будет: 1) искать его и изучать , или 2) слепо добавлять Close()вручную. Если он выберет 2), возможно, какой-то другой разработчик увидит избыточность Close()и вместо того, чтобы «посмеиваться», проинструктирует менее опытного разработчика. Я не за то, чтобы усложнять жизнь неопытным разработчикам, но я за то, чтобы превратить их в опытных разработчиков.
Р. Мартиньо Фернандес
14
Если вы используете + Close () и включаете / анализируете, вы получаете «предупреждение: CA2202: Microsoft.Usage: Объект« f »может быть размещен более одного раза в методе« Foo (строка) ». Чтобы избежать генерации системы. ObjectDisposedException Вы не должны вызывать Dispose более одного раза для объекта .: Lines: 41 "Так что, хотя текущая реализация в порядке с вызовом Close и Dispose, согласно документации и / analysis, это не нормально и может измениться в будущих версиях. сеть.
marc40000
4
+1 за хороший ответ. Еще одна вещь, чтобы рассмотреть. Почему бы не добавить комментарий после закрывающей скобки, например // Закрыть или, как я, будучи новичком, я добавляю один вкладыш после любой закрывающей скобки, что не ясно. как, например, в длинном классе я бы добавил // Конечное пространство имен XXX после последней закрывающей скобки и // Конечный класс YYY после второй заключительной закрывающей скобки. Разве это не то, для чего комментарии. Просто любопытно. :) Как новичок, я видел такой код, поэтому я пришел сюда. Я действительно задавал вопрос «Зачем нужно второе закрытие». Я чувствую, что дополнительные строки кода не добавляют ясности. Сожалею.
Фрэнсис Роджерс
51

Нет, вы не должны вызывать эти методы вручную. В конце usingблока Dispose()автоматически вызывается метод, который позаботится об освобождении неуправляемых ресурсов (по крайней мере для стандартных классов .NET BCL, таких как потоки, устройства чтения / записи и т. Д.). Таким образом, вы также можете написать свой код следующим образом:

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()Метод вызывает Dispose().

Дарин димитров
источник
1
Я почти уверен, что вам не нужно быть usingпервым, responseStreamпоскольку он обернут, readerчто обеспечит его закрытие при утилизации читателя. +1 тем не менее
Исак Саво
Это сбивает с толку, когда вы сказали The Close method calls Dispose.... и в остальной части вашего поста, вы подразумеваете, что Dispose()будет звонить Close(), я не должен вызывать последний вручную. Вы говорите, что они называют друг друга?
Наваз
@ Наваз, мой пост сбил с толку. Метод Close просто вызывает Dispose. В вашем случае вам нужно утилизировать, чтобы освободить неуправляемые ресурсы. Оборачивая ваш код в оператор using, вызывается метод Dispose.
Дарин Димитров
3
Грозный ответ. Предполагается, что вы можете использовать usingблок. Я реализую класс, который пишет время от времени и, следовательно, не может.
Jez
5
@Jez Ваш класс должен затем реализовать интерфейс IDisposable и, возможно, также Close (), если close является стандартной терминологией в области , чтобы классы, использующие ваш класс, могли использовать using(или, опять же, перейти к шаблону Dispose).
Дорус
13

В документации сказано, что эти два метода эквивалентны:

StreamReader.Close : эта реализация Close вызывает метод Dispose, передавая истинное значение.

StreamWriter.Close : эта реализация метода Close вызывает метод Dispose, передавая истинное значение.

Stream.Close : этот метод вызывает Dispose, указывая true для освобождения всех ресурсов.

Итак, оба они одинаково действительны:

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

Лично я бы придерживался первого варианта, так как он содержит меньше «шума».

Heinzi
источник
5

Во многих классах, которые поддерживают оба Close()и Dispose()методы, два вызова будут эквивалентны. Однако в некоторых классах можно повторно открыть объект, который был закрыт. Некоторые такие классы могут поддерживать некоторые ресурсы после закрытия, чтобы разрешить повторное открытие; другие могут не поддерживать какие-либо ресурсы Close(), но могут установить флаг, Dispose()чтобы явно запретить повторное открытие.

Контракт IDisposable.Disposeявно требует, чтобы вызов его для объекта, который никогда не будет использоваться снова, в худшем случае был бы безвредным, поэтому я бы рекомендовал вызывать либо IDisposable.Disposeметод, либо метод, вызываемый Dispose()для каждого IDisposableобъекта, независимо от того, вызывает ли он также или нет Close().

Supercat
источник
К вашему сведению, в блогах MSDN есть статья, в которой рассказывается о том, что такое «Закрой и утилизируй». blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieSee
1

Это старый вопрос, но теперь вы можете писать с использованием операторов без необходимости блокировать каждый из них. Они будут утилизированы в обратном порядке, когда содержащий блок будет закончен.

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

Тодд Скелтон
источник