diff --git a/src/DynamicExpresso.Core/Lambda.cs b/src/DynamicExpresso.Core/Lambda.cs index 183f360f..707f51d7 100644 --- a/src/DynamicExpresso.Core/Lambda.cs +++ b/src/DynamicExpresso.Core/Lambda.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; +using DynamicExpresso.Reflection; namespace DynamicExpresso { @@ -151,14 +152,16 @@ public Expression LambdaExpression() internal LambdaExpression LambdaExpression(Type delegateType) { + var parameterExpressions = DeclaredParameters.Select(p => p.Expression).ToArray(); var types = delegateType.GetGenericArguments(); // return type - types[types.Length - 1] = _expression.Type; + if (delegateType.GetGenericTypeDefinition() == ReflectionExtensions.GetFuncType(parameterExpressions.Length)) + types[types.Length - 1] = _expression.Type; var genericType = delegateType.GetGenericTypeDefinition(); var inferredDelegateType = genericType.MakeGenericType(types); - return Expression.Lambda(inferredDelegateType, _expression, DeclaredParameters.Select(p => p.Expression).ToArray()); + return Expression.Lambda(inferredDelegateType, _expression, parameterExpressions); } } } diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index 292c7209..c75a0271 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -11,6 +11,7 @@ using System.Security; using System.Text; using DynamicExpresso.Exceptions; +using DynamicExpresso.Reflection; using DynamicExpresso.Resources; using Microsoft.CSharp.RuntimeBinder; @@ -2348,9 +2349,6 @@ private static bool CheckIfMethodIsApplicableAndPrepareIt(MethodData method, Exp private static LambdaExpression GenerateLambdaFromInterpreterExpression(InterpreterExpression ie, Type delegateType) { - if (delegateType.GetGenericArguments().Length != ie.Parameters.Count + 1) - return null; - return ie.EvalAs(delegateType); } @@ -3453,7 +3451,7 @@ public InterpreterExpression(ParserArguments parserArguments, string expressionT } // prior to evaluation, we don't know the generic arguments types - _type = typeof(Func<>).Assembly.GetType($"System.Func`{parameters.Length + 1}"); + _type = ReflectionExtensions.GetFuncType(parameters.Length); } public IList Parameters @@ -3468,6 +3466,9 @@ public override Type Type internal LambdaExpression EvalAs(Type delegateType) { + if (!IsCompatibleWithDelegate(delegateType)) + return null; + var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray()); _type = lambdaExpr.Type; return lambdaExpr; @@ -3475,9 +3476,12 @@ internal LambdaExpression EvalAs(Type delegateType) internal bool IsCompatibleWithDelegate(Type target) { - return target.IsGenericType - && target.BaseType == typeof(MulticastDelegate) - && target.GetGenericArguments().Length == _parameters.Count + 1; + if (!target.IsGenericType || target.BaseType != typeof(MulticastDelegate)) + return false; + + var genericTypeDefinition = target.GetGenericTypeDefinition(); + return genericTypeDefinition == ReflectionExtensions.GetFuncType(_parameters.Count) + || genericTypeDefinition == ReflectionExtensions.GetActionType(_parameters.Count); } } diff --git a/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs b/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs index c403a54c..cc93333a 100644 --- a/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs +++ b/src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -56,5 +56,16 @@ public DelegateInfo(Type returnType, Parameter[] parameters) public Type ReturnType { get; private set; } public Parameter[] Parameters { get; private set; } } + + public static Type GetFuncType(int parameterCount) + { + // +1 for the return type + return typeof(Func<>).Assembly.GetType($"System.Func`{parameterCount + 1}"); + } + + public static Type GetActionType(int parameterCount) + { + return typeof(Action<>).Assembly.GetType($"System.Action`{parameterCount}"); + } } } diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index 9a4b4468..37fee024 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -663,7 +663,7 @@ public class PageType public string PageName { get; set; } public int VisualCount { get; set; } } - + [Test] public void GitHub_Issue_261() { @@ -749,10 +749,38 @@ public void GitHub_Issue_287() Assert.AreEqual(str is IEnumerable[][], interpreter.Eval("(str is IEnumerable[][])")); Assert.AreEqual(str is IEnumerable[][], interpreter.Eval("(str is IEnumerable[][])")); } + + private class Npc + { + public int money { get; set; } + } + + [Test] + public void GitHub_Issue_292() + { + var interpreter = new Interpreter(InterpreterOptions.LambdaExpressions); + + var testnpcs = new List(); + for (var i = 0; i < 5; i++) + testnpcs.Add(new Npc { money = 0 }); + + interpreter.Reference(typeof(GithubIssuesTestExtensionsMethods)); + interpreter.SetVariable("NearNpcs", testnpcs); + + var func = interpreter.ParseAsDelegate("NearNpcs.ActionToAll(n => n.money = 10)"); + func.Invoke(); + + Assert.IsTrue(testnpcs.All(n => n.money == 10)); + } } internal static class GithubIssuesTestExtensionsMethods { public static bool IsInFuture(this DateTimeOffset date) => date > DateTimeOffset.UtcNow; + public static void ActionToAll(this IEnumerable source, Action action) + { + foreach (var item in source) + action(item); + } } } diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs index 1a5c79da..0f86135b 100644 --- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs +++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs @@ -421,6 +421,41 @@ public void Lambda_CannotUseDuplicateParameterInSubLambda() )))", new Parameter("root", typeof(NestedLambdaTestClass)))); } + private class Npc + { + public int Money { get; set; } + public void AddMoney(Action action) + { + action(this, 10, "test"); + } + } + + [Test] + public void Lambda_ShouldAllowActionLambda() + { + var target = new Interpreter(InterpreterOptions.LambdaExpressions); + + var list = new List() { 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); + } + + [Test] + public void Lambda_MultipleActionLambdaParameters() + { + var target = new Interpreter(InterpreterOptions.LambdaExpressions); + + var npc = new Npc { Money = 10 }; + target.SetVariable("npc", npc); + + var result = target.Eval(@"npc.AddMoney((n, i, str) => n.Money = i + str.Length)"); + Assert.IsNull(result); + Assert.AreEqual(14, npc.Money); + } + private static NestedLambdaTestClass BuildNestedTestClassHierarchy() { return new NestedLambdaTestClass()