Skip to content

Commit

Permalink
Add support for Action<> lambdas (#293)
Browse files Browse the repository at this point in the history
Fix #292
  • Loading branch information
metoule committed Jul 14, 2023
1 parent f919582 commit a199778
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 11 deletions.
7 changes: 5 additions & 2 deletions src/DynamicExpresso.Core/Lambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;
using DynamicExpresso.Reflection;

namespace DynamicExpresso
{
Expand Down Expand Up @@ -151,14 +152,16 @@ public Expression<TDelegate> LambdaExpression<TDelegate>()

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);
}
}
}
18 changes: 11 additions & 7 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Security;
using System.Text;
using DynamicExpresso.Exceptions;
using DynamicExpresso.Reflection;
using DynamicExpresso.Resources;
using Microsoft.CSharp.RuntimeBinder;

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<Parameter> Parameters
Expand All @@ -3468,16 +3466,22 @@ 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;
}

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);
}
}

Expand Down
13 changes: 12 additions & 1 deletion src/DynamicExpresso.Core/Reflection/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -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}");
}
}
}
30 changes: 29 additions & 1 deletion test/DynamicExpresso.UnitTest/GithubIssues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ public class PageType
public string PageName { get; set; }
public int VisualCount { get; set; }
}

[Test]
public void GitHub_Issue_261()
{
Expand Down Expand Up @@ -749,10 +749,38 @@ public void GitHub_Issue_287()
Assert.AreEqual(str is IEnumerable<int[]>[][], interpreter.Eval("(str is IEnumerable<int[]>[][])"));
Assert.AreEqual(str is IEnumerable<int?[][]>[][], interpreter.Eval("(str is IEnumerable<int?[][]>[][])"));
}

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

[Test]
public void GitHub_Issue_292()
{
var interpreter = new Interpreter(InterpreterOptions.LambdaExpressions);

var testnpcs = new List<Npc>();
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<Action>("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<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
action(item);
}
}
}
35 changes: 35 additions & 0 deletions test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Npc, int, string> action)
{
action(this, 10, "test");
}
}

[Test]
public void Lambda_ShouldAllowActionLambda()
{
var target = new Interpreter(InterpreterOptions.LambdaExpressions);

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);
}

[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()
Expand Down

0 comments on commit a199778

Please sign in to comment.