dynamicexpresso / DynamicExpresso

C# expressions interpreter
http://dynamic-expresso.azurewebsites.net/
MIT License
1.91k stars 364 forks source link

Can lambda expressions only be used as conditional judgments and return bool? #292

Closed airs1991 closed 12 months ago

airs1991 commented 12 months ago

To implement the mocked foreach, I use an extension method:

public static class Extend
{
     public static void All<T>(this IEnumerable<T> source, Action<T> action)
     {
         foreach (var item in source)
             action(item);
     }
}

I want to change the money field of each npc in List<Npc> NearNpcs to 10, so I wrote a Lambda string:

"NearNpcs.All(n=>n.money=10)"

(The InterpreterOptions.LambdaExpressions setting is already turned on, and the Extend class and associated required variables are registered.)

It reported the error:

DynamicExpresso.Exceptions.ParseException: A value of type 'Int32' cannot be converted to type 'Boolean' (at index 0).

Does Lambda parsing currently only support conditional parsing that can return bool?

metoule commented 12 months ago

Can you please paste the entire code sample? I suspect we only support Func lambdas, but not Action lambdas (ie those with no return type).

davideicardi commented 12 months ago

@airs1991 Maybe the problem is that DS try to call the standard .All function instead of your custom one? https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.all?view=net-7.0

Can you try to use a different name? But yes, having the full code can help us to understand better the problem. If you can I suggest to write it as other unit test...

airs1991 commented 12 months ago

@davideicardi You're right, indeed the All name affects parsing. But when I try to resolve with a custom name, I get a new error: Here is my test code.

public static class Extend
{
    public static void ActionToAll<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
            action(item);
    }
}
public void TestForeach()
    {
        DynamicExpresso.Interpreter interpreter = new(DynamicExpresso.InterpreterOptions.LambdaExpressions);
        List<Npc> testnpcs = new();
        for (int i = 0; i < 5; i++)
            testnpcs.Add(new Npc { money = 0 });
        interpreter.Reference(typeof(Extend));
        interpreter.SetVariable("NearNpcs", testnpcs);
        var func = interpreter.ParseAsDelegate<Action>("NearNpcs.ActionToAll(n=>n.money=10)");
        func.Invoke();
    }

E 0:00:03:0634 :0 @ System.Linq.Expressions.Expression DynamicExpresso.Parsing.Parser.ParseMethodInvocation(System.Type , System.Linq.Expressions.Expression , Int32 , System.String , DynamicExpresso.Parsing.TokenId , System.String , DynamicExpresso.Parsing.TokenId , System.String ): DynamicExpresso.Exceptions.NoApplicableMethodException: No applicable method 'ActionToAll' exists in type 'List`1' (at index 8).

I tried changing the extension method from IEnumerable to List:

public static class Extend
{
    public static void ActionToAll<T>(this List<T> source, Action<T> action)
    {
        foreach (var item in source)
            action(item);
    }
}

But still the same. I don't understand why

interpreter.Reference(typeof(Extend))

doesn't work.

metoule commented 12 months ago

Yes, that's the issue I suspect: we probably find the ActionToAll method, but can't use it because the lambda parser expects the lambda to have a return value. I'll have a look, thanks for posting the code!

metoule commented 12 months ago

I've reproduced with a simpler example that doesn't require registering the extension method (using List.ForEach):

private class Npc
{
    public int Money { get; set; }
}

[Test]
public void Lambda_ShouldAllowActionLambda()
{
    var target = new Interpreter(InterpreterOptions.Default | InterpreterOptions.LambdaExpressions);
    target.EnableAssignment(AssignmentOperators.All);

    var list = new List<Npc>() { new Npc { Money = 10 } };
    target.SetVariable("list", list);

    var result = target.Eval(@"list.ForEach(n => n.Money = 5)");
    Assert.IsNull(result);
    Assert.AreEqual(5, list[0].Money);
}

Exception:

DynamicExpresso.Exceptions.NoApplicableMethodException : No applicable method 'ForEach' exists in type 'List`1' (at index 5).