Click here to Skip to main content
Click here to Skip to main content

Using "using" Statements: DisposalAccumulator

By , 25 Feb 2013
Rate this:
Please Sign up or sign in to vote.

Epigraph:

Thanks to everyone; everyone is dismissed.

Favorite film director's phrase, according to some movies about movie making Smile | <img src=

Introduction

This tip is based on my own practice but was recently inspired by my CodeProject colleague Mika Wendelius who posed a very interesting question in the CodeProject Questions & Answers forum.

Using a pattern based on the interface System.IDisposable and the C# using statement, he managed to face a very simple but representative case where using could dramatically damage the code reuse. I answered on the question on the same day, pointing out that the solution really depends on the relationships between classes involved in the instantiation under the using statement's resource acquisition part (the class declarations were not shown in the question). If two of the classes are related by inheritance, and one of the classes uses a late-bound constructor parameter, one pretty obvious resolution is possible without any repeated code fragments. I demonstrated this solution based on the assumption of having inheritance and late binding in those IDisposable classes. I also basically explained how a solution for a general case could look, but did not provide any code for that, because I think that Mika is more than enough qualified to get the idea and do everything by himself. Anyone can see this answer here.

The problem with the remarkable Mika's example is rooted in the clash between having dynamic (run-time) dependency between classes or structures instantiated under the using resource acquisition and the using construct structure being defined statically and frozen by compile time. When Mika introduces a condition affecting the dependency in the object graph, it broke the static using structure.

I have a solution for this problem, but the exotic Mika's example is not the only case where this solution can be used. It was something I never thought of before (again, a wonderful example), but there are many more trivial examples which would make using just poorly readable and hence a subject of developer's mistakes. One such trivial case would be the one with the need of excessive using blocks nesting; a simple case where there are just too many disposable objects could also make using of using somewhat problematic.

That's why I think the simple technique I'm going to show could be of some interest to many members.

The Problem

I've slightly modified Mika's sample, to make it a bit simpler but to manifest the problem in a bit more dramatic way.

Consider the following method with two different nested using statements. I need this “naive” code sample to expose the problem:

static void NaiveWay(bool createSecond) {
    if (createSecond)
        using (First first = new First()) {
            using (Second second = new Second(first)) {
                using (Third third = new Third(second)) {
                    third.DoSomething();
                } //disposing third
            } //disposing second
        } //disposing first
    else //oh, no! :-) the code reuse is lost:
        using (First first = new First()) {
            using (Third third = new Third(first)) {
                third.DoSomething();
            } //disposing third
        } //disposing first
} //NaiveWay

Apparently, this “naive” violates one of the most fundamental principles of programming: “Don't repeat yourself”. Such repeated code fragments is always a problem for code maintenance and hence they invite human mistakes and threaten code reliability. The two code fragments with different using structures should be somehow merged together, but how?

Why the two if branches would be needed? This is because the different values of the Boolean parameters dictate different nesting of using blocks, not just the structure of the object graph. As I pointed out in my answer referenced above, different class declarations and different object graphs can require such different using structures. Normally, different forms of the object graph depending on some conditions do not present any problems, but instantiation in the resource acquisition part of the using statements (please see the ISO/IEC 23270 of 09/01/2006 standard document, section 15.3, “The using statement”) creates the problem.

In one case, all three classes could be not related by inheritance, but the different using structures could result from the fact that the class Third has two alternative constructors:

using System;

//...

class First : IDisposable {
    void IDisposable.Dispose() {/* ... */}
    //...
} //class First

class Second : IDisposable {
    internal Second(First child) {/* ... */}
    void IDisposable.Dispose() {/* ... */}
    //...
} //class Second

class Third : IDisposable {
    internal Third(First child) {/* ... */}
    internal Third(Second child) {/* ... */}
    internal void DoSomething() {/* ... */}
    void IDisposable.Dispose() {/* ... */}
    //...
} //class Third

Another variant of declaration of the classes could also use identical using structures shown above. This is the case where the class Third uses the single constructor with the late bound run-time type of the input parameter:

