'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 1:[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());
Solution 2:[2]
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
Solution 3:[3]
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://docs.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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Servy |
| Solution 2 | Jens Meinecke |
| Solution 3 | KRA2008 |
