C# CA2000: избавьтесь от объектов, прежде чем потерять область видимости, используя FileStream/XmlTextReader
У меня есть много кода, как это:
FileStream fs = File.Open(@"C:\Temp\SNB-RSS.xml", FileMode.Open);
using (XmlTextReader reader = new XmlTextReader(fs))
{
/* Some other code */
}
Это дает мне следующее предупреждение анализа кода:
CA2000 : Microsoft.Reliability : In method 'SF_Tester.Run()', object 'fs' is not disposed along all exception paths. Call System.IDisposable.Dispose on object 'fs' before all references to it are out of scope.
Если я последую предложению и добавлю File.Open в оператор using, я получу следующее:
CA2202 : Microsoft.Usage : Object 'fs' can be disposed more than once in method 'SF_Tester.Run()'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.: Lines: 39
Я использую VS2010, и я не могу не думать, что я делаю что-то не так, но я не вижу этого. Что я делаю неправильно?
7 ответов
Вздох, утомительно, не так ли? Избегайте всего этого, используя рекомендованный метод Create():
using (var reader = XmlReader.Create(@"C:\Temp\SNB-RSS.xml")) {
//...
}
Поскольку никто еще не предоставил решение, которое решает эту проблему, я пишу свое рабочее решение здесь:
FileStream fs = new FileStream(fileName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
try
{
using (var fileWriter = new StreamWriter(fs, encoding))
{
fs = null;
fileWriter.Write(content);
}
}
finally
{
if (fs != null)
fs.Dispose();
}
Это удаляет CA2000.
Я только догадываюсь; сейчас нет времени проходить полный анализ.
Предположим, XmlTextReader
конструктор "берет на себя ответственность" за переданный поток, и таким образом XmlTextReader
будет тоже Dispose
основной поток. Это объясняет поведение, которое вы видите. возможно XmlTextReader
конструктор может выдать, и в этом случае, исходное предупреждение о fs
будет иметь смысл. Однако, учитывая эту гипотезу, этот код
var fs = File.Open(@"C:\Temp\SNB-RSS.xml", FileMode.Open);
XmlTextReader reader = null;
try
{
reader = new XmlTextReader(fs);
}
finally
{
if (reader== null)
{
fs.Dispose();
}
}
if (reader != null)
{
using (reader)
{
/* Some other code */
}
}
это, я думаю, правильно, но все же дает ложное предупреждение. Это пахнет как хороший пример, который демонстрирует ограничения инструментов статического анализа.
Как кто-то еще сказал, есть другой API для непосредственного создания ридера из имени файла (XmlReader.Create()
), который избегает всего этого (и показывает, насколько хорошо спроектированные API, ориентированные на сценарии, полезны по удивительным причинам).
Это известная проблема
Если вы используете StreamWriter, а не XmlTextReader (как в решении выше), вы можете использовать аналогичный метод через соответствующий конструктор; например
var sw = new StreamWriter("filename.txt");
или же
var sw = new StreamWriter("filename.txt", /*append to file = */ false );
Из документации неясно, будет ли первая форма конструктора перезаписывать или добавлять в файл.
Просто используйте "использование" для файлового потока
using(FileStream fs = new FileStream(fileName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
// some codes here
}
Не изменяйте fs и не используйте fs.close() внутри с помощью фигурных скобок.
Как упомянуто в этом ответе, единственный способ обойти это правильно - это сделать, как рекомендовано в CA2202, и использовать внешний блок try-finally вместо внешнего блока using. Внутри внутреннего использования, установите внешний IDisposable объект равным нулю, чтобы предотвратить доступ к нему после завершения внутреннего использования.
Вот обобщенная оболочка, которая делает это "правильно", то есть работает с плохо спроектированным XmlReader (может быть, он не должен был вступать во владение потоком, который он получает? Не уверен, каким будет правильный способ сделать это)
Отказ от ответственности: не проверено
public static TResult SafeNestedUsing<TOuter, TInner, TResult>(Func<TOuter> createOuterDisposable, Func<TOuter, TInner> createInnerDisposable, Func<TInner, TResult> body)
where TInner : IDisposable
where TOuter : class, IDisposable
{
TOuter outer = null;
try
{
outer = createOuterDisposable();
using (var inner = createInnerDisposable(outer))
{
var result = body(inner);
outer = null;
return result;
}
}
finally
{
if (null != outer)
{
outer.Dispose();
}
}
}
Пример использования:
SafeNestedUsing<MemoryStream, XmlReader, XmlDocument>(
() => new MemoryStream(array),
(memStream) => XmlReader.Create(memStream, xmlReaderSettings),
(xmlReader) =>
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
return xmlDoc;
});
Это довольно неуклюже, и вы можете утверждать, что вместо этого лучше повторить шаблон try/set null/finally. Но для повторяющегося паттерна вложенных употреблений я бы предпочел сделать это таким образом, чем повторять все время каждый раз.
Использовать using
Заявление также на самом FileStream так же, как на XmlTextReader.
http://msdn.microsoft.com/en-us/library/system.io.filestream(VS.71).aspx.
Grz, Крис.