views:

434

answers:

6

C# has the using statement, specifically for IDisposable objects. Presumably, any object specified in the using statement will hold some sort of resource that should be freed deterministically.

However, it seems to me that there are many designs in programming which have a single, definite beginning and end, but lack intrinsic language support. The using construct provides an opportunity to use the built in features of a code editor to, at least, clearly and naturally highlight the scope of such a design or operation.

What I have in mind is the sort of operation that frequently starts with a BeginXXX() and EndXXX() method, though there are plenty of different flavors, such as an asynchronous code execution that involves a "start" and a "join".

Take this naive example.

webDataOperation.Start();
GetContentFromHardDrive();
webDataOperation.Join();
// Perform operation that requires data from both sources

What if, instead, the Start method returned an object whose IDisposable.Dispose method performs the join operation.

using(webDataOperation.Start()) {
    GetContentFromHardDrive();
}
// Perform operation that requires data from both sources

Or, better yet, what I specifically had in mind: I have an object that does highly specialized graphics blitting and has a Begin() and End() method (a design also present in DirectX and XNA). Instead...

using(blitter.BlitOperation()) {
    // Do work
}
// Use result

It seems to be more natural and readable, but is it inadvisable, seeing as it uses the IDisposable interface and the using statement for unintended purposes? In other words, would this be on par with o*verloading an operator in a non-intuitive way*?

+1  A: 

I think you should use IDisposable for what it's intended for, and nothing else. That is, if maintainability matters to you.

John Saunders
John: See the Framework Design Guidelines on Factored Types: http://blogs.msdn.com/brada/archive/2009/02/23/framework-design-guidelines-factored-types.aspx -- This is now a recommended approach to handling them.
Reed Copsey
How does this impact maintainability? And is this the only reason that you recommend strict adherence to canon?
Snarfblam
It affects the ability of other developers to understand what's going on. My reason for the recommendation includes the fact that it's hard enough to get people to use IDisposable for the normal reasons - we don't need extra ones.
John Saunders
I do agree - I do not (and would not suggest) using it everywhere. That being said, I think there are good use cases for it, especially when you have to call the closing argument or there are side-effects. I believe that's why it's now explicitly mentioned in the design guidelines, and used frequently in the BCL in this manner. IMO, once it's used in multiple locations in the BCL and included in the official design guidelines, it becomes a "normal reason" to use it.
Reed Copsey
+11  A: 

This is a perfectly acceptable practice. These are called Factored Types, and the Framework Design Guidelines recommends doing just this.

Basically, if the type wraps an operation with a specific lifetime, using IDisposable and the using statement becomes an appropriate thing to consider.

I actually blogged about this specific topic here, as well.

Reed Copsey
Good find, but do you have any reasoning for your argument? The article recommends it, but doesn't discuss why.
Snarfblam
It's because it guarantees that that the EndOperation() is always called (via Dispose())
Matthew Scharley
@Snarfblam: Basically, the operation itself acts as a resource which needs cleanup. Read my blog post for more details - it's the second link. I put much more justification in it than I would here.
Reed Copsey
also note that "using" is NOT "specifically for IDisposable objects", see my article on aliased generics http://ebersys.blogspot.com/2006/08/hiding-generics-complexity.html
BlackTigerX
This discussion is about the using statement, not the using directive (i.e. importing namespaces and defining aliases is irrelevant to the topic).
Snarfblam
A: 

I'd say it's acceptable - in fact, I've used it in some projects where I wanted to have an action triggered at the end of a specific code block.

