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

9.9k views Asked by At

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?

7

There are 7 answers

0
Hans Passant On BEST ANSWER

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

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

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.

0
CJBrew On

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.

3
testalino On

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.

1
Kris van der Mast On

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.

0
Brian On

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).

0
sinelaw On

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.