C# CA2000:在使用FileStream/XmlTextReader时,确保在失去作用域之前处理对象

8

我有很多这样的代码:

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.

如果我按照建议在using语句中放置File.Open,那么我会得到如下结果:

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,但我感觉自己做错了什么,却又找不到问题所在。 我到底哪里做错了?


FYI,“new XmlTextReader”自.NET 2.0起已被弃用。请改用XmlReader.Create,就像Hans在下面所示的那样。 - John Saunders
哎呀,我不知道 XmlTextReader 已经被弃用了。现在这个问题就有意义了。谢谢! - The Diamond Z
CA2202的原因是,释放ReaderWriter也会释放用于创建它的FileStream;这意味着如果Writer的构造失败,则必须释放fs,但如果成功则不需要;这导致了testalino下面的答案中的回答。 - PJTraill
7个回答

15

叹气,真是令人筋疲力尽。使用建议的Create()方法可以避免所有这些问题:

 using (var reader = XmlReader.Create(@"C:\Temp\SNB-RSS.xml")) {
     //...
 }

11

由于目前没有人提供解决此问题的方案,因此我在这里写下了我的可行解决方案:

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 错误。


好的,我一早上都在搜索,这是第一个真正有效的方法。谢谢!我的问题不在于StreamWriter而是XMLReader,但同样的CA Hell。 - TheZenker
实际上这是最好的答案。 请参阅http://connect.microsoft.com/VisualStudio/feedback/details/611525/disposable-objects-and-ca2000-ca2202-warnings - ken2k
testalino:您可能希望将此集成到您的答案中:在问题中使用fs导致CA2202的原因是,处理ReaderWriter也会处理用于创建它的FileStream;这意味着如果Writer的构建失败,则必须处理fs,但如果成功则不需要处理!对我来说,这似乎是这些类设计上的缺陷。我不能说我喜欢使用您的模式,但我没有看到更好的方法;编写包装类似乎是错误的。 - PJTraill

1

我只是猜测,现在没有时间进行完整的分析。

假设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对于各种原因都是一件好事)。


1

0

只需在文件流中使用 'using'

 using(FileStream fs = new FileStream(fileName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
// some codes here

}

不要修改 fs,也不要在花括号内使用 fs.close()。


0

此答案所述,唯一正确的解决方法是按照CA2202建议使用外部try-finally块而不是外部using块。在内部using中,将外部IDisposable对象设置为null,以防止在内部using完成后访问它。

这里有一个通用的包装器,可以“正确地”执行此操作,即解决了设计不良的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的模式,我宁愿用这种方式而不是每次都完整重复一遍。

-1

原问题明确表示他已经尝试过这个方法,但是它会产生不同的警告。 - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接