Wes Deyer used it in his LINQ to ASCII Art program, he called it action disposable (Wes works on the C# compiler team - I'd trust his judgment :D):

http://blogs.msdn.com/wesdyer/archive/2007/02/23/linq-to-ascii-art.aspx

class ActionDisposable: IDisposable
{
    Action action;

    public ActionDisposable(Action action)
    {
        this.action = action;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.action();
    }

    #endregion
}

Now you can return that from a function, and do something like this:

using(ExtendedConsoleWriter.Indent())
{
     ExtendedConsoleWriter.Write("This is more indented");
}

ExtendedConsoleWriter.Write("This is less indented");
Charles
Wow, that's a blatant abuse of the using statement. I would have no idea what that code was trying to do without opening up the ExtendedConsoleWriter's code. If there were calls to IncreaseIndent and DecreaseIndent around the code, there'd be no question.
Ryan Emerle
I would not use "using" for this, but, suppose that there were a similar keyword, and associated interface, such as "with": with(writer.Indent()){ stuff; }, it would be a convinent construct.
Snarfblam
Yikes, Ryan. It was a quickly written example. I agree, I'd call it IncreaseIndent - in which case I would not call it "a blatant abuse of the using statement". This sort of concept can be seen in scoping transactions throughout .NET - it seems like you're coming down a little harshly there on the minutae.
Charles
Wes no longer works on the C# team, unfortunately for me.
Eric Lippert
+3  A: 

It's a common pattern, but personally, I believe that there's no excuse to abuse IDisposable like that when you can achieve the same effect in a much more obvious way with anonymous delegates and/or lambdas; i.e.:

blitter.BlitOperation(delegate
{
   // your code
});
Pavel Minaev
And, if you wanted to, say, always do the work in another thread, you don't need to tear apart your code (i.e., this design adds value)
Ryan Emerle
Although I do like this pattern, as well, this adds its own complexity - in some cases, it may become less obvious than handling via a using statement. Many (especially entry level) devs have problems grokking lambdas.
Reed Copsey
This doesn't allow for equivelent safety, in some cases, as well. For example, you cannot use this inside of a method with the yield statement and get guaranteed cleanup...
Reed Copsey
Reed, I'm not sure what you mean. If the body of BlitOperation does cleanup after invoking the delegate, why would it matter if the caller is an iterator method?
Pavel Minaev
+4  A: 

Just because you can (or because Phil Haack says it's okay), doesn't mean you should.

The basic rule of thumb: if I can read your code and understand what it's doing and what your intent was, then it's acceptable. If, on the other hand, you need to explain what you did, or why you did it, it's probably going to trip up junior developers maintaining the code.

There are many other patterns that can accomplish this with better encapsulation.

The bottom line: this "technique" buys you nothing and only acts to confuse other developers.

Ryan Emerle
+1 for the commend about understanding. I completely agree - although, in general, I think using IDisposable for factored types provides a very clean, understandable means of handling them in many cases... but it's a matter of writing your code correctly, as well as being very obvious about what you're doing. ie: TransactionScope in the BCL is natural to me - the indent code in another answer here is not. Like many things, I think this is helpful, but can easily be abused.
Reed Copsey
Agreed. What works, works. It's easy to get carried away with interesting concepts, but nothing that only serves to confuse should be used in a real world project.
Charles
If it seemed confusing to me, I wouldn't have asked. Ignoring potential confusion, do you have an alternative that would be neater or simpler? I like how "using" is inline, indented, and language supported, and explicitly, concretely and plainly encapsulates scope.
Snarfblam
@Snarfblam: In some cases, using a method that accepts a delegate (some Action/Func of some form), and calling that in-between your startup/teardown code works well. There are some cases that are unhandled by that approach, though. See Pavel Minaev's answer for details.
Reed Copsey
+4  A: 

I recommend against it; my belief is that code is to effectively communicate with the maintainer of the code, not the compiler, and should be written with the maintainer's comprehension in mind. I try to use "using" only to dispose of a resource, typically an unmanaged resource.

I am in a minority. Most people it seems use "using" as a general purpose "I want some cleanup code to run even if an exception is thrown" mechanism.

I dislike this because (1) we already have a mechanism for that, called "try-finally", (2) it uses a feature for a purpose it was not intended for, and (3) if the call to the cleanup code is important, then why isn't it visible at the point where it is called? If it is important then I want to be able to see it.

Eric Lippert