using оператор FileStream и / или StreamReader - предупреждения Visual Studio 2012

Новая Visual Studio 2012 жалуется на обычную комбинацию кода, которую я всегда использовал. Я знаю, что это кажется излишним, но я сделал следующее в своем коде «на всякий случай».

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    using (var sr = new StreamReader(fs))
    {
        // Code here
    }
}

Visual studio «предупреждает» меня, что я избавляюсь от файловой системы более одного раза. Итак, мой вопрос в том, будет ли правильный способ написать это:

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    var sr = new StreamReader(fs);
    // do stuff here
}

Или я должен сделать это так (или какой-то другой вариант, не упомянутый).

var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

using (var sr = new StreamReader(fs))
{
    // Code here
}

Я искал несколько вопросов в StackOverflow, но не нашел ничего, что напрямую касалось бы наилучшей практики для этой комбинации.

Спасибо!


person JHubbard80    schedule 17.08.2012    source источник
comment
+1: Хороший вопрос, JUbbard80! :)   -  person paulsm4    schedule 17.08.2012
comment
При двойном использовании, подобном этому в VS2013, FileStream и StreamReader, предупреждения не выдается. Это из-за комбинации FileStream и StreamReader? Или VS2013 (в отличие от VS2012) признает, что это конкретное двойное удаление не является проблемой?   -  person sidbushes    schedule 10.09.2016


Ответы (7)


Ниже показано, как Microsoft рекомендует это делать. Он длинный и громоздкий, но безопасный:

FileStream fs = null;
try
{
    fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using (TextReader tr= new StreamReader(fs))
    {
        fs = null;
        // Code here
    }
}
finally
{
    if (fs != null)
        fs.Dispose();
}

Этот метод всегда гарантирует, что все будет удалено, несмотря на то, какие исключения могут быть выброшены. Например, если конструктор StreamReader выдает исключение, FileStream все равно будет правильно удален.

person Dan    schedule 17.08.2012
comment
О, это удобно. Я не понимал, что пример, использованный в CA2202 (ссылка Microsoft Recommends), был аналогичным примером кода в моем вопросе. - person JHubbard80; 17.08.2012
comment
Хорошо. Вы, вероятно, тоже не добавили бы эту дополнительную ошибку синтаксиса запятой, которую я только что отредактировал;). - person Dan; 17.08.2012
comment
Я добавил немного больше объяснений, хотя я думаю, что из примера и ссылки было довольно ясно, почему. Если вы считаете, что ответ содержит дезинформацию, уместно проголосовать против него. В противном случае просто проголосуйте за предпочтительный ответ. Этот ответ вряд ли может быть дезинформацией, если он почти дословно рекомендует Microsoft. - person Dan; 17.08.2012
comment
При дальнейшем тестировании. Этот ответ не сбрасывает предупреждение VS2012 с StreamReader. Только StreamWriter. - person JHubbard80; 17.08.2012
comment
Поскольку консенсуса нет, я буду придерживаться практики, рекомендованной Microsoft. Хотя считаю это перебором. Это решение по-прежнему вызывает предупреждение, но, как указывает hvd, это предупреждение возникает, когда IDisposable не реализован правильно. Рассматриваемые объекты (FileStream, StreamReader) правильно реализуют IDisposable, и предупреждение можно игнорировать. Хотя я согласен с замечаниями Дэна, Даниэля, hvd и paulsm4. Мой ответ не генерирует предупреждение, но он рискует получить нераспределенный FileStream, если конструктор streamreader выдает исключение. Спасибо за вашу помощь. - person JHubbard80; 19.08.2012
comment
Это нелепый узор. Многократное удаление объектов, как правило, безопасно (я не знаю ни одного случая, когда бы этого не произошло). Этот код ужасного качества, и Microsoft не должен его рекомендовать. Кроме того, это небезопасно в случае асинхронного исключения непосредственно перед fs = null ;. - person usr; 19.08.2012
comment
@usr, пожалуйста, опубликуйте свой ответ. - person Pedro77; 24.02.2017
comment
@ Pedro77 нет необходимости, существует несколько ответов. Просто пытаюсь предупредить людей, потому что многие были бы соблазнены принятым статусом и большим количеством голосов (на что не глупо полагаться как новичок, если у вас нет другого способа решить). Не используйте этот код. - person usr; 24.02.2017

Visual studio «предупреждает» меня, что я избавляюсь от файловой системы более одного раза.

Да, но это нормально. В документации для IDisposable.Dispose говорится:

Если метод Dispose объекта вызывается более одного раза, объект должен игнорировать все вызовы после первого. Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз.

Исходя из этого, предупреждение является фиктивным, и я бы предпочел оставить код как есть и подавить предупреждение.

person Community    schedule 18.08.2012

Поскольку ответ Дэна работает только со StreamWriter, я считаю, что это может быть наиболее приемлемым ответом. (Ответ Дэна по-прежнему будет содержать дважды удаленное предупреждение с StreamReader - как упоминает Дэниел Хилгарт и exacerbatedexpert, StreamReader удаляет файловый поток)

