dadhi/FastExpressionCompiler

An expression with a single parameter concatenated to a string causes 'Exception has been thrown by the target of an invocation' during delegate invocation.

Rotabor opened this issue · 3 comments

An expression with a single parameter concatenated to a string causes 'Exception has been thrown by the target of an invocation' during delegate invocation while the same expression compiled by 'System.Linq.Expressions.LambdaExpression.Compile' is invoked successfully.
An expression with two parameters, where the result of an operation with them is concatenated to a string, doesn't cause such error.
The sample code below uses DynamicExpresso (NuGet 'DynamicExpresso.Core') library. It's used to produce the required expression. At the same time, it seems to be not related to the issue.

// .Net 8.0
using System.Linq.Expressions;
using FastExpressionCompiler;
using DynamicExpresso;

namespace ConsoleApp3;

internal class Program {

    internal static LambdaExpression GetLambdaExpression(Expression expression, Type delegateType, Parameter[] parameters) {
        Type[] genericArguments = delegateType.GetGenericArguments();
        genericArguments[^1] = expression.Type;
        return Expression.Lambda(delegateType.GetGenericTypeDefinition().MakeGenericType(genericArguments), expression, parameters.Select((Parameter p) => p.Expression).ToArray());
    }

    static void Main() {
        Interpreter interpreter = new();

        Console.WriteLine("Expression with a single parameter");
        Console.WriteLine(new String('-', 80));
        var exprns1 = "\"The value is \" + erty";
        Parameter[] parameters1 = [new Parameter("erty", Activator.CreateInstance(Type.GetType("System.Double")))];
        var expression1 = interpreter.Parse(exprns1, parameters1).Expression;
        Console.WriteLine("Expression --> " + expression1);
        var delegateType1 = Expression.GetFuncType(typeof(double), typeof(string));
        var le1 = GetLambdaExpression(expression1, delegateType1, parameters1);
        Console.WriteLine("Lambda xpression --> " + le1);
        Console.WriteLine(new String('-', 80));
        Delegate dlg1 = le1.CompileFast();
        string? res1 ="";
        Console.WriteLine("CompileFast");
        try { res1 = (string)dlg1.DynamicInvoke(3.14); } catch (Exception ex) { Console.WriteLine(ex.Message); }
        Console.WriteLine(res1);
        Console.WriteLine(new String('-', 80));

        Console.WriteLine("Compile");
        dlg1 = le1.Compile();
        res1 = "";
        try { res1 = (string)dlg1.DynamicInvoke(3.14); } catch (Exception ex) { Console.WriteLine(ex.Message); }
        Console.WriteLine(res1);
        Console.WriteLine(new String('-', 80));
        Console.WriteLine("\r\n");


        Console.WriteLine("Expression with two parameters");
        Console.WriteLine(new String('-', 80));
        var exprns2 = "\"The value is \" + (erty + dfgh)";
        Parameter[] parameters2 = [
            new Parameter("erty", Activator.CreateInstance(Type.GetType("System.Double"))),
            new Parameter("dfgh", Activator.CreateInstance(Type.GetType("System.Double"))),
        ];
        var expression2 = interpreter.Parse(exprns2, parameters2).Expression;
        Console.WriteLine("Expression --> " + expression2);
        var delegateType2 = Expression.GetFuncType(typeof(double), typeof(double), typeof(string));
        var le2 = GetLambdaExpression(expression2, delegateType2, parameters2);
        Console.WriteLine("Lambda xpression --> " + le2);
        Console.WriteLine(new String('-', 80));
        Delegate dlg2 = le2.CompileFast();
        string? res2 = "";
        Console.WriteLine("CompileFast");
        try { res2 = (string)dlg2.DynamicInvoke(3.1415, 2.7183); } catch (Exception ex) { Console.WriteLine(ex.Message); }
        Console.WriteLine(res2);
        Console.WriteLine(new String('-', 80));
    }
}

Result:

Expression with a single parameter
--------------------------------------------------------------------------------
Expression --> Concat("The value is ", IIF((ConvertChecked(erty, Nullable`1) == null), null, erty.ToString()))
Lambda xpression --> erty => Concat("The value is ", IIF((ConvertChecked(erty, Nullable`1) == null), null, erty.ToString()))
--------------------------------------------------------------------------------
CompileFast
Exception has been thrown by the target of an invocation.

--------------------------------------------------------------------------------
Compile
The value is 3,14
--------------------------------------------------------------------------------


Expression with two parameters
--------------------------------------------------------------------------------
Expression --> Concat("The value is ", IIF((ConvertChecked((erty + dfgh), Nullable`1) == null), null, (erty + dfgh).ToString()))
Lambda xpression --> (erty, dfgh) => Concat("The value is ", IIF((ConvertChecked((erty + dfgh), Nullable`1) == null), null, (erty + dfgh).ToString()))
--------------------------------------------------------------------------------
CompileFast
The value is 5,8598
--------------------------------------------------------------------------------

@Rotabor Hi, thanks for the finding.
Could you reproduce the issue without DynamicExpresso, I would like to have a Unit Test similar to this: test/FastExpressionCompiler.IssueTests/Issue355_Error_with_converting_to_from_signed_unsigned_integers.cs

using NUnit.Framework;

#if LIGHT_EXPRESSION
using System.Linq.Expressions;
using FastExpressionCompiler.LightExpression;
using static FastExpressionCompiler.LightExpression.Expression;
namespace FastExpressionCompiler.LightExpression.IssueTests
#else
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
namespace FastExpressionCompiler.IssueTests
#endif
{
    [TestFixture]
    public class Issue404_String_plus_parameter_causes_Exception_in_target_invocation : ITest {
        public int Run() {
            Test1();
            return 1;
        }

        [Test]
        public void Test1() {
            var param = Parameter(typeof(double));
            Expression sumExpr = Call(
                typeof(string).GetMethod("Concat", [typeof(object), typeof(object)]),
                Constant("The value is "),
                Condition(
                    Equal(ConvertChecked(param, typeof(double?)), Constant(null)),
                    Constant(""),
                    Call(param, typeof(double).GetMethod("ToString", Type.EmptyTypes))
                )
            );
            var le2 = Lambda<Func<double, string>>(sumExpr, [param]);
            Delegate dlg2 = le2.CompileFast();
            string? res2 = "";
            double x = 1.6;
            res2 = (string)dlg2.DynamicInvoke(x);

            Assert.AreEqual(res2, "The value is " + x.ToString());
        }
    }
}

I have added the test, and it's passing just fine in the latest source. So I would consider it's fixed.