1

Is the compiler able to merge any of the following code into a single try/catch?

using (FileStream stream = new FileStream(filePath, FileMode.Open))
{
    using (BinaryReader reader = new BinaryReader(stream))
    {
        a try/catch around my actual code...
    }
}

If not, would there be any functional difference if I do this instead?

FileStream stream = null;
BinaryReader reader = null;
try
{
    stream = new FileStream(filePath, FileMode.Open);
    reader = new BinaryReader(stream)
    // do my stuff
}
catch (stuff)
{
}
finally
{
    if (stream != null)
        stream.Close();
    if (reader != null)
        reader.Close();
}
SilentSin
  • 1,026
  • 9
  • 18

2 Answers2

2

Since the overhead of having an extra try/finally is virtually zero, there is no difference if the compiler combines two blocks in one or keeps them separate. Your translated code would have a single try-catch embedded inside two layers of try-finally, which are virtually free in terms of performance.

Converting the using code manually has a different implication - the visibility of variables is different. Your translation of nested using statements leaves stream and reader variables accessible after the block. They refer to a closed stream and a closed reader. Regular using, on the other hand, keeps its variables inside its scope, as if an additional pair of curly braces were placed around the whole block:

{   // <<==
    FileStream stream = null;
    BinaryReader reader = null;
    try {
        stream = new FileStream(filePath, FileMode.Open);
        reader = new BinaryReader(stream)
        // do my stuff
    } finally {
        if (stream != null)
            stream.Close();
        if (reader != null)
            reader.Close();
    }
}   // <<==

using is better in this way, because it lets you avoid an extra level of nesting while keeping its variable in local scope. Moreover, you could reduce the level of nesting in your source by placing two consecutive using blocks together at the same level of indentation, with no curly braces:

using (FileStream stream = new FileStream(filePath, FileMode.Open))
using (BinaryReader reader = new BinaryReader(stream)) {
    a try/catch around my actual code...
}
Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks, I hadn't considered the scope like that. Since closing the reader also closes the stream, what if I were to do something like using (BinaryReader reader = new BinaryReader(new FileStream(...)))? – SilentSin May 15 '16 at 09:16
  • 1
    @SilentSin This would be even better, assuming that you do not need separate access to `FileStream` outside of `BinaryRead`. Usually you don't need it, but in rare occasions you may want to examine it. You still have access to `BaseStream`, but its type is `Stream`, not `FileStream`, so you would end up with a cast (i.e. `((FileStream)reader.BaseStream).Position` instead of `stream.Position` in case of nested `using` blocks). – Sergey Kalinichenko May 15 '16 at 09:21
  • 1
    @SilentSin If you do that, if the stream is constructed, but the construction of the `BinaryReader` throws an exception, your stream won't get disposed until the garbage collector sees that there are no more references to it. Verifying that the construction of `BinaryReader` won't cause an exception is easy in some cases, complicated in others. –  May 15 '16 at 09:24
  • @hvd so that means if the FileStream is blocking any other process/thread/whatever from accessing the file, it would keep doing so for an indeterminate amount of time before the GC comes along? – SilentSin May 15 '16 at 09:31
  • 1
    @SilentSin Correct - assuming that `BinaryReader` constructor throws, `FileStream` would remain unclosed until GC comes along to deal with it. – Sergey Kalinichenko May 15 '16 at 09:34
  • @ dasblinkenlight In that case, I think I'll go with the two un-nested usings, just to be on the safe side. Thanks to both of you. – SilentSin May 15 '16 at 09:36
2

Is the compiler able to merge any of the following code into a single try/catch?

No, the compiler is not able to make your code behave differently from what you've written. And it would be different even without the possibility of Close() and Dispose() having different effects, but let's pretend you called Dispose() yourself.

If not, would there be any functional difference if I do this instead?

Yes. Putting both Dispose() invocations in a single finally block means that reader won't be disposed if stream.Dispose() throws an exception.

No well-written IDisposable's Dispose() method throws an exception, but the compiler cannot know or assume that all implementations are well-written.