19

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 overloading an operator in a non-intuitive way?

Giffyguy
  • 20,378
  • 34
  • 97
  • 168
snarf
  • 2,684
  • 1
  • 23
  • 26
  • Why not just use { at the start and } at the end? This provides scope without abusing using. – cja Dec 08 '14 at 10:44

6 Answers6

20

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
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Good find, but do you have any reasoning for your argument? The article recommends it, but doesn't discuss why. – snarf Jul 07 '09 at 23:58
  • It's because it guarantees that that the EndOperation() is always called (via Dispose()) – Matthew Scharley Jul 08 '09 at 00:00
  • 1
    @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 Jul 08 '09 at 00:10
  • 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 Aug 05 '09 at 19:18
  • This discussion is about the using statement, not the using directive (i.e. importing namespaces and defining aliases is irrelevant to the topic). – snarf Aug 08 '09 at 01:48
12

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
  • 647,829
  • 179
  • 1,238
  • 2,067
  • It seems to me that this take on `using` is unqiuely restrictive. Out of curiosity, is there any other language feature in C# where you would recommend against its use -- even though it functions perfectly and clearly -- because said use wasn't what it was designed for? – Kirk Woll Apr 24 '12 at 23:25
  • @KirkWoll: Yes. I recommend against the use of *all* language features used for something other than what they were designed for. – Eric Lippert Apr 25 '12 at 06:14
  • @EricLippert - of course, it's not always clear what a language feature was designed for. In fact, even in the specific case of `using`, I thought there was some indication that the name "using" was specifically chosen because it was anticipated that the feature _would not_ be used only for disposing of unmanaged resources. – kvb Apr 25 '12 at 14:27
  • I'm personally unable to accept this belief because otherwise it would be against your belief to [use Java on anything other than embedded systems](http://programmers.stackexchange.com/questions/49422/was-java-originally-designed-for-a-toaster). – rwong Jan 02 '15 at 23:20
6

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
  • 15,461
  • 8
  • 52
  • 69
  • +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 Jul 08 '09 at 00:32
  • 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 Jul 08 '09 at 00:34
  • 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. – snarf Jul 08 '09 at 00:35
  • 2
    @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 Jul 08 '09 at 00:39
5

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
  • 99,783
  • 25
  • 219
  • 289
  • 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 Jul 08 '09 at 00:13
  • 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 Jul 08 '09 at 00:24
  • 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 Jul 08 '09 at 00:33
  • 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 Jul 08 '09 at 06:45
2

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

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • 1
    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 Jul 07 '09 at 23:56
  • How does this impact maintainability? And is this the only reason that you recommend strict adherence to canon? – snarf Jul 07 '09 at 23:59
  • 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 Jul 08 '09 at 00:14
  • 1
    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 Jul 08 '09 at 00:20
0

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
  • 6,199
  • 6
  • 50
  • 66
  • 1
    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 Jul 08 '09 at 00:19
  • 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. – snarf Jul 08 '09 at 00:31
  • 3
    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 Jul 08 '09 at 00:31