Prevent IDisposable
structs from being boxed
#8337
-
ProblemThe following public struct MyStruct : IDisposable
{
public void Dispose()
{
// ...
}
} Can be used in an using (var something = new MyStruct())
{
// ...
} That will be lowered to this MyStruct something = default(MyStruct);
try
{
// ...
}
finally
{
((IDisposable)something).Dispose();
} This code will box the struct and allocate it on the heap, which is not optimal. public struct MyStruct : IDisposable
{
// (Explicit interface implementation)
void IDisposable.Dispose()
{
// ...
}
} Known solutionYou could fix the problem by making a public ref struct MyStruct
{
public void Dispose()
{
// ...
}
} Which would be used like this behind the scenes MyStruct something = default(MyStruct);
try
{
// ...
}
finally
{
something.Dispose();
} But it would make the struct not capable of escaping the stack, which would be an unnecessary limitation in this case ProposalCreate a generic static method that handles disposals // ↓ (Preferably by ref)
public static void Dispose<T>(ref T value) where T : IDisposable => value.Dispose(); That would be called like this behind the scenes MyStruct something = default(MyStruct);
try
{
// ...
}
finally
{
SomeClass.Dispose(ref something);
} Which would prevent boxing, allow for explicit interface implementations and would work with classes too |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
|
Beta Was this translation helpful? Give feedback.
-
I don't think that boxing even occurs in your introductory example. It is just that SharpLab and ILSpy produce this output, because the original IL cannot be expressed better in C# (see icsharpcode/ILSpy#3089). In the using statement the call to Dispose is is lowered to a constrained callvirt instruction, which does not box, if the value type implements the method: |
Beta Was this translation helpful? Give feedback.
-
The old C# compiler always lowered The C# specification says the lowered form is JIT eliding the box is a recent thing --- I think it's since .NET 6.0. According to Performance Improvements in .NET 6.0, section "JIT", it appears ((IFace)(value_type_expr)).Method(args...) will be optimized to (var temp = (value_type_expr);
// constrained call, so
// it always calls the interface method,
// not the struct method.
temp.Method(args...))
// temp is otherwise inaccessible to user code,
// which is what makes this optimization valid. This optimization happens at JIT level ( |
Beta Was this translation helpful? Give feedback.
((IDisposable)something).Dispose()
does not box. It’s a "constrained call" and the JIT elides the actual boxing.