class First : IDisposable {
    void IDisposable.Dispose() {/* ... */}
    //...
} //class First

class Second : First, IDisposable {
    internal Second(First child) {/* ... */}
    void IDisposable.Dispose() {/* ... */}
    //...
} //class Second

class Third : IDisposable {
    internal Third(First child) {/* ... */}
    internal void DoSomething() {/* ... */}
    void IDisposable.Dispose() {/* ... */}
    //...
} //class Third

For the second case, I've demonstrated the solution shown in sample code in my answer referenced above. But this is a special case. How to overcome the difficulty explained in the present section above in a universal manner which would cover both cases of the object graph, as well as many more?

To approach the problem, let's remember that the purpose of using of the using statement the automatic call to the method System.IDisposable.Dispose and that it is strictly equivalent to some try-finally statement. (Please see this link.) Of course, if we try to write such equivalent try-finally statement, the problem of repeated code can be resolved. Let's see:

static void DontRepeatYourselfTryFinally(bool createSecond) {
    First first = null;
    Second second = null;
    Third third = null;
    try {
        first = new First();
        if (createSecond) {
            second = new Second(first);
            third = new Third(second);
        } else
            third = new Third(first);
        third.DoSomething();
    } finally {
        if (third != null) ((IDisposable)third).Dispose();
        if (second != null) ((IDisposable)second).Dispose();
        if (first != null) ((IDisposable)first).Dispose();
    } //exception
} //DontRepeatYourselfTryFinally

Is it a good solution? Well, it's pretty ugly, despite of getting rid of some repeated code. First of all, it requires type casting with all those brackets; and this is always ugly and error prone. Even more importantly, one clear benefit of using has gone: now we need to remember about both construction of objects and calling that Dispose method, which can easily be forgotten. It could be considered as better code or not, but certainly not as a good one.

The Solution

Let's finally admit that using just different variants of C# syntax is not enough. We need a helper class which can cover all the possible object construction patterns in a uniform manner. It does not even have to be generic, because it simply deals with types (classes, and structures as well, in boxed form) implementing the interface System.IDisposable. However, using its only public method is dramatically simplified if we make it generic. I called it DisposalAccumulator:

using IDisposable = System.IDisposable;
using System.Collections.Generic;

public class DisposalAccumulator : IDisposable {

    public T Add<T>(T element) where T : IDisposable {
        if (element != null)
            stack.Push(element);
        return element;
    } //Add

    void IDisposable.Dispose() {
        foreach (IDisposable element in stack)
            element.Dispose();
    } //IDisposable.Dispose

    Stack<IDisposable> stack = new Stack<IDisposable>();

} //class DisposalAccumulator

In essence, the class implements System.IDisposable.Dispose to dispose all its added elements. The Stack class is used to implement element storage, without extra overhead, with the most natural order of enumeration of elements used for disposal: in reverse, relative to the order of adding them.

Now, the usage is reduced to using of only one using statement, no ifs, no buts. It's used only for the instance of the DisposalAccumulator:

static void DontRepeatYourself(bool createSecond) {
    using (DisposalAccumulator accumulator = new DisposalAccumulator()) {
        First first = accumulator.Add(new First());
        Third third;
        if (createSecond) //no need to create a variable "second"
            third = accumulator.Add(new Third(accumulator.Add(new Second(first))));
        else
            third = accumulator.Add(new Third(first));
        third.DoSomething();
    } //everything is disposed
} //DontRepeatYourself 

Note the value of the generic Add method with the pass-through input parameter. Interestingly, type inference (available in .NET v.3.5 and later) allows not to instantiate this generic method with Add, as the compiler can figure out the required generic parameter from the compile-time type of a variable on the left of the assignment operator. It makes the usage especially smooth.

One may argue that the call to DisposalAccumulator.Add can easily be forgotten for some of the variables. My answer would be: that is not more likely than forgetting of using one of using statements; it usually happens when a developers fails to see that some library type implements System.IDisposable.

Conclusion

