Description
Support ref struct
and "fast invoke" by adding new reflection APIs. The new APIs will be faster than the current object[]
boxing approach by leveraging the existing System.TypedReference<T>
.
TypedReference
is a special type and is super-fast because since it is a ref struct
with its own opcodes. By extending it with this feature, it provides alloc-free, stack-based “boxing” with support for all argument types (reference types, value types, pointers and ref structs [pending]) along with all modifiers (byval, in, out, ref, ref return). Currently reflection does not support passing or invoking a ref struct
since it can’t be boxed to object
; the new APIs are to support ref struct
with new language features currently being investigated.
Example syntax (actual TBD):
MyClass obj= …;
MethodInfo methodInfo = …;
int param1 = 42; // Pass an integer
Span<int> param2 = new Span<int>(new int[]{1, 2, 3}); // Pass a span (not possible with reflection today)
// Adding support for Span<TypedReference> requires the language and runtime asks below.
Span<TypedReference> parameters = stackalloc TypedReference[2];
// The 'byval', 'in', 'out' and 'ref' modifiers in the callee method's parameters all have the same caller syntax
parameters[0] = TypedReference.FromRef(ref param1);
parameters[1] = TypedReference.FromRef(ref param2);
methodInfo.GetInvoker().Invoke(returnValue: default, target: TypedReference.FromRef(ref obj), parameters);
// The first call to this methodInfo will be slower; subsequent calls used cached IL when possible
// Shorter syntax using __makeref (C# specific)
parameters[0] = __makeref(param1);
parameters[1] = __makeref(param2);
methodInfo.Invoke(default, __makeref(obj), parameters));
Dependencies
The Roslyn and runtime dependencies below are required for the programming model above. These are listed in the order in which they need to be implemented.
- ref fields: necessary to rationalize TypedReference.
- Support passing a
ref struct
as a generic argument. This would be used when adding the static factory methodpublic static TypedReference CreateFromRefStruct<T>(ref T myRefStruct) where T : ref struct
which basically wraps__makeref(myrefstruct)
. It could also be used to enableSpan<T> where T : ref struct
which is the ideal stack-based collection implementation that can containTypedReference
s. - An arbitrary array/span of references to enable a stack-based, variable length container for method arguments. See also related comments in ref fields: necessary to rationalize TypedReference and prototype using
params
. The new invoke APIs require a collection ofTypeReference
s. There are several possible implementations; the most normalized solution would be supportingSpan<T> where T : ref struct
thus enabling anyref struct
(not justTypedReference
) to be used in a container. UPDATE: done in prototype per [API Proposal]: byref parameter collection for invoke #75349 - Removing the concept of restricted types including
TypedReference
. (Roslyn link TBD). Ideally,TypedReference
is a normalref struct
. Note thatTypedReference
currently hasByReference<byte>
to contain an interior pointer to the value so this will likely need a different type. Also,TypedReference
has several compile-time limitations including not be able to be passed to another method that should be removed resulting in only standardref struct
semantics. - Add reflection support for Span and other ref struct types (this is the library\API issue and requires all of the above work items)
Motivation
Reflection is ~20x slower than a Delegate call for a typical method. Many users including our own libraries use IL Emit instead which is non-trivial and error-prone. The expected gains are ~10x faster with no allocs; verified with a prototype. Internally, IL Emit is used but with a proposed slow-path fallback for AOT (non-emit) cases. The existing reflection invoke APIs may also layer on this.
In Scope
APIs to invoke methods using TypedReference
including passing a TypedReference
collection. TypedReference
must be treated as a normal ref struct
(today it has nuances and special cases).
Support ref struct (passing and invoking).
Performance on par with existing ref emit scenarios:
- Property get\set
- Field get\set
- Parameterized constructors and methods
To scope this feature, the minimum functionality that results in a win by allowing System.Text.Json
to remove its dependency to System.Reflection.Emit for inbox scenarios.
Out of Scope
This issue is an incremental improvement of reflection by adding new Invoke APIs and leveraging the existing TypedReference
while requiring some runtime\Roslyn changes. Longer-term we should consider a more holistic runtime and Roslin support for reflection including JIT intrinsics and\or new "dynamic invoke" opcodes for performance along with perhaps C# auto-stack-boxing to\from a ref TypedReference
.
Implementation
A design doc is forthcoming.
The implementation will likely cache the generated method on the corresponding MethodBase
and MemberInfo
objects.
100% backwards compat with the existing object[]-based Invoke APIs is not necessary but will be designed with laying in mind (e.g. parameter validation, special types like ReflectionPointer, the Binder
pattern, CultureInfo for culture-aware methods) so that in theory the existing object[]-based Invoke APIs could layer on this new work.
This issue supersedes other reflection performance issues that overlap: