-1

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.

KRA2008
  • 121
  • 1
  • 11
  • 1
    *Approach 2 (equivalent to Approach 1 but less readable)* no true at all *Approach 3 (equal to Approaches 1 and 2 in safety....)* not true ... – Selvin Nov 08 '18 at 03:53
  • @Selvin Please edit to make true? Thanks. Is my question still apparent despite the mistake? – KRA2008 Nov 08 '18 at 03:54
  • App1 is `try{foo = bar.GetFoo();try{blat = new Blat(foo); /*and so on*/}finall{blat?.Dispose();}}finall{foo?.Dispose();}` ... so a) appr2 is disposing in other order b) it will not dispose rest if any of prev dispose throw an exception ... b) also apply to appr3 – Selvin Nov 08 '18 at 03:56
  • c) in appr3 you may use disposed object (as you dont know if `Bonk.FromBlat` implementation is not using `foo` internally) – Selvin Nov 08 '18 at 04:02
  • I have edited the question a lot to remove the aforementioned "Approaches" that were severely distracting and unhelpful for illustrating the question itself. The previous comments which reference them are probably now confusing. – KRA2008 Dec 10 '18 at 18:37
  • Which of the operations are quick, and which are long running? Do you need to dispose of each object before moving on to the next line, or do you just want to dispose of the first three before moving onto the final one? – Servy Dec 10 '18 at 18:38
  • @Servy The particular questions you asked are indeed important to consider, thank you. Given that this is a hypothetical situation I have no real answers for them, but if I did would there be any answer which would result in an alternative to the chained usings? – KRA2008 Dec 10 '18 at 18:46
  • @KRA2008 Absolutely. There's a huge difference between whether you dispose of each disposable immediately after creating the object after it, versus disposing of all of them only after creating the final object (but before using that final object). – Servy Dec 10 '18 at 18:56
  • I have trouble imagining a case where you would want/need to do this. What kind of resource can serve as a dependency after it has been disposed? And, if such a case exists, what is the `Dispose()` method even doing? If you, for example, create a reader from a stream, then closing the stream *before* the reader would generally leave the reader in an invalid state. If an API allows you to eagerly close dependent resources in this way, then perhaps there is something fundamentally wrong with how that API deals with resources. – Mike Strobel Dec 10 '18 at 19:29

3 Answers3

1

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());
Servy
  • 202,030
  • 26
  • 332
  • 449
0

Versions 1 & 2 are NOT functionally equivalent, as the order of disposal should be reversed in your pseudo code for 2:

Foo foo = null;
Blat blat = null;
Bonk bonk = null;
Bork bork = null;
try
{
    foo = bar.GetFoo();
    blat = new Blat(foo);
    bonk = Bonk.FromBlat(blat);
    bork = Bob.FromBip(bonk);
    var target = bork.Whatever; // I GOT THE TARGET
}
finally
{   // the disposal should happen in reverse order to instantiation
    bork?.Dispose();
    bonk?.Dispose();
    blat?.Dispose();
    foo?.Dispose();
}

And seeing how you got that order wrong, you might appreciate why using chained using statements ensures that things happen in the right order instead of relying on you to get it right

If the compiler can ensure that things are done in the right order - get the compiler to do it for you - less chance of errors

Jens Meinecke
  • 2,904
  • 17
  • 20
  • I have edited the question a lot to remove the "Approaches" that were severely distracting and unhelpful for illustrating the question itself. Feel free to edit your answer given the more focused question. – KRA2008 Dec 10 '18 at 18:39
0

3+ years later I can report that C# 8 released in September of 2019 added the inline using statement. My question says "I'm curious if there is another way to do this." and now there is another way.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

I can't say if the disposing happens any more eagerly but it looks nicer to me with fewer brackets and parentheses and also in certain cases avoids nesting.

KRA2008
  • 121
  • 1
  • 11