Overall, the nesting using statements, even without a run-time condition affecting their structures could be used if the number of disposable objects is reasonably small. In all other cases, using the DisposalAccumulator or a similar helper would be quite beneficial.

Credits

Of course, I appreciate inventiveness, the thirst for perfection, openness and collaborative attitudes of Mika Wendelius who caused me to get a very fresh look at the problem.

Andreas Gieriet found a bug in the last code fragment, usage sample. I fixed it. Thank you very much, Andi!

Robert Schutt advised an improvement to the code sample of DontRepeatYourselfTryFinally — thank you very much, Robert!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sergey Alexandrovich Kryukov
Architect
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberEdo Tzumer25-Feb-13 19:16 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov25-Feb-13 19:35 
GeneralMy vote of 5 PinmemberMarcus Kramer2-Nov-12 10:40 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov2-Nov-12 10:51 
GeneralRe: My vote of 5 PinmemberMarcus Kramer2-Nov-12 10:58 
Questionput try/catch PinmemberFatCatProgrammer18-Sep-12 7:20 
AnswerRe: put try/catch PinmvpSergey Alexandrovich Kryukov18-Sep-12 10:00 
GeneralRe: put try/catch PinmemberFatCatProgrammer18-Sep-12 12:51 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov18-Sep-12 14:02 
GeneralRe: put try/catch PinmemberFatCatProgrammer18-Sep-12 14:46 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov18-Sep-12 14:57 
GeneralRe: put try/catch PinmemberFatCatProgrammer18-Sep-12 15:16 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov18-Sep-12 16:29 
GeneralRe: put try/catch PinmemberFatCatProgrammer19-Sep-12 0:46 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov19-Sep-12 8:23 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov19-Sep-12 11:08 
GeneralRe: put try/catch PinmemberFatCatProgrammer20-Sep-12 2:54 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov20-Sep-12 6:22 
GeneralRe: put try/catch PinmemberFatCatProgrammer20-Sep-12 17:11 
GeneralRe: put try/catch PinmvpSergey Alexandrovich Kryukov20-Sep-12 18:44 
GeneralExtending this idea Pinmembertorial18-Sep-12 4:55 
GeneralRe: Extending this idea PinmvpSergey Alexandrovich Kryukov18-Sep-12 10:03 
GeneralRe: Extending this idea Pinmembertorial21-Sep-12 3:55 
GeneralMy vote of 5 PinmemberAdamDavidHill18-Sep-12 0:44 
Well written and an interesting problem. Smile | :)
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov18-Sep-12 3:47 
GeneralMy vote of 5 Pinmemberrobert_schutt17-Sep-12 4:02 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov17-Sep-12 14:14 
GeneralThe call added (Re: My vote of 5) PinmvpSergey Alexandrovich Kryukov17-Sep-12 14:23 
GeneralMy vote of 5 PinmemberDrABELL16-Sep-12 7:58 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov16-Sep-12 15:38 
GeneralMy vote of 5 PinmvpMika Wendelius14-Sep-12 7:56 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov14-Sep-12 8:15 
SuggestionA slightly different approach.... PinmemberAndrew Rissing14-Sep-12 5:19 
GeneralRe: A slightly different approach.... PinmemberAndrew Rissing14-Sep-12 5:22 
GeneralRe: A slightly different approach.... PinmvpSergey Alexandrovich Kryukov14-Sep-12 6:29 
QuestionMy 5! PinmemberAndreas Gieriet13-Sep-12 22:11 
AnswerRe: My 5! [modified] PinmvpSergey Alexandrovich Kryukov14-Sep-12 6:30 
GeneralMy vote of 5 Pinmemberazweepay13-Sep-12 21:51 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov14-Sep-12 6:37 
GeneralMy vote of 5 PinmemberEddy Vluggen13-Sep-12 21:22 
GeneralRe: My vote of 5 PinmvpSergey Alexandrovich Kryukov14-Sep-12 6:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 26 Feb 2013
Article Copyright 2012 by Sergey Alexandrovich Kryukov
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid