A using statement required the type to implement IDisposable. That was a problem for ref struct C# 7.2 types such as Span<T>, which cannot implement an interface at all - implementing IDisposable would make them boxable, which defeats the purpose of a stack-only type.
C# 8.0 adds pattern-based disposal. If a type has an accessible Dispose() method that returns void, a using statement will call it, with no interface involved.
This is what lets a ref struct own stack storage and still get deterministic cleanup while staying on the stack.
Code
csharp
ref struct Arena
{
private Span<byte> _buffer;
public Arena(Span<byte> buffer, int size) => _buffer = buffer[..size];
public Span<byte> Rent(int count) => _buffer[..count];
public void Dispose() => _buffer.Clear(); // No interface needed
}
static void Work()
{
using var arena = new Arena(stackalloc byte[1024], 1024);
var slice = arena.Rent(16);
// Dispose clears the buffer at the end of the method
}csharp
ref struct Arena
{
private Span<byte> _buffer;
public Arena(Span<byte> buffer, int size) => _buffer = buffer[..size];
public Span<byte> Rent(int count) => _buffer[..count];
public void Clear() => _buffer.Clear();
}
static void Work()
{
var arena = new Arena(stackalloc byte[1024], 1024);
try
{
var slice = arena.Rent(16);
}
finally
{
arena.Clear(); // Called by hand, easy to forget
}
}Notes
- The
Disposemethod must be accessible and returnvoid; it cannot returnbool, and an explicit interface implementation does not count because it cannot be called by name - Works with both the
usingstatement and the using declaration C# 8.0 form shown above - No interface is involved, so the call binds statically and does not box or dispatch - the same reason a
ref structcould not implementIDisposablein the first place - It also lets a class offer a cleanup hook without committing to the
IDisposablecontract, which is useful for a type that only sometimes owns a resource - Types that implement
IDisposableare unaffected; this is an additional path, not a replacement