-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Open
dotnet/corefx
#28674Labels
api-needs-workAPI needs work before it is approved, it is NOT ready for implementationAPI needs work before it is approved, it is NOT ready for implementationarea-System.Reflection
Milestone
Description
This addresses long-standing issues with reflection around support for byref-like types. For additional detail, see
- New reflection invoke APIs [draft] designs#286 (this issue is a subset of the proposed design)
- Developers using reflection invoke should be able to use ref struct #45152
- [API Proposal]: byref parameter collection for invoke #75349
API
Note that these are all new types so the diff
format was not used for readability.
namespace System.Reflection
{
public ref struct MethodInvoker
{
// This type is designed for supporting a variable-length number of arguments allocated by the caller
public unsafe MethodInvoker(ArgumentValue* argumentStorage, int argCount)
// Dispose needs to be called to unregister GC tracking
public void Dispose()
// Target
public object? GetTarget()
public ref T GetTarget<T>()
public void SetTarget(object value)
public unsafe void SetTarget(void* value, Type type)
public void SetTarget<T>(ref T value)
// Arguments
public object? GetArgument(int index)
public ref T GetArgument<T>(int index)
public void SetArgument(int index, object? value)
public unsafe void SetArgument(int index, void* value, Type type)
public void SetArgument<T>(int index, ref T value)
// Return
public object? GetReturn()
public ref T GetReturn<T>()
public void SetReturn(object value)
public unsafe void SetReturn(void* value, Type type)
public void SetReturn<T>(ref T value)
// Unsafe versions; no conversions or validation
public unsafe void InvokeDirectUnsafe(MethodBase method)
// Faster for fixed parameter count (object-only) and no ref\out. Any extra args are ignored.
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target)
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target, ReadOnlySpan<object?> args)
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target, object? arg1)
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target, object? arg1, object? arg2)
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target, object? arg1, object? arg2, object? arg3)
public static unsafe object? InvokeDirectUnsafe(MethodBase method, object? target, object? arg1, object? arg2, object? arg3, object? arg4)
// Safe versions; validation and conversions as in reflection today
public void Invoke(MethodBase method)
public static object? Invoke(MethodBase method, object? target)
public static object? Invoke(MethodBase method, object? target, ReadOnlySpan<object?> args)
public static object? Invoke(MethodBase method, object? target, object? arg1)
public static object? Invoke(MethodBase method, object? target, object? arg1, object? arg2)
public static object? Invoke(MethodBase method, object? target, object? arg1, object? arg2, object? arg3)
public static object? Invoke(MethodBase method, object? target, object? arg1, object? arg2, object? arg3, object? arg4)
}
// Used to define the correct storage requirements for the MethodInvoker constructor.
public struct ArgumentValue { }
}
Samples
unsafe
{
using (MethodInvoker invoker = new MethodInvoker(argCount: 3))
{
invoker.SetArgument(0, new MyClass());
invoker.SetArgument(1, null);
invoker.SetArgument(2, 42);
invoker.SetArgument(3, "Hello");
invoker.InvokeDirectUnsafe(method);
}
}
Avoiding boxing
Value types can be references to avoid boxing.
int i = 42;
int ret = 0;
using (MethodInvoker invoker = new MethodInvoker(argCount: 3))
{
invoker.SetArgument(0, new MyClass());
invoker.SetArgument(1, null);
invoker.SetArgument<int>(2, ref i); // No boxing (argument not required to be byref)
invoker.SetArgument(3, "Hello");
invoker.SetReturn<int>(ref ret); // No boxing; 'ret' variable updated automatically
unsafe
{
invoker.InvokeDirectUnsafe(method);
}
}
Pass a Span<T>
to a method
Span<int> span = new int[] { 42, 43 };
using (MethodInvoker invoker = new MethodInvoker(argCount: 1))
{
unsafe
{
MethodInvoker invoker = new MethodInvoker(ref args);
#pragma warning disable CS8500
void* ptr = (void*)new IntPtr(&span);
#pragma warning restore CS8500
// Ideally in the future we can use __makeref(span) instead of the above.
invoker.SetArgument(0, ptr, typeof(Span<int>));
invoker.InvokeDirectUnsafe(method);
}
}
Future
For perf, we may add fixed-length parameter storage to MethodInvoker:
// Fixed length (say up to 8)
public MethodInvoker(ref ArgumentValuesFixed values)
along with the supporting type:
// Used for fastest perf for the MethodInvoker ctor above where the arguments are of a known small count.
public ref struct ArgumentValuesFixed
{
public const int MaxArgumentCount; // 8 shown here (pending perf measurements to find optimal value)
// Used for the general case instead of the ctors below that only take 'object'.
public ArgumentValuesFixed(int argCount)
public ArgumentValuesFixed(object? arg1)
public ArgumentValuesFixed(object? arg1, object? arg2)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3, object? arg4)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3, object? arg4, object? arg5)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7)
public ArgumentValuesFixed(object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7, object? arg8)
}
with samples:
Fixed-length arguments
MethodInfo method = ... // Some method to call
ArgumentValuesFixed values = new(4); // 4 parameters
MethodInvoker= new MethodInvoker(ref values);
invoker.SetArgument(0, new MyClass());
invoker.SetArgument(1, null);
invoker.SetArgument(2, 42);
invoker.SetArgument(3, "Hello");
// Can inspect before or after invoke:
object o0 = invoker.GetArgument(0);
object o1 = invoker.GetArgument(1);
object o2 = invoker.GetArgument(2);
object o3 = invoker.GetArgument(3);
invoker.InvokeDirect(method);
int ret = (int)invoker.GetReturn();
Fixed-length object arguments (faster)
ArgumentValuesFixed args = new(new MyClass(), null, 42, "Hello");
MethodInvoker invoker = new MethodInvoker(ref args);
invoker.InvokeDirect(method);
Original issue text from @ahsonkhan:
From https://github.com/dotnet/coreclr/issues/5851#issuecomment-337356969
It is about calling methods on Span or that take Span arguments via reflection:
- It is not possible to do it via existing reflection methods. We should have test to verify that e.g.
typeof(SpanExtensions).GetMethod("AsReadOnlySpan", new Type[] { typeof(string) }).Invoke(null, new object[] { "Hello" });
fails gracefully. - We may want to look into adding new reflection APIs that allow calling these methods via reflection.
adamkvd, cdmihai, TETYYS, scottbilas, 0xfeeddeadbeef and 3 more
Metadata
Metadata
Assignees
Labels
api-needs-workAPI needs work before it is approved, it is NOT ready for implementationAPI needs work before it is approved, it is NOT ready for implementationarea-System.Reflection