-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
For structures, making a virtual call to the boxing interface will produce a copy, which is incorrect behavior.
Introducing these unsafe APIs can bypass boxing when needed and ensure the correct behavior of the structure
public struct WithSideEffect : IDisposable
{
public int a;
public void Dispose()
{
a = 456;
Console.WriteLine("123");
}
}
void SomeMethodCallDispose<T>(ref T a)
{
if (a is IDisposable b) b.Dispose();
}
// or
void SomeMethodCallDispose<T>(ref T a) where T : struct
{
if (a is IDisposable b) b.Dispose();
}
// or
void SomeMethodCallDispose<T>(ref T a) where T : struct
{
((IDisposable)a).Dispose();
}
...
var a = new WithSideEffect();
SomeMethodCallDispose(ref a);
if (a is IDisposable b) b.Dispose(); // or
((IDisposable)a).Dispose(); // or
// all same, copied
Console.WriteLine(a.a); // 0 https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=net-9.0
The constrained prefix can also be used for invocation of interface methods on value types, because the value type method implementing the interface method can be changed using a MethodImpl. If the constrained prefix is not used, the compiler is forced to choose which of the value type's methods to bind to at compile time. Using the constrained prefix allows the MSIL to bind to the method that implements the interface method at run time, rather than at compile time.
MSIL has the ability to call interfaces directly without boxing, which C# does not expose.
API Proposal
public static partial class RuntimeHelpers
{
// possible impl
internal class IsDisposable <T>
{
public static bool Value{ get; } = typeof(IDisposable).IsAssignableFrom(typeof(T));
}
[Intrinsic]
public static bool IsDisposable<T>() => IsDisposable<T>.Value;
}
public static unsafe partial class Unsafe
{
public static void TryDispose<T>(ref T value)
{
if (!RuntimeHelpers.IsDisposable<T>) return;
Dispose(ref value);
}
[Intrinsic]
public static void Dispose<T>(ref T value)
{
// ldarg.0
// constrained. !T
// callvirt instance void [System.Runtime]System.IDisposable::Dispose()
// ret
}
}API Usage
var a = new WithSideEffect();
Unsafe.TryDispose(ref a); // console: 123
Console.WrteLine(a.a); // 456Alternative Designs
No response
Risks
No response