When combining a using statement with a fluent api that can potentially throw, the lowered code will never call dispose correctly.
If I have the following class that exposes a fluent interface:
public class Wrapper : IDisposable
{
private bool _isAdded;
public Wrapper Add()
{
_isAdded = true;
return this;
}
public void Dispose() => Console.WriteLine("dispose called");
public Wrapper ThrowIfAdded() => _isAdded ? throw new Exception() : this;
}
and I call it with the following:
using var willNotDispose = new Wrapper().Add().ThrowIfAdded();
the lowered code will result in the Dispose call occurring after the fluent method chain is completed.
Wrapper willNotDispose = new Wrapper().Add().ThrowIfAdded();
try
{
}
finally
{
if (willNotDispose != null)
{
((IDisposable)willNotDispose).Dispose();
}
}
Alternatively, if the call to .ThrowIfAdded() is done outside of the initial using declaration,
using var willDispose = new Wrapper().Add();
willDispose.ThrowIfAdded();
the lowered code is generated as expected.
Wrapper willDispose = new Wrapper().Add();
try
{
willDispose.ThrowIfAdded();
}
finally
{
if (willDispose != null)
{
((IDisposable)willDispose).Dispose();
}
}
While I understand why this is occurring, it isn't desirable. Is there any way to coerce the former initialization to compile to the latter? Ideally, it would be an attribute or form of compiler hint that would result in:
Wrapper willDispose = default;
try
{
willDispose = new Wrapper().Add().ThrowIfAdded();
}
finally
{
if (willDispose != null)
{
((IDisposable)willDispose).Dispose();
}
}
which I would have expected the original example to compile to in the first place.