Throwing the baby out with the bathwater

| 7 Comments
Richard Hale Shaw writes on using statements in C# and his conclusion is "it's too confusing" and "I'd suggest not even using using statements". I think that position is a little harsh, but I think that the main problem is that using tries to provide support for "scoped locals" and it doesn't do it well enough.

Richard refers to this C# idiom for deterministic resource release.

using(MyObj obj = new MyObj())
{
   obj.DoStuff();
}

The point being that although the memory that MyObj uses will not be reclaimed until the garbage collector runs other resources that MyObj uses could be released at a precisely defined point via an implementation of IDisposable.

Richard points out that you are not forced to create the object in the using statement and that if you pass in an already existing object you might write code that relies on being able to use that object after the using statement ends and disposes of the object. He then suggests that because of this you should consider not using using statements...

Given that he delves into the IL that results from using a using statement and shows how it's simply syntactic sugar for the corresponding try/finally construct. I assume that his "don't do it" recommendation equally applies to this:

MyObj obj = new MyObj()
try
{
   obj.DoStuff();
}
finally
{
   if (obj != null) obj.Dispose();
}

Which, to me, looks like a suggestion that using IDisposable at all is a bad idea... He may have a point. In the nice and fluffy .Net world where you don't have to worry about resource management ;) because the garbage collector does it for you the use of IDisposable brings back all the resource management issues that .Net tried so hard to avoid. I think they just got their syntactic sugar wrong.

Lets rewind to this time five years ago when thoughts of deterministic destruction in .Net were being raised. This posting, "Resource management" from Brian Harry on the DOTNET@DISCUSS.DEVELOP.COM list seems to be the story on the .Net resource management design, and the conception of IDisposable and using. (Having just read back through Brian's stuff my immediate thought was "this guy should blog!" he does, here, but not often. Pity!). I think, as a solution for the "scoped locals" problem, using is only halfway there... Given that the using was designed to be used to implement scoped locals and given that it's just syntactic sugar over the usual IL that is generated if you do the work that's required long hand, I think they should have gone further and actually added the concept of a "scoped local" as a first class concept in .Net.

For those of you not familiar with the C++ RAII idiom, scoped locals are used a lot for resource management in C++. The basic idea is that you create an object that manages a resource and it acquires the resource when you create the object and it releases the resource when you destroy the object. You then create one of these objects on the stack at the scope where you wish to use the resource and it takes care of getting the object for you and releasing it when you done. The advantage that RAII has over managing the resource lifetime yourself is that when the object goes out of scope it cleans up. This means that you can have multiple returns from a function, including "returning" via an exception, and the resource will always be cleaned up correctly at exactly the right time. You end up with code like this:

{
   if (/* something */)
   {
      CDatabaseConnection connection(/* stuff */);
      
      /* do stuff with the connection */
  
   }  // Database connection is released when execution passes here...
}

You'll find that in "good" C++ code you'll hardly ever see bare resources being used, they'll always be wrapped in objects that tend to live on the stack and allow scope rules to determine when they're released.

Of course in .Net you can't have objects on the "stack". Everything is "heap" allocated using new and then you just forget about it and let the garbage collector do its job. I think the solution for using would have been to allow the appearance of stack based objects, the code would look something like this.

{
   {
      MyObj obj;

      obj.DoStuff();
   } // end of scope, object 'obj' disposed.
}

This could result in pretty much the same IL as the using statement above. Yet it can't be misused. Note that I'm not suggesting any changes to how memory is allocated, it's just a different form of syntactic sugar than the one they ended up with. I'd almost be tempted to say that these scoped objects should have a "destructor" and that they shouldn't be allowed to be created via new, but then I like restrictions that prevent programmers misusing features ;).

Given explicit support for scoped local objects in .Net, the confusion that Richard finds with using could go away. I guess it's always easy to come up with "better" designs, especially with 5 years worth of hindsight, but I don't see why there would be any problem with this kind of functionality, what are the downsides?

7 Comments

> Of course in .Net you can't have objects on the
> "stack". Everything is "heap" allocated using new ..

No. You can have stack based "objects". They are called value types.

But they cannot have finalizers/destructors (§18.3.9 in ECMA-334)

Fair enough. But you can't allocate reference types on the stack and that's what you need to be able to do to use RAII with scoped locals.

Hi Len,

The C++/CLI language coming out with VS2005 will support faux-stack instances, where disposal is tied to scope, just as your last sample.

Where MC++ mapped the C++ destructor to the CLR finalizer, C++/CLI does the opposite -- the destructor compiles into an implementation of IDisposable::Dispose, and the finalizer gets a whole new syntax (!ClassName()).

I like that better, and especially better than the confused semantics of C#. No wonder people (including RHS, it seems) have problems understanding the ramifications of the disposable pattern.

- Kim

I like the look of C++/CLI a lot. It's definitely something I'll be spending some time with soon.

I guess my C# example is slightly more complex to implement than the using version as the scope is implicit rather than explicitly defined by the using, still, compilers know these kinds of things so I don't think it would be that hard to implement.

It just goes to show that effective resource management is harder than it seems and that a garbage collector doesn't magically solve all the problems; still, it's clear from Brian Harry's posting that they were all well aware of this at the time.

Back when I was pointing out the problems with Richard Hale Shaw's comparison of .Net and C++ memory management I came to the conclusion that: So, it seems to me, that Richard's .Net object reference advantages boil down to one thing, you can't use an unassigned, "dangling", object reference; such an attempt is a compiler error whereas in C++ it would merely be a warning...

It actually seems that once you introduce IDisposable into the mix that advantage goes away too. Any object reference that you encounter could have been disposed already and if it has any access to it should result in an exception... Hmm. How is the .Net method of resource management any easier?

What I would like to see is a complete disassociation between scoping any memory management. It would be nice if C# and VB had a "scoped" keyword that could be used to mark a variable as scoped at declaration. A scoped variable would always call its "LeavingScope" method when it goes out of scope. Only classes that implement the IScopable interface could be used this way.

C#
class MyFoo : IScopable {
void LeavingScope() {
// Do something not necessarilly related
// to memory management
}
}

void bar() {
if (...) {
scoped MyFoo foo;
// ...
}
}

VB
Class MyFoo
Implements Scopable
Sub LeavingScope Implements _ Scopable.LeavingScope
' Do something not necessarilly related
' to memory management
End Sub
End Class

Sub Bar
If ... Then
Scoped foo As MyFoo
'...
End If
End Sub

To me this fits in more naturally with what you already expect, and it isn't unnecessarilly tied in with memory management concepts. Of course IDisposable is already basically the same as IScopable, so all that's really missing is the "scoped" keyword.

RAII is usefull for more than just memory (or even resource) management, and Scoped syntax is a lot more understandable than using(){} IMHO.

Justin

I like your idea of "scoped" and the suggested disconnection between memory management and scope lifetime.

Leave a comment