Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Action<> lambdas #293

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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