използване на израза 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 ме „предупреждава“, че изхвърлям fs повече от веднъж. Така че въпросът ми е следният, дали правилният начин да напиша това е:

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 правилно и предупреждението може да бъде игнорирано. Въпреки че съм съгласен с точките, направени от Dan, Daniel, 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 ме „предупреждава“, че изхвърлям fs повече от веднъж.

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

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

Въз основа на това предупреждението е фалшиво и моят избор би бил да оставя кода такъв, какъвто е, и да премахна предупреждението.

person Community    schedule 18.08.2012

Тъй като отговорът на Дан изглежда работи само със StreamWriter, вярвам, че това може да е най-приемливият отговор. (Отговорът на Дан все пак ще даде предупреждение за изхвърляне два пъти със StreamReader - както Daniel Hilgarth и 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
Дан - вашият пример все още изхвърля предупреждението на визуалното студио, поради което го премахнах като отговор. Няма да изведе предупреждението със 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

В момента правя уебсайт с раздели.

http://imageshack.us/photo/my-images/16/chosentab.png/ (Няма да ми позволи да вградя изображението)

Както можете да видите, има раздел, наречен classPvP, който е избран и това става чрез добавяне на „selected“ в класа на етикета. Този уебсайт обаче ще бъде като уики и като такъв ще има много различни страници и не искам да се налага да създавам отделен „шаблон“ (ако желаете) за всяка страница с различен избран раздел, защото тогава актуализирането или промяната на нещо във всички тези шаблони би било много трудно.

Опитвам се да използвам този код, за да добавя в желания клас, когато сте избрали раздел:

<?php
//Defining the variables
$a="[[*menutitle]]";
if ($a=="classpvp"){
echo "selected";
} else {
echo $a;
} ?>

Със CMS (MODx), който използвам, [[*menutitle]] се заменя с какъвто и да е раздел, който искам да съм „избран“. По някаква причина обаче операторът if не се изпълнява, но ехото извежда, че стойността на $a е classpvp.

Това сериозно ме обърква, защо операторът if не се изпълнява, но стойността е правилна - съхранявам ли нещо лошо? (Съжалявам, аз съм много нов в PHP)

(Имайки предвид, че [[TabSelectClassPvP]] се заменя с php кода) Когато се използва var_dump($a) (както е предложено), класът се превръща от:

<li class="navTab [[TabSelectClassPvP]]">
ClassPvP
</li>

to:

<li class="navTab string(14) " classpvp"="">
ClassPvP
</li>

Точният php код, който се изпълнява е:

<?php
//Defining the variables
$a="[[*menutitle]]";
var_dump($a);
if ($a=="classpvp"){
echo "selected";
} else {
echo $a;
}

Благодаря, Muffinjello

P.S. - Отворен съм за нови начини за това, може би нещо, което извлича информация от HTML ID?

- person Daniel Hilgarth; 17.08.2012
comment
@exacerbatedexpert: Аха. Желаете ли да уточните? - person Daniel Hilgarth; 18.08.2012
comment
@exacerbatedexpert: Трябва да се научиш да четеш правилно. Написах, че изхвърля the FileStream. Този, в който беше предадено. Контекстът на този въпрос беше, че FileStream се предава в StreamReader... - person Daniel Hilgarth; 18.08.2012

Това е така, защото начинът, по който сте използвали StreamReader, изхвърля потока, когато е изхвърлен. Така че, ако изхвърлите и потока, той ще бъде изхвърлен два пъти. Някои смятат това за недостатък в StreamReader--но все пак го има. Във 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

Две решения:

А) Доверявате се на 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();
}

Връзките по-долу илюстрират напълно легален синтаксис. IMO, този синтаксис "използване" е много за предпочитане пред вложеното "използване". Но признавам - това не решава първоначалния въпрос:

ИМХО...

person paulsm4    schedule 17.08.2012
comment
@palsm4 - (редактиране - току-що разбрах, че потвърждавате това сега, но за да подчертая за бъдещите читатели) този въпрос започна във връзка с предупрежденията на Visual Studio 2012. Кодът по-горе все още ще предизвика това предупреждение. За справка копирах/поставих кода по-горе в тестова програма, за да проверя, че предупреждението все още се появява: CA2202 Не изхвърляйте обекти многократно Обектът „fs“ може да се изхвърля повече от веднъж [...]. За да избегнете генерирането на System.ObjectDisposedException, не трябва да извиквате Dispose повече от един път на обект. Това е така, защото VS2012 знае, че streamreader изхвърля файловия поток - person JHubbard80; 17.08.2012
comment
Така че предполагам, че вашето решение е try/finally: IN; използвайки: OUT ;) Правилно? - person paulsm4; 18.08.2012
comment
@paulsm4: -1: Вашите подредени употреби все още не коригират предупреждението и обиждането на другите не ви дава доверие - person Daniel Hilgarth; 18.08.2012
comment
1) подредените употреби (2 реда + 1 комплект скоби) са просто опция. IMO много по-приятна опция от глупаво, ненужно влагане (с допълнителни скоби). 2) влагането и подреждането генерират един и същ IL байт код. Всички сме съгласни с това :) 3) Доколкото не решава предупреждението (и аз го казах), изобщо не бих се занимавал с глупавото използване. Просто бих направил всичко с (единствен!) блок за опитване/изхвърляне. - person paulsm4; 18.08.2012