44

When I run code analysis on the following chunk of code I get this message:

Object 'stream' can be disposed more than once in method 'upload.Page_Load(object, EventArgs)'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.

using(var stream = File.Open(newFilename, FileMode.CreateNew))
using(var reader = new BinaryReader(file.InputStream))
using(var writer = new BinaryWriter(stream))
{
    var chunk = new byte[ChunkSize];
    Int32 count;
    while((count = reader.Read(chunk, 0, ChunkSize)) > 0)
    {
        writer.Write(chunk, 0, count);
    }
}

I don't understand why it might be called twice, and how to fix it to eliminate the error. Any help?

Mike Jones
  • 1,006
  • 1
  • 9
  • 14
  • 3
    As of today, VS2017 is throwing CA2202 about each and every `using` clause. Someone needs to get their act together. – ajeh Sep 21 '17 at 20:16
  • The contract of the `IDisposable` interface is that it must be safe to call `Dispose()` more than once. So my suggestion is: don't fix the warning. (See [here](https://web.archive.org/web/20151020034302/http://blogs.msdn.com/b/tilovell/archive/2014/02/12/the-worst-code-analysis-rule-that-s-recommended-ca2202.aspx) for a cautionary tale. [Here](https://github.com/MicrosoftDocs/visualstudio-docs/issues/1575) it was decided not to port the rule.) – Mike Rosoft Sep 13 '21 at 10:27

6 Answers6

24

I struggled with this problem and found the example here to be very helpful. I'll post the code for a quick view:

using (Stream stream = new FileStream("file.txt", FileMode.OpenOrCreate))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {
        // Use the writer object...
    }
}

Replace the outer using statement with a try/finally making sure to BOTH null the stream after using it in StreamWriter AND check to make sure it is not null in the finally before disposing.

Stream stream = null;
try
{
    stream = new FileStream("file.txt", FileMode.OpenOrCreate);
    using (StreamWriter writer = new StreamWriter(stream))
    {
        stream = null;
        // Use the writer object...
    }
}
finally
{
    if(stream != null)
        stream.Dispose();
}

Doing this cleared up my errors.

mxgg250
  • 705
  • 8
  • 13
14

To illustrate, let's edit your code

using(var stream = File.Open(newFilename, FileMode.CreateNew))
{
    using(var reader = new BinaryReader(file.InputStream))
    {
        using(var writer = new BinaryWriter(stream))
        {
            var chunk = new byte[ChunkSize];
            Int32 count;
            while((count = reader.Read(chunk, 0, ChunkSize)) > 0)
            {
                writer.Write(chunk, 0, count);
            }
        } // here we dispose of writer, which disposes of stream
    } // here we dispose of reader
} // here we dispose a stream, which was already disposed of by writer

To avoid this, just create the writer directly

using(var reader = new BinaryReader(file.InputStream))
    {
        using(var writer = new BinaryWriter( File.Open(newFilename, FileMode.CreateNew)))
        {
            var chunk = new byte[ChunkSize];
            Int32 count;
            while((count = reader.Read(chunk, 0, ChunkSize)) > 0)
            {
                writer.Write(chunk, 0, count);
            }
        } // here we dispose of writer, which disposes of its inner stream
    } // here we dispose of reader

edit: to take into account what Eric Lippert is saying, there could indeed be a moment when the stream is only released by the finalizer if BinaryWriter throws an exception. According to the BinaryWriter code, that could occur in three cases

  If (output Is Nothing) Then
        Throw New ArgumentNullException("output")
    End If
    If (encoding Is Nothing) Then
        Throw New ArgumentNullException("encoding")
    End If
    If Not output.CanWrite Then
        Throw New ArgumentException(Environment.GetResourceString("Argument_StreamNotWritable"))
    End If
  • if you didn't specify an output, ie if stream is null. That shouldn't be a problem since a null stream means no resources to dispose of :)
  • if you didn't specify an encoding. since we don't use the constructor form where the encoding is specified, there should be no problem here either (i didn't look into the encoding contructor too much, but an invalid codepage can throw)
    • if you don't pass a writable stream. That should be caught quite quickly during development...

Anyway, good point, hence the edit :)

samy
  • 14,832
  • 2
  • 54
  • 82
  • 2
    Now what if new BinaryWriter throws after the output stream is opened? Who closes the stream then? No one, until the finalizer runs. In practice, that doesn't happen much and in practice, even if it does happen the worst consequence is that the file stays open slightly too long. But if your logic *requires* that all resources be aggressively cleaned up no matter what crazy exceptions happen, then this code is not correct. – Eric Lippert Oct 20 '10 at 23:41
  • @Eric: yes indeed, but since there's no way in the classes to check that the filestream is already closed, we can't easily take it into account. If we had a property Closed() as boolean in the stream, we could add closing logic to the filestream to handle special cases where an exception occurs in the Binary Writer constructor – samy Oct 21 '10 at 13:52
  • More specifically, if I use the above code, and run code analysis I get the error message saying: call System.IDisposable.Dispose on object 'File.Open(string.Concat(upload.BaseDir, newId, CS$<>8__locals3.suffix), FileMode.CreateNew)' before all references to it are out of scope. – Mike Jones Oct 25 '10 at 15:26
  • Yes, there's no escaping the use of an explicit `Dispose` in a `try/catch` if you really want to cover all bases... – samy Oct 26 '10 at 07:38
  • 1
    Really, I am confused, isn't that the whole purpose of the using statement that I am told I should remove? Isn't using(e){ S } short hand for: try { e; S } finally(e.Dispose();} – Mike Jones Oct 26 '10 at 14:13
  • 1
    The `using` keyword automatically disposes at the end of the scope. But since the BinaryWriter disposes the stream it's working with directly, you've got a discrespancy in the "one dispose per scope". So there's a case (pointed out by Eric) where if you want to be as exhaustive as possible, you mustn't use a using for the stream. That's all due to the behavior of the BinaryWriter which disposes of the stream its handed, when it should just let the creator of the stream handle it. I'll try to give an example tomorrow when i have a compiler handy – samy Oct 26 '10 at 21:18
9

The BinaryReader/BinaryWriter will dispose the underlying stream for you when it disposes. You don't need to do it explicitly.

To fix it you can remove the using around the Stream itself.

Dismissile
  • 32,564
  • 38
  • 174
  • 263
7

A proper implementation of Dispose is explicitly required not to care if it's been called more than once on the same object. While multiple calls to Dispose are sometimes indicative of logic problems or code which could be better written, the only way I would improve the original posted code would be to convince Microsoft to add an option to BinaryReader and BinaryWriter instructing them not to dispose their passed-in stream (and then use that option). Otherwise, the code required to ensure the file gets closed even if the reader or writer throws in its constructor would be sufficiently ugly that simply letting the file get disposed more than once would seem cleaner.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    Well, there actually is such a constructor parameter, it is called "leaveOpen". And guess what? You'll get CA2202 even if you use it. Despite the fact that leaveOpen is more robust against double invocation of Dispose than the official sample. – Jirka Hanika Feb 11 '21 at 17:03
6

Your writer will dispose your stream, always.

Aliostad
  • 80,612
  • 21
  • 160
  • 208
1

Suppress CA2202 whenever you are sure that the object in question handles multiple Dispose calls correctly and that your control flow is impeccably readable. BCL objects generally implement Dispose correctly. Streams are famous for that.

But don't necessarily trust third party or your own streams if you don't have unit tests probing that scenario yet. An API which returns a Stream may be returning a fragile subclass.

Jirka Hanika
  • 13,301
  • 3
  • 46
  • 75