Search code examples
c#garbage-collectionusingidisposablereadability

Is there a using/Dispose syntax that will be more eager to dispose but just as safe as chaining?


If I'm doing the following:

using (var foo = bar.GetFoo())
using (var blat = new Blat(foo))
using (var bonk = Bonk.FromBlat(blat))
using (var bork = Bob.FromBip(bonk))
{
    var target = bork.ToArray(); // I GOT THE TARGET
}

It's very safe and readable, but my understanding is that only after the very last line does the whole thing unwind to dispose everything. Given that foo, blat, and bonk are only used in single lines to create bork, is there a good way ("good" being subjective of course) to dispose them the moment after they're used to free up resources as soon as possible? I really like the safety and readability of the using chain, I'm just curious if there is another way to do this.


Solution

  • You can write a method to use an IDisposable that produces a value. Part of the issue here is that a using statement, being a statement, doesn't produce a value, making it hard to chain:

    public static TResult Use<TSource, TResult>(this TSource source, Func<TSource, TResult> selector)
        where TSource : IDisposable
    {
        using (source)
            return selector(source);
    }
    

    This lets you write:

    var target = bar.GetFoo()
        .Use(foo => new Blat(foo))
        .Use(blat => Bonk.FromBlat(blat))
        .Use(bonk => Bob.FromBonk(bonk))
        .Use(bork => bork.ToArray());
    

    Note that this Use method accepts an IDisposable from outside, but disposes it of it itself, so if you call Use on an IDisposable that you're still keeping in scope, you could end up using it even after it's been disposed, which should error. In this case we're never hold onto the disposable objects, but someone else might not know that they shouldn't do that.

    If you wanted to address that, you could write a single function that chains disposable values together. By putting it all in one function you can ensure that no disposables "leak" out of it quite so easily:

    public static TResult ChainDisposables<T1, T2, T3, T4, TResult>(Func<T1> firstFunc,
        Func<T1, T2> secondFunc,
        Func<T2, T3> thirdFunc,
        Func<T3, T4> fourthFunc,
        Func<T4, TResult> resultFunc)
        where T1 : IDisposable
        where T2 : IDisposable
        where T3 : IDisposable
        where T4 : IDisposable
    {
        return firstFunc()
            .Use(secondFunc)
            .Use(thirdFunc)
            .Use(fourthFunc)
            .Use(resultFunc);
    }
    

    (You'd need to create overloads for each different number of objects in the chain, by just following the example.)

    Which would let you write:

    var target = ChainDisposables(() => bar.GetFoo(),
        foo => new Blat(foo),
        blat => Bonk.FromBlat(blat),
        bonk => Bob.FromBonk(bonk),
        bork => bork.ToArray());