Search code examples
c#linqexpressionexpression-treescil

CompileToMethod cannot compile constant '<some value>' because it is a non-trivial value, such as a live object


I'm trying to create a type with Reflection.Emit with a method named EvaluateOnCondition. I generate the method's body with Linq Expressions, and I want to inject the IL of the expression into EvaluateOnCondition with the CompileToMethod method of LambdaExpression, but when I execute the CompileToMethod method I'm getting the following error:

CompileToMethod cannot compile constant 'some value' because it is a non-trivial value, such as a live object. Instead, create an expression tree that can construct this value.

This is what I'm trying to do:

MethodBuilder evaluateOnCondition = tb.DefineMethod("EvaluateOnCondition", MethodAttributes.Public | MethodAttributes.Static, typeof(bool), new[] { typeof(object), typeof(object) });
onCondition.CompileToMethod(evaluateOnCondition); // throw the error

onCondition variable is a LambdaExpression and is a comparative condition created with Linq Expressions representing the following pseudo-code: obj1.prop1 == obj2.prop1

This is my StackTrace:

   at System.Linq.Expressions.Compiler.BoundConstants.EmitCacheConstants(LambdaCompiler lc)
   at System.Linq.Expressions.Compiler.LambdaCompiler..ctor(AnalyzedTree tree, LambdaExpression lambda, MethodBuilder method)
   at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, MethodBuilder method, DebugInfoGenerator debugInfoGenerator)
   at System.Linq.Expressions.LambdaExpression.CompileToMethodInternal(MethodBuilder method, DebugInfoGenerator debugInfoGenerator)
   at System.Linq.Expressions.LambdaExpression.CompileToMethod(MethodBuilder method)
   at Integra.Space.Language.Runtime.LanguageTypeBuilder.CreateEqualsMethod(TypeBuilder tb, Type parentType, Type typeOtherSource, Boolean isSecondSource, LambdaExpression onCondition) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\TypeBuilders\LanguageTypeBuilder.cs:line 343
   at Integra.Space.Language.Runtime.LanguageTypeBuilder.CreateTypeBuilder(List`1 listOfFields, Type parentType, Boolean overrideGetHashCodeMethod, Boolean overrideEquals, Type typeOtherSource, Boolean isSecondSource, LambdaExpression onCondition) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\TypeBuilders\LanguageTypeBuilder.cs:line 165
   at Integra.Space.Language.Runtime.LanguageTypeBuilder.CompileExtractedEventDataComparerTypeForJoin(Type parentType, Type typeOfTheOtherSource, Boolean isSecondSource, LambdaExpression onCondition) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\TypeBuilders\LanguageTypeBuilder.cs:line 92
   at Integra.Space.Language.Runtime.ObservableConstructor.CreateProjectionExpression(PlanNode plans) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 3033
   at Integra.Space.Language.Runtime.ObservableConstructor.CreateExpressionNode(PlanNode actualNode, Expression leftNode, Expression rightNode) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 358
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 304
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 313
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 299
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 299
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 313
   at Integra.Space.Language.Runtime.ObservableConstructor.GenerateExpressionTree(PlanNode plan) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 313
   at Integra.Space.Language.Runtime.ObservableConstructor.CreateObservableJoin(PlanNode actualNode) in C:\Users\Oscar\Documents\GitHubVisualStudio\Integra.Space.Language\Integra.Space.Language\Runtime\ObservableConstructor.cs:line 936

Debug view of the onCondition variable

.Block(System.Boolean $variable) {
    .Try {
        .Block() {
            .If (
                True
            ) {
                .Call System.Diagnostics.Debug.WriteLine("Start of the equal operation '==': ")
            } .Else {
                .Default(System.Void)
            };
            $variable = (System.Object).Block(System.Object $variable) {
                .If (.Constant<System.Reflection.RuntimePropertyInfo>(System.Object _adapter_Name) == null) { // 12 line
                    $variable = .Default(System.Object)
                } .Else {
                    .Try {
                        .Block() {
                            .If (
                                True
                            ) {
                                .Call System.Diagnostics.Debug.WriteLine("Start of the get property operation: _adapter_Name")
                            } .Else {
                                .Default(System.Void)
                            };
                            $variable = $NewScopeParameter_0._adapter_Name;
                            .If (
                                True
                            ) {
                                .Call System.Diagnostics.Debug.WriteLine("End of the get property operation: _adapter_Name")
                            } .Else {
                                .Default(System.Void)
                            };
                            .Default(System.Void)
                        }
                    } .Catch (System.Exception $var1) {
                        .Block() {
                            .Call System.Diagnostics.Debug.WriteLine("No fue posible obtener la propiedad _adapter_Name, error en la linea: 0 columna: 162 con [email protected]")
                            ;
                            .Throw .New Integra.Space.Language.Exceptions.RuntimeException(
                                "RuntimeException: Line: 0, Column: 162, Instruction: [email protected], Error: RE5: Error with the get property operation",
                                $var1)
                        }
                    }
                };
                $variable
            } == (System.Object).Block(System.Object $variable) {
                .If (.Constant<System.Reflection.RuntimePropertyInfo>(System.Object _adapter_Name) == null) {
                    $variable = .Default(System.Object)
                } .Else {
                    .Try {
                        .Block() {
                            .If (
                                True
                            ) {
                                .Call System.Diagnostics.Debug.WriteLine("Start of the get property operation: _adapter_Name")
                            } .Else {
                                .Default(System.Void)
                            };
                            $variable = $NewScopeParameter_1._adapter_Name;
                            .If (
                                True
                            ) {
                                .Call System.Diagnostics.Debug.WriteLine("End of the get property operation: _adapter_Name")
                            } .Else {
                                .Default(System.Void)
                            };
                            .Default(System.Void)
                        }
                    } .Catch (System.Exception $var2) {
                        .Block() {
                            .Call System.Diagnostics.Debug.WriteLine("No fue posible obtener la propiedad _adapter_Name, error en la linea: 0 columna: 188 con [email protected]")
                            ;
                            .Throw .New Integra.Space.Language.Exceptions.RuntimeException(
                                "RuntimeException: Line: 0, Column: 188, Instruction: [email protected], Error: RE5: Error with the get property operation",
                                $var2)
                        }
                    }
                };
                $variable
            };
            .If (
                True
            ) {
                .Call System.Diagnostics.Debug.WriteLine("End of the equal operation")
            } .Else {
                .Default(System.Void)
            };
            .Default(System.Void)
        }
    } .Catch (System.Exception $var3) {
        .Block() {
            .Call System.Diagnostics.Debug.WriteLine("Error con la expresion de igualdad en la linea: 0 columna: 167 con [email protected] == [email protected]")
            ;
            .Throw .New Integra.Space.Language.Exceptions.RuntimeException(
                "RuntimeException: Line: 0, Column: 167, Instruction: [email protected] == [email protected], Error: RE43: Error with the equal operation '=='",
                $var3)
        }
    };
    $variable
}

I suspect the error is thrown in this line of the core.

BoundConstants line 133

BTW: What is non-trivial value in C#? How could I fix this?

Tell me if you need more information


Solution

  • What is non-trivial value in C#? How could I fix this?*

    In general null, bool, char, sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, string, Type, MethodBase are ok. Everything else isn't.

    You can see it by starting from VariableBinder.VisitConstant:

    if (ILGen.CanEmitConstant(node.Value, node.Type))
    {
        return node;
    }
    this._constants.Peek().AddReference(node.Value, node.Type);
    

    Going to the AddReference is bad, and will cause in the long run the exception you saw.

    Then you can look at ILGen.CanEmitConstant and see the tests that are done.

    Why this happens? Because you are trying to compile inside a method an object. While this is normally possible if you use the LambdaExpression.Compile(), because the object isn't really saved inside the method but is simply a reference to the object that is living in memory, if you use CompileToMethod() the .NET must be able to save a copy of the object inside the method, because CompileToMethod() (and MethodBuilder/TypeBuilder/AssemblyBuilder) are able to generate new dlls. These dlls can be shared with other machines/loaded days later (they are "normal" dlls), so they aren't guaranteed to be run only during the lifetime of the current program. To do this the .NET would need to serialize the object and then deserialize it when the method is called. But this isn't supported. Clearly this doesn't happen with some primitive types (and I'll note that not all the .NET primitive types are supported. Missing are IntPtr and UIntPtr) and with some special types like Type (that have a special handling inside the .NET).