dynamicexpresso / DynamicExpresso

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

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

Closed airs1991 closed 1 year ago

airs1991 commented 1 year 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 1 year 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 1 year 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 1 year 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 1 year 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 1 year 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).