tags:

views:

75

answers:

3

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?

+2  A: 

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
A: 

Your writer will dispose your stream, always.

Aliostad
+5  A: 

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
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
@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
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
Yes, there's no escaping the use of an explicit `Dispose` in a `try/catch` if you really want to cover all bases...
samy
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
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