/cc @jkotas
Background
There are certain scenarios today - largely involving activation, serialization, and DI - where library authors perform codegen in order to perform operations on arbitrary types. The primary reason for this is performance. The standard Reflection APIs are too slow to be used in the code paths targeted by these library authors, and though codegen has a large upfront cost it performs considerably better when amortized over the lifetime of the application.
This approach generally works well, but the .NET Framework is considering scenarios where it must operate in environments which do not allow codegen. This renders ineffective the existing performance improvement techniques used by these library authors.
We are uniquely positioned to provide a set of APIs which can cover the majority of scenarios traditionally involving reflection-based codegen. The general idea is that library authors can rely on the APIs we provide to work correctly both in codegen-enabled and in codegen-disallowed environments. Alternatively, the library authors can detect at runtime whether codegen is enabled, and if so they can use their existing highly-optimized codegen logic, falling back to the new API surface if codegen is disallowed.
Sample API surface
namespaceSystem.Reflection{publicdelegaterefTFieldFieldAccessor<TField>(objecttarget);publicdelegaterefTFieldFieldAccessor<TTarget,TField>(refTTargettarget);/// <summary>/// Provides factories that can be used by serializers, formatters, and DI systems/// to perform reflection-like activities in performance-critical code paths./// </summary>[SecurityCritical]publicstaticclassReflectionServices{publicstaticboolIsCodegenAllowed{get;}/* * FIELD ACCESSORS, GETTERS, AND SETTERS */publicstaticFieldAccessor<TField>CreateFieldAccessor<TField>(FieldInfofieldInfo);publicstaticFieldAccessor<TTarget,TField>CreateFieldAccessor<TTarget,TField>(FieldInfofieldInfo);publicstaticFunc<object,object>CreateFieldGetter(FieldInfofieldInfo);publicstaticFunc<object,TField>CreateFieldGetter<TField>(FieldInfofieldInfo);publicstaticFunc<TTarget,TField>CreateFieldGetter<TTarget,TField>(FieldInfofieldInfo);publicstaticAction<object,object>CreateFieldSetter(FieldInfofieldInfo);publicstaticAction<object,TField>CreateFieldSetter<TField>(FieldInfofieldInfo);// TTarget must not be value type; field must be an instance field.publicstaticAction<TTarget,TField>CreateFieldSetter<TTarget,TField>(FieldInfofieldInfo);/* * PARAMETERLESS OBJECT CREATION */publicstaticFunc<object>CreateInstanceFactory(Typetype);publicstaticFunc<T>CreateInstanceFactory<T>();/* * PARAMETERFUL OBJECT CREATION */publicstaticFunc<object[],object>CreateInstanceFactory(ConstructorInfoconstructorInfo);publicstaticFunc<object[],T>CreateInstanceFactory<T>(ConstructorInfoconstructorInfo);publicstaticDelegateCreateInstanceFactoryTyped(ConstructorInfoconstructorInfo,TypedelegateType);/* * PROPERTY GETTERS AND SETTERS * TODO: How would indexed properties be represented? Using the normal method invocation routines? */publicstaticFunc<object,object>CreatePropertyGetter(PropertyInfopropertyInfo);publicstaticFunc<object,TProperty>CreatePropertyGetter<TProperty>(PropertyInfopropertyInfo);publicstaticFunc<TTarget,TProperty>CreatePropertyGetter<TTarget,TProperty>(PropertyInfopropertyInfo);publicstaticAction<object,object>CreatePropertySetter(PropertyInfopropertyInfo);publicstaticAction<object,TProperty>CreatePropertySetter<TProperty>(PropertyInfopropertyInfo);// TTarget must not be value type; property must be an instance property.publicstaticAction<TTarget,TProperty>CreatePropertySetter<TTarget,TProperty>(PropertyInfopropertyInfo);/* * METHODS */publicstaticFunc<object,object[],object>CreateMethodInvoker(MethodInfomethodInfo);// If instance method, 'delegateType' must be open over 'this' parameter.publicstaticDelegateCreateMethodInvoker(MethodInfomethodInfo,TypedelegateType);/* * EVENTS */publicstaticAction<object,object>CreateEventSubscriber(EventInfoeventInfo);// Event must be an instance event.publicstaticAction<TTarget,TDelegate>CreateEventSubscriber<TTarget,TDelegate>(EventInfoeventInfo);publicstaticAction<object,object>CreateEventUnsubscriber(EventInfoeventInfo);// Event must be an instance event.publicstaticAction<TTarget,TDelegate>CreateEventUnsubscriber<TTarget,TDelegate>(EventInfoeventInfo);}}Goals and non-goals
These APIs are not geared toward standard application developers who are already comfortable using the existing Reflection API surface. They are instead geared toward advanced library developers who need to perform Reflection operations in performance-sensitive code paths.
These APIs must work in a codegen-disallowed execution environment. (Are there exceptions?)
These APIs do not need to cover all scenarios currently allowed by the existing methods on MethodInfo and related types. For example, constructors that take ref or out parameters are sufficiently rare that we don't need to account for them. They can be invoked via the standard Reflection APIs.
These APIs do not need to have the same observable behavior as using the Reflection APIs; e.g., we may determine that these APIs should not throw TargetInvocationException on failure. But these APIs must provide consistent behavior regardless of whether they're running within a codegen-enabled or a codegen-disallowed environment.
Delegate creation does not need to be particularly optimized since there will be many checks performed upfront and we will ask callers to cache the returned delegate instances. However, once the delegates are created their invocation must be faster than calling the existing Reflection APIs. (Exception: if codegen is disallowed, then delegate invocation should be faster than calling the existing Reflection APIs wherever possible, and it must not be slower.)
It is an explicit goal to get serialization library authors to prefer this system over hand-rolling codegen for most member access scenarios. The selling points of this API would be ease of use (compared to hand-rolling codegen), performance, and the ability to work in a wide variety of execution environments.
It is an explicit non-goal to have performance characteristics equal to or better than a library's own custom codegen. For example, a DI system might choose to codegen a single method that both queries a service provider to get dependency instances and calls newobj on the target constructor. Such a system will always outperform these generalized APIs, but the API performance should be good enough that library authors would be generally satisfied using them over Reflection as a fallback in these scenarios.
These APIs do not need to support custom implementations of MemberInfo. Only support for CLR-backed members is required.
/cc @jkotas
Background
There are certain scenarios today - largely involving activation, serialization, and DI - where library authors perform codegen in order to perform operations on arbitrary types. The primary reason for this is performance. The standard Reflection APIs are too slow to be used in the code paths targeted by these library authors, and though codegen has a large upfront cost it performs considerably better when amortized over the lifetime of the application.
This approach generally works well, but the .NET Framework is considering scenarios where it must operate in environments which do not allow codegen. This renders ineffective the existing performance improvement techniques used by these library authors.
We are uniquely positioned to provide a set of APIs which can cover the majority of scenarios traditionally involving reflection-based codegen. The general idea is that library authors can rely on the APIs we provide to work correctly both in codegen-enabled and in codegen-disallowed environments. Alternatively, the library authors can detect at runtime whether codegen is enabled, and if so they can use their existing highly-optimized codegen logic, falling back to the new API surface if codegen is disallowed.
Sample API surface
Goals and non-goals
These APIs are not geared toward standard application developers who are already comfortable using the existing Reflection API surface. They are instead geared toward advanced library developers who need to perform Reflection operations in performance-sensitive code paths.
These APIs must work in a codegen-disallowed execution environment. (Are there exceptions?)
These APIs do not need to cover all scenarios currently allowed by the existing methods on
MethodInfoand related types. For example, constructors that takereforoutparameters are sufficiently rare that we don't need to account for them. They can be invoked via the standard Reflection APIs.These APIs do not need to have the same observable behavior as using the Reflection APIs; e.g., we may determine that these APIs should not throw
TargetInvocationExceptionon failure. But these APIs must provide consistent behavior regardless of whether they're running within a codegen-enabled or a codegen-disallowed environment.Delegate creation does not need to be particularly optimized since there will be many checks performed upfront and we will ask callers to cache the returned delegate instances. However, once the delegates are created their invocation must be faster than calling the existing Reflection APIs. (Exception: if codegen is disallowed, then delegate invocation should be faster than calling the existing Reflection APIs wherever possible, and it must not be slower.)
It is an explicit goal to get serialization library authors to prefer this system over hand-rolling codegen for most member access scenarios. The selling points of this API would be ease of use (compared to hand-rolling codegen), performance, and the ability to work in a wide variety of execution environments.
It is an explicit non-goal to have performance characteristics equal to or better than a library's own custom codegen. For example, a DI system might choose to codegen a single method that both queries a service provider to get dependency instances and calls
newobjon the target constructor. Such a system will always outperform these generalized APIs, but the API performance should be good enough that library authors would be generally satisfied using them over Reflection as a fallback in these scenarios.These APIs do not need to support custom implementations of
MemberInfo. Only support for CLR-backed members is required.