Skip to content

Expression Tree Issues

Michael Ketting edited this page Nov 5, 2017 · 1 revision

Original problem

Referencing a type that is in the process of being generated is not possible in standard expression trees (System.Linq.Expression). At generation time only a TypeBuilder is available for those types. Unfortunately instances of TypeBuilder, MethodBuilder, etc… cannot be used for building expression trees, because the expression classes have argument checks which exercise members (e.g. MethodBuilder.GetParameters) that are not implemented by the builder objects.

Approach

Instead of directly using TypeBuilder, MethodBuilder, etc… in expression trees custom reflection objects (MutableTypeInfo, MutableMethodInfo, etc…) are used. The custom reflection objects are derived from the abstract base classes (Type, MethodInfo) and implement the members exercised by the argument validation in the expression classes. They are also used to look up the real builder objects for code generation at a later time. We discovered the following problems and workarounds (+WA+) for this approach in combination with the original System.Linq.Expression and compiler classes (LambdaCompiler).

  • LambdaExpression.CompileToMethod(MethodBuilder) only works for static methods and the this reference cannot not be accessed directly.
    • +WA+: Compile to static helper method which explicitly takes the this reference as its first parameter and call it from the original method. Users would be supplied with an expression (e.g. from a context) that represents the this reference and can be used to create the expression tree. The This-Expression would then be switched out for an expression that represents the additional this parameter.
  • MethodCallExpression does not support the concept of non-virtual calls to methods that are defined virtual. This is needed in an overriding method that wants to call the overridden (base) method.
    • +WA+: Manually generate a helper method in IL which does the non-virtual base call. Use this method instead of a direct base call. Users would be supplied with an appropriate expression.
  • Referencing currently generated types is a problem in general. Tricking the expression classes by first supplying fake types, i.e. MutableTypeInfo (to pass argument validation) and then replacing the fake types with type builders via reflection just before code generation (LambdaExpression.CompileToMethod) is not enough: For code generation some of the members which are not properly implemented by TypeBuilder, MethodBuilder, etc… are needed. In addition, the builder classes are sealed.
    • +WA+: none The last issue implies that the standard code generation (LambdaCompiler) cannot be used as intended and has to be adapted for usage. Because the LambdaCompiler references many internal members of the System.Linq.Expression namespace, they cannot be used together with custom code generation.

Conclusion

Custom (DLR) code generation and custom expressions (DLR) are needed. Either the user of the pipeline directly creates DLR expression tress or standard (System.Linq.Expression) expression trees are mapped onto DLR trees. The goal is to make as few changes to the original DLR classes as possible so that they can be upgraded to new versions with acceptable effort.

Customizations (so far)

  • Custom ILGenerator: The code generation was changed to use a custom ILGenerator (IEmitter). This emitter generates proper IL for the MutableTypeInfo, MutableMethodInfo, etc… reflection objects by using the actual builder objects stored by them.
  • Extension expressions: Support for extension expressions (exp.NodeType == ExpressionType.Extension) was added. Such expressions implement IEmittableExpression and have the chance to emit arbitrary IL code. This was needed for supporting a custom ThisExpression (load first argument in instance method).
Clone this wiki locally