using (TextReader tr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
    string line;
    while ((line = tr.ReadLine()) != null)
    {
        // Do work here
    }
}

Это очень похоже на ответ Дэниела Хилгарта, измененный для вызова dispose с помощью оператора Using в StreamReader, поскольку теперь ясно, что StreamReader вызовет dispose в FileStream (согласно всем другим сообщениям, документация упоминается)

Обновлять:

Я нашел этот пост. За что стоит. Закрывает ли поток при удалении streamreader?

person JHubbard80    schedule 17.08.2012
comment
Однако не уверен в обработке исключений. Я бы подумал, что в конструкторе FileStream произойдут какие-либо исключения, и поэтому они не дойдут до инициализации StreamReader. Поэтому в этом случае мне не нужно беспокоиться о нераспределенном FileStream из-за исключения в StreamReader. Конечно, я мог ошибаться ... - person JHubbard80; 17.08.2012
comment
Оператор using (f(x)) { } действительно компилируется как y = f(x); using(y) { }. Таким образом, любые исключения, созданные инициализатором оператора using, не вызовут неявный оператор finally. Лучший способ обойти это - создать конструкторы, которые будут безопасными для исключений и очищать ресурсы после выброса, что, как я полагаю, делают классы Stream. - person Steve Guidi; 17.08.2012
comment
Как я уже сказал, я думаю, что мой пример излишен, а в случае класса StreamReader я думаю, что приведенное выше совершенно нормально, но если вы использовали другой класс, отличный от StreamReader, или они изменят реализацию в будущем так что возможно, что конструктор вызовет исключение после создания внутренней зависимости IDisposable, тогда внутренняя IDisposable не будет удалена. Я все еще голосую, что опубликованный мною пример самый безопасный. - person Dan; 17.08.2012
comment
Дэн - ваш пример все еще выдает предупреждение Visual Studio, поэтому я удалил его в качестве ответа. Он не выдаст предупреждение с StreamWriter. Просто StreamReader как streamreader избавляется от объекта внутри, и VS2012 это знает. - person JHubbard80; 17.08.2012
comment
@ JHubbard80: код, который вы сейчас используете, должен выдать другое предупреждение, например, CA2000 < / а>. - person Daniel Hilgarth; 18.08.2012
comment
@DanielHilgarth Согласно документации, вы правы. Должно. Однако я только что это проверил. Используя мой код и код из документа CA2000 (в котором есть обратная косая черта без экранирования, хех). Не поднимает CA2000. Странный. Я попытаюсь изучить это подробнее, прежде чем отмечать ответ как ответ. - person JHubbard80; 19.08.2012
comment
@ JHubbard80 Могу я спросить, каков результат вашего расследования? - person Coops; 13.11.2013

Да, правильным способом было бы использовать вашу первую альтернативу:

using (FileStream fs = new FileStream(filePath, FileMode.Open,
                                      FileAccess.Read, FileShare.ReadWrite)) 
{ 
    TextReader tr = new StreamReader(fs); 
    // do stuff here 
} 

Причина в следующем:
Удаление StreamReader удаляет только FileStream, так что на самом деле это единственное, что вам нужно удалить.

Ваш второй вариант (просто внутреннее «использование») не является решением, так как он оставит FileStream нерасположенным, если внутри конструктора StreamReader возникнет исключение.

person Daniel Hilgarth    schedule 17.08.2012
comment
Я бы подумал, что важно избавиться от объекта StreamReader на тот случай, если у этого объекта есть элементы, которые также необходимо удалить. Может быть ясно известно (через отражение, другое), что StreamReader удаляет только FileStream, но как повседневная практика аналогичных ситуаций я не могу этого предсказать. Когда читателю передается строковый аргумент, я предполагаю, что он создает и удаляет файловый поток, который создает внутри себя. При передаче файлового потока я предполагаю, что он не удаляет объект, полученный в качестве аргумента, поскольку он не знает, будет ли поток продолжать использоваться извне. - person JHubbard80; 17.08.2012
comment
@ JHubbard80: Ваше предположение неверно. StreamReader действительно удаляет поток, который он был передан, что в первую очередь вызывает ваше предупреждение. Однако вы правы, использование моего кода может быть проблематичным, если (1) класс когда-либо изменит свою реализацию и (2) вы переключаете свою базу кода на эту новую версию. Если вы видите в этом проблему, воспользуйтесь альтернативой, предложенной Дэном. Я бы никогда не стал использовать такой громоздкий код в таком простом сценарии, как этот, потому что это не дает никакой пользы. - person Daniel Hilgarth; 17.08.2012
comment
Справедливо. Однако кажется странным, что он берет на себя ответственность за удаление объекта, созданного и переданного извне. Я знаю, что это очень чертовски маловероятно, что я бы использовал его извне в этом конкретном контексте, но я полагаю, что если бы я создал его вне, то я должен был бы нести ответственность за выбор, когда его следует утилизировать. В очередной раз благодарим за помощь. Я бы сказал, что оба ваших ответа по-своему верны. Принятие ответа будет подбрасыванием монеты. - person JHubbard80; 17.08.2012
comment
@ JHubbard80: Согласен, я бы тоже реализовал StreamReader иначе. Это странное поведение действительно укусило меня в прошлом, поскольку я получал ObjectDisposedExceptions при доступе к потоку, который я еще не удалил, после удаления StreamReader, который его использовал ... - person Daniel Hilgarth; 17.08.2012
comment
@exacerbatedexpert: Ага. Хотите уточнить? - person Daniel Hilgarth; 18.08.2012
comment
@exacerbatedexpert: нужно научиться правильно читать. Я написал, что он утилизирует файл FileStream. Тот, в котором он был передан. Контекстом этого вопроса был FileStream, передаваемый в StreamReader ... - person Daniel Hilgarth; 18.08.2012

Это потому, что способ, которым вы использовали StreamReader, удаляет поток при его удалении. Итак, если вы также удаляете поток, он удаляется дважды. Некоторые считают это недостатком _2 _ - но, тем не менее, он там есть. В VS 2012 (.NET 4.5) есть опция в StreamReader, чтобы не удалять поток с новым конструктором: http://msdn.microsoft.com/en-us/library/gg712952

person exacerbatedexpert    schedule 17.08.2012
comment
Я также рекомендую использовать расширенный конструктор, чтобы избежать этой огромной проблемы. Существует так много вещей, которые могут пойти не так, как надо, между моментом, когда поток назначается читателю, и моментом, когда он знает, что он должен избавиться от переданного в потоке, что может помешать удалению потока вообще. В этом случае Microsoft допустила ошибку, позволив StreamReader выйти за рамки своей ответственности. Вероятно, это проистекает из менталитета всегда удалять IDisposable ресурсы, но по умолчанию в этом случае должно быть наоборот, а расширенный вариант должен заключаться в удалении по запросу. - person Jeremy; 20.12.2017
comment
Где это становится более очевидным, так это объект MemoryStream. Скорее всего, вы НЕ хотите выбрасывать его даже после прочтения. В основном используется в модульном тестировании, но это действительно так. - person Jeremy; 20.12.2017

Два решения:

A) Вы доверяете Reflector или Documentation и знаете, что *Reader и *Writer закроют лежащий в основе *Stream. Но предупреждение: это не сработает в случае сгенерированного исключения. Так что это не рекомендуемый способ:

using (TextReader tr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
    // Code here
}

B) Вы игнорируете предупреждение, поскольку в документации указано The object must not throw an exception if its Dispose method is called multiple times. Это рекомендуемый способ, так как это хорошая практика - всегда использовать using, и безопасно в случае сгенерированного исключения:

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
internal void myMethod()
{
    [...]
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (TextReader tr = new StreamReader(fs))
    {
        // Code here
    }
}
person Cœur    schedule 18.04.2014

Учитывая всю чушь, которую породил этот (совершенно законный!) Вопрос, я бы предпочел следующее:

FileStream fs = null;
TextReader tr= null;
try
{
    fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    tr= new StreamReader(fs);
    // Code here
}
finally
{
    if (tr != null)
        tr.Dispose();
    if (fs != null)
        fs.Dispose();
}

Ссылки ниже иллюстрируют совершенно допустимый синтаксис. ИМО, этот синтаксис «использования» намного предпочтительнее вложенного «использования». Но я признаю - это не решает исходный вопрос:

ПО МОЕМУ МНЕНИЮ...

person paulsm4    schedule 17.08.2012
comment
@ palsm4 - (изменить - я только что понял, что вы признаете это сейчас, но чтобы подчеркнуть это для будущих читателей), этот вопрос начался относительно предупреждений Visual Studio 2012. Приведенный выше код все равно вызовет это предупреждение. Для справки я скопировал / вставил приведенный выше код в тестовую программу, чтобы убедиться, что предупреждение все еще появляется: CA2202 Не удаляйте объекты несколько раз Объект 'fs' можно удалять более одного раза [...]. Чтобы избежать генерации исключения System.ObjectDisposedException, не следует вызывать Dispose более одного раза для объекта. Это потому, что VS2012 знает, что streamreader удаляет файловый поток. - person JHubbard80; 17.08.2012
comment
Итак, я предполагаю, что ваше решение - попробовать / наконец: IN; using: OUT;) Верно? - person paulsm4; 18.08.2012
comment
@ paulsm4: -1: Ваше сложное использование все еще не устраняет предупреждение, и оскорбление других не дает вам доверия - person Daniel Hilgarth; 18.08.2012
comment
1) использование с накоплением (2 строки + 1 набор скобок) - это просто вариант. ИМО гораздо более приятный вариант, чем тупое ненужное вложение (с лишними скобками). 2) и вложение, и в стек генерируют один и тот же байт-код IL. Мы все согласны с этим :) 3) В той мере, в какой это не решает проблему (и я уже говорил об этом), я бы вообще не стал беспокоиться о глупом использовании. Я бы просто сделал все с помощью (одного!) Блока try / dispose. - person paulsm4; 18.08.2012