Problem statement
There are sometimes foreach
scenarios that require deep nesting due to multiple disposable objects involved that look like this:
using(..) { foreach(..) { using(..) { } } }
or a real-world example from one of my applications:
using (_logger.BeginScope().CorrelationHandle("TestBundle").AttachElapsed()) using (Disposable.Create(() => { foreach (var item in cache.Values) item.Dispose(); })) { _logger.Log(Abstraction.Layer.Service().Meta(new { TestBundleFileName = testBundle.FileName })); foreach (var current in tests) { using (_logger.BeginScope().CorrelationHandle("TestCase").AttachElapsed()) { // body } } }
I find this code is very ugly and there is virtually no other way of writing this (or it's just me and I cannot think of any).
Suggested solution
So, in order to make it prettier I created the Using
extension that encapsulates the outer and inner using
s within the loop.
Its body is identical to the using/foreach/using
pattern I'd like to get rid of elsewhere:
public static class EnumerableExtensions
{
public static IEnumerable<(TItem Current, TInner Context)> Using<TItem, TOuter, TInner>
(
this IEnumerable<TItem> source,
TOuter outer,
Func<TItem, TInner> inner
)
{
using (new Disposer<TOuter>(outer))
{
foreach (var item in source)
{
using (var context = new Disposer<TInner>(inner(item)))
{
yield return (item, context);
}
}
}
}
}
This is using a helper struct
for disposing TOuter
and TInner
public readonly struct Disposer<T> : IDisposable
{
public Disposer(T value)
{
Value = value;
}
public T Value { get; }
public void Dispose()
{
if (Value is IDisposable disposable)
{
disposable.Dispose();
}
else
{
foreach (var property in typeof(T).GetProperties())
{
if (property.GetValue(Value) is IDisposable disposableProperty)
{
disposableProperty.Dispose();
}
}
}
}
public static implicit operator T(Disposer<T> disposer) => disposer.Value;
}
And here is the Disposable
helper:
public class Disposable : IDisposable
{
private readonly Action _dispose;
private Disposable(Action dispose)
{
_dispose = dispose;
}
public static IDisposable Empty => new Disposable(() => { });
public static IDisposable Create(Action dispose)
{
return new Disposable(dispose);
}
public void Dispose()
{
_dispose();
}
}
Example usage
Now, when I have such a scenario, I can put everything inside the loop:
void Main()
{
var numbers = new[] { 1, 2, 3 };
foreach (var (item, context) in numbers.Using
(
outer: Disposable.Create (() => Console.WriteLine("Outer disposed.")),
inner: _ => Disposable.Create (() => Console.WriteLine("Inner disposed.")))
)
{
item.Dump();
context.Dump();
}
}
Then, when I refactor the previous ugly piece of code with this extension it turns into this:
foreach (var (current, context) in tests.Using ( outer: new { Scope = _logger.BeginScope().CorrelationHandle("TestBundle").AttachElapsed(), CleanUp = Disposable.Create(() => { foreach (var item in cache.Values) item.Dispose(); }) }, inner: _ => _logger.BeginScope().CorrelationHandle("TestCase").AttachElapsed()) ) { // body }
Questions
- What do you think of this helper?
- Would you say the code is now easier to read?
- Can the
Using
extension or theDisposer<T>
be improved?