Чи потрібно дзвонити 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
Порада. Ви можете використовувати такий оператор, як такий, для декількох одноразових ітонів: використання (Stream responseStream = response.GetResponseStream ()), використовуючи (StreamReader Reader = новий StreamReader (responseStream)), використовуючи (StreamWriter Writer = новий StreamWriter (ім'я файлу)) {//...Чистий код}
Latrova


Вам не потрібно вкладати такі висловлювання, як ви можете складати їх один на одного і мати один набір дужок. В іншому дописі я запропонував редагувати фрагмент коду, який повинен був використовувати оператори з цією технікою, якщо ви хочете подивитися та виправити "стрілку коду": stackoverflow.com/questions/5282999/…
Тимофій Гонсалес

2
@ Suncat2000 У вас може бути декілька за допомогою операторів, але не вкладати їх, а замість цього складати їх. Я не маю в виду синтаксису , як це який обмежує тип: 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()метод на 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();
}

Це не впливає на поведінку коду, але допомагає читати.


20
" У C # у нас вже є безліч закритих фігурних брекетів. Тож як ми можемо знати, хто з них виконує закриття на потоці? Я не думаю, що це велика проблема: потік закритий" в потрібний час ", тобто коли змінна виходить із сфери застосування та більше не потрібна.
Хайнзі

110
Гм, ні, це "чому чорт його закриває двічі ??" швидкість навантаження під час читання.
Ганс Пасант

57
Я не згоден із надлишком Close()дзвінка. Якщо хтось менш досвідчений дивиться на код і не знає про usingнього, він: 1) шукає його і вчиться , або 2) сліпо додає Close()вручну. Якщо він вибирає 2), може бути який -небудь інший розробник буде бачити зайвим Close()і замість «посміхаючись», інструктує менш досвідченого розробника. Я не за те, щоб ускладнити життя недосвідченим розробникам, але я за те, щоб перетворити їх на досвідчених розробників.
Р. Мартіньо Фернандес

14
Якщо ви використовуєте + Close () і вмикаєте / аналізуєте, ви отримуєте "попередження: CA2202: Microsoft. Використання: Об'єкт 'f" можна розміщувати не один раз у методі "Foo (string)". Щоб уникнути генерації системи. ObjectDisposedException ви не повинні викликати Dispose більше одного разу на об’єкт.: Рядки: 41 "Отже, поки поточна реалізація чудова з викликом Close and Dispose, згідно документації та / аналізу, це не нормально і може змінитись у майбутніх версіях. сітка
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(), я не повинен дзвонити останній вручну. Ти кажеш, вони дзвонять один одному?
Наваз

@Nawaz, моя публікація була заплутаною. Метод закриття просто викликає розпорядження. У вашому випадку вам потрібен Dispose, щоб звільнити некеровані ресурси. Обгортаючи код за допомогою оператора, викликається метод Dispose.
Дарин Димитров

3
Страшна відповідь. Це передбачає, що ви можете використовувати usingблок. Я реалізую клас, який час від часу пише і тому не може.
Jez

5
@Jez Ваш клас повинен реалізувати інтерфейс IDisposable, і, можливо, також Close (), якщо близько є стандартною термінологією в даній області , щоб класи, що використовують ваш клас, могли використовувати using(або, знову ж таки, перейти до шаблону розпорядження).
Дорус

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

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


5

У багатьох класах, які підтримують Close()і Dispose()методи, і методи, два виклики були б рівнозначними. Однак у деяких класах можливо повторно відкрити закритий об'єкт. Деякі такі класи можуть зберігати деякі ресурси живими після закриття, щоб дозволити повторне відкриття; інші можуть не містити жодних ресурсів живими Close(), але можуть встановити прапор, Dispose()щоб явно заборонити повторне відкриття.

Договір IDisposable.Disposeявно вимагає, щоб викликати його на об'єкт, який більше ніколи не буде використаний, буде в гіршому випадку нешкідливим, тому я рекомендував би викликати або один, IDisposable.Disposeабо метод, що викликається Dispose()на кожному IDisposableоб'єкті, незалежно від того, дзвонить чи ні Close().


FYI ось стаття про блоги 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.