C# CA2000:Dispose objects before losing scope using FileStream/XmlTextReader

一笑奈何 提交于 2019-12-06 23:49:15

问题


I have lots of code like this:

FileStream fs = File.Open(@"C:\Temp\SNB-RSS.xml", FileMode.Open); 
using (XmlTextReader reader = new XmlTextReader(fs)) 
{ 
   /* Some other code */
}

This gives me the following Code Analysis warning:

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.

If I follow the suggestion and I put the File.Open in a using statement, I get this:

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

I'm using VS2010 and I can't help but think I'm doing something wrong but I don't see it. What am I doing wrong?


回答1:


Sigh, exhausting isn't it. Avoid all this by using the recommended Create() method:

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



回答2:


As nobody provided a solution that solves this issue yet, I'm writing my working solution down here:

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

This removes CA2000.




回答3:


I am only guessing; don't have time to go through a full analysis now.

Suppose the XmlTextReader constructor 'takes ownership' of the stream passed in, and so disposing the XmlTextReader will also Dispose the underlying stream. That would explain the behavior you see. Perhaps XmlTextReader constructor can throw, and in that instance, the original warning about fs would make sense. However, given that hypothesis, this code

        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 */
            }
        }

is, I think, correct, but still yields a spurious warning. This smells like a nice example that demonstrates the limitations of static analysis tools.

As someone else said, there is another API to directly create the reader from the filename (XmlReader.Create()), which avoids all this (and shows how well-designed scenario-focused APIs are a good thing for a surprising variety of reasons).




回答4:


It's a known issue

http://connect.microsoft.com/VisualStudio/feedback/details/535118/ca2000-and-ca2202-offer-contradictory-warnings

If you're using a StreamWriter rather than XmlTextReader (as in the solution above) you could use a similar method via the relevant constructor; e.g.

var sw = new StreamWriter("filename.txt");

or

var sw = new StreamWriter("filename.txt", /*append to file = */ false );

It is not clear from the documentation whether the first form of constructor will overwrite or append to a file.




回答5:


As mentioned in this answer, the only way to work around it correctly is to do as recommended in CA2202 and use an outer try-finally block instead of an outer using block. Inside the inner using, set the outer IDisposable object to null to prevent it from being accessed once the inner using has finished.

Here's a generic wrapper that does it "correctly", i.e. works around the badly designed XmlReader (maybe it should not have taken ownership of the stream it receives? Not sure what the right way to do it would be)

Disclaimer: Not really tested

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

Example usage:

SafeNestedUsing<MemoryStream, XmlReader, XmlDocument>(
    ()          => new MemoryStream(array),
    (memStream) => XmlReader.Create(memStream, xmlReaderSettings),
    (xmlReader) =>
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(xmlReader);
        return xmlDoc;
    });

This is quite clunky, and you may argue that it's better to repeat the try/set null/finally pattern instead. But for a repeating pattern of nested usings I'd rather do it this way than repeat the full thing each time.




回答6:


just use 'using' for the filestream

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

}

Don't modify fs and don't use fs.close() inside using curly braces.




回答7:


Use the using statement also on the FileStream itself just like on the XmlTextReader.

http://msdn.microsoft.com/en-us/library/system.io.filestream(VS.71).aspx.

Grz, Kris.



来源:https://stackoverflow.com/questions/3128446/c-sharp-ca2000dispose-objects-before-losing-scope-using-filestream-xmltextreade

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!