diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs
index b8fdcff0..445fe777 100644
--- a/src/DynamicExpresso.Core/Interpreter.cs
+++ b/src/DynamicExpresso.Core/Interpreter.cs
@@ -60,6 +60,11 @@ public Interpreter(InterpreterOptions options)
_settings.LambdaExpressions = true;
}
+ if ((options & InterpreterOptions.DetectUsedParameters) == InterpreterOptions.DetectUsedParameters)
+ {
+ _settings.DetectUsedParameters = true;
+ }
+
_visitors.Add(new DisableReflectionVisitor());
}
@@ -366,7 +371,7 @@ public Lambda Parse(string expressionText, params Parameter[] parameters)
///
public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters)
{
- return ParseAsLambda(expressionText, expressionType, parameters);
+ return ParseRawExpression(expressionText, expressionType, parameters).ToLambda();
}
[Obsolete("Use ParseAsDelegate(string, params string[])")]
@@ -385,8 +390,8 @@ public TDelegate Parse(string expressionText, params string[] paramet
///
public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames)
{
- var lambda = ParseAs(expressionText, parametersNames);
- return lambda.Compile();
+ var lambda = ParseRawExpression(expressionText, parametersNames);
+ return lambda.Compile();
}
///
@@ -399,35 +404,14 @@ public TDelegate ParseAsDelegate(string expressionText, params string
///
public Expression ParseAsExpression(string expressionText, params string[] parametersNames)
{
- var lambda = ParseAs(expressionText, parametersNames);
- return lambda.LambdaExpression();
- }
-
- internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames)
- {
- var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);
-
- // return type is object means that we have no information beforehand
- // => we force it to typeof(void) so that no conversion expression is emitted by the parser
- // and the actual expression type is preserved
- var returnType = delegateInfo.ReturnType;
- if (returnType == typeof(object))
- returnType = typeof(void);
-
- var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters);
- return lambda.LambdaExpression(delegateType);
+ var lambda = ParseRawExpression(expressionText, parametersNames);
+ return lambda.LambdaExpression();
}
public Lambda ParseAs(string expressionText, params string[] parametersNames)
{
- return ParseAs(typeof(TDelegate), expressionText, parametersNames);
- }
-
- internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames)
- {
- var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);
-
- return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
+ var lambda = ParseRawExpression(expressionText, parametersNames);
+ return lambda.ToLambda();
}
#endregion
@@ -478,7 +462,14 @@ public IdentifiersInfo DetectIdentifiers(string expression)
#region Private methods
- private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters)
+ internal ParseResult ParseRawExpression(string expressionText, params string[] parametersNames)
+ {
+ var delegateInfo = ReflectionExtensions.GetDelegateInfo(typeof(TDelegate), parametersNames);
+ var parseResult = ParseRawExpression(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
+ return new ParseResult(parseResult);
+ }
+
+ internal ParseResult ParseRawExpression(string expressionText, Type expressionType, Parameter[] parameters)
{
var arguments = new ParserArguments(
expressionText,
@@ -491,7 +482,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet
foreach (var visitor in Visitors)
expression = visitor.Visit(expression);
- var lambda = new Lambda(expression, arguments);
+ var lambda = new ParseResult(expression, arguments);
#if TEST_DetectIdentifiers
AssertDetectIdentifiers(lambda);
@@ -501,7 +492,7 @@ private Lambda ParseAsLambda(string expressionText, Type expressionType, Paramet
}
#if TEST_DetectIdentifiers
- private void AssertDetectIdentifiers(Lambda lambda)
+ private void AssertDetectIdentifiers(ParseResult lambda)
{
var info = DetectIdentifiers(lambda.ExpressionText);
diff --git a/src/DynamicExpresso.Core/InterpreterExtensions.cs b/src/DynamicExpresso.Core/InterpreterExtensions.cs
new file mode 100644
index 00000000..31ca8353
--- /dev/null
+++ b/src/DynamicExpresso.Core/InterpreterExtensions.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace DynamicExpresso
+{
+ ///
+ /// Interpreter extensions.
+ ///
+ public static class InterpreterExtensions
+ {
+ ///
+ /// Compiles lambda with used parameters.
+ ///
+ public static Delegate Compile(this ParseResult parseResult)
+ {
+ return parseResult.LambdaExpression().Compile();
+ }
+
+ ///
+ /// Compiles lambda with declared parameters.
+ ///
+ public static TDelegate Compile(this ParseResult parseResult)
+ {
+ return parseResult.LambdaExpression().Compile();
+ }
+
+ ///
+ /// Compiles lambda with declared parameters.
+ ///
+ public static TDelegate Compile(this ParseResult parseResult)
+ {
+ return Compile((ParseResult)parseResult);
+ }
+
+ ///
+ /// Convert parse result to a lambda expression with used parameters.
+ ///
+ public static LambdaExpression LambdaExpression(this ParseResult parseResult)
+ {
+ return Expression.Lambda(parseResult.Expression, parseResult.UsedParameters.Select(_ => _.Expression).ToArray());
+ }
+
+ ///
+ /// Convert parse result to a lambda expression with declared parameters.
+ ///
+ public static Expression LambdaExpression(this ParseResult parseResult)
+ {
+ return Expression.Lambda(parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray());
+ }
+
+ ///
+ /// Convert parse result to a lambda expression with declared parameters.
+ ///
+ public static Expression LambdaExpression(this ParseResult parseResult)
+ {
+ return ((ParseResult)parseResult).LambdaExpression();
+ }
+
+ ///
+ /// Convert parse result to a lambda expression with declared parameters.
+ ///
+ public static LambdaExpression LambdaExpression(this ParseResult parseResult, Type delegateType)
+ {
+ return Expression.Lambda(delegateType, parseResult.Expression, parseResult.DeclaredParameters.Select(_ => _.Expression).ToArray());
+ }
+ }
+}
diff --git a/src/DynamicExpresso.Core/InterpreterOptions.cs b/src/DynamicExpresso.Core/InterpreterOptions.cs
index 5ed789f3..bd950aaf 100644
--- a/src/DynamicExpresso.Core/InterpreterOptions.cs
+++ b/src/DynamicExpresso.Core/InterpreterOptions.cs
@@ -31,12 +31,16 @@ public enum InterpreterOptions
///
LambdaExpressions = 32,
///
- /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes
+ /// Detect which parameters are actually used in the expression, to minimise the compiled lambda signature.
///
- Default = PrimitiveTypes | SystemKeywords | CommonTypes,
+ DetectUsedParameters = 64,
///
- /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + CaseInsensitive
+ /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters
///
- DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | CaseInsensitive,
+ Default = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters,
+ ///
+ /// Load all default configurations: PrimitiveTypes + SystemKeywords + CommonTypes + DetectUsedParameters + CaseInsensitive
+ ///
+ DefaultCaseInsensitive = PrimitiveTypes | SystemKeywords | CommonTypes | DetectUsedParameters | CaseInsensitive,
}
}
diff --git a/src/DynamicExpresso.Core/Lambda.cs b/src/DynamicExpresso.Core/Lambda.cs
index e89edbb8..38e8ccd2 100644
--- a/src/DynamicExpresso.Core/Lambda.cs
+++ b/src/DynamicExpresso.Core/Lambda.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@@ -10,47 +10,20 @@ namespace DynamicExpresso
///
/// Represents a lambda expression that can be invoked. This class is thread safe.
///
- public class Lambda
+ public class Lambda : ParseResult
{
- private readonly Expression _expression;
- private readonly ParserArguments _parserArguments;
+ private readonly bool _caseInsensitive;
+ private readonly StringComparison _keyComparison;
private readonly Lazy _delegate;
- internal Lambda(Expression expression, ParserArguments parserArguments)
+ internal Lambda(Expression expression, ParserArguments parserArguments) : base(expression, parserArguments)
{
- _expression = expression ?? throw new ArgumentNullException(nameof(expression));
- _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));
-
- // Note: I always lazy compile the generic lambda. Maybe in the future this can be a setting because if I generate a typed delegate this compilation is not required.
- _delegate = new Lazy(() =>
- Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()).Compile());
+ _caseInsensitive = parserArguments.Settings.CaseInsensitive;
+ _keyComparison = parserArguments.Settings.KeyComparison;
+ _delegate = new Lazy(() => this.Compile());
}
- public Expression Expression { get { return _expression; } }
- public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } }
- public string ExpressionText { get { return _parserArguments.ExpressionText; } }
- public Type ReturnType { get { return Expression.Type; } }
-
- ///
- /// Gets the parameters actually used in the expression parsed.
- ///
- /// The used parameters.
- [Obsolete("Use UsedParameters or DeclaredParameters")]
- public IEnumerable Parameters { get { return _parserArguments.UsedParameters; } }
-
- ///
- /// Gets the parameters actually used in the expression parsed.
- ///
- /// The used parameters.
- public IEnumerable UsedParameters { get { return _parserArguments.UsedParameters; } }
- ///
- /// Gets the parameters declared when parsing the expression.
- ///
- /// The declared parameters.
- public IEnumerable DeclaredParameters { get { return _parserArguments.DeclaredParameters; } }
-
- public IEnumerable Types { get { return _parserArguments.UsedTypes; } }
- public IEnumerable Identifiers { get { return _parserArguments.UsedIdentifiers; } }
+ public bool CaseInsensitive => _caseInsensitive;
public object Invoke()
{
@@ -66,7 +39,7 @@ public object Invoke(IEnumerable parameters)
{
var args = (from usedParameter in UsedParameters
from actualParameter in parameters
- where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison)
+ where usedParameter.Name.Equals(actualParameter.Name, _keyComparison)
select actualParameter.Value)
.ToArray();
@@ -116,49 +89,5 @@ private object InvokeWithUsedParameters(object[] orderedArgs)
throw;
}
}
-
- public override string ToString()
- {
- return ExpressionText;
- }
-
- ///
- /// Generate the given delegate by compiling the lambda expression.
- ///
- /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
- public TDelegate Compile()
- {
- var lambdaExpression = LambdaExpression();
- return lambdaExpression.Compile();
- }
-
- [Obsolete("Use Compile()")]
- public TDelegate Compile(IEnumerable parameters)
- {
- var lambdaExpression = Expression.Lambda(_expression, parameters.Select(p => p.Expression).ToArray());
- return lambdaExpression.Compile();
- }
-
- ///
- /// Generate a lambda expression.
- ///
- /// The lambda expression.
- /// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
- public Expression LambdaExpression()
- {
- return Expression.Lambda(_expression, DeclaredParameters.Select(p => p.Expression).ToArray());
- }
-
- internal LambdaExpression LambdaExpression(Type delegateType)
- {
- var types = delegateType.GetGenericArguments();
-
- // return type
- 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());
- }
}
}
diff --git a/src/DynamicExpresso.Core/ParseResult.cs b/src/DynamicExpresso.Core/ParseResult.cs
new file mode 100644
index 00000000..818a4fb7
--- /dev/null
+++ b/src/DynamicExpresso.Core/ParseResult.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace DynamicExpresso
+{
+ ///
+ /// Represents an expression parse result.
+ ///
+ public class ParseResult
+ {
+ private readonly Expression _expression;
+ private readonly ParserArguments _parserArguments;
+
+ internal ParseResult(Expression expression, ParserArguments parserArguments)
+ {
+ _expression = expression ?? throw new ArgumentNullException(nameof(expression));
+ _parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));
+ }
+
+ internal ParseResult(ParseResult other) : this(other._expression, other._parserArguments)
+ {
+ }
+
+ public string ExpressionText => _parserArguments.ExpressionText;
+ public virtual Type ReturnType => _expression.Type;
+
+ ///
+ /// Gets the parsed expression.
+ ///
+ /// The expression.
+ public Expression Expression => _expression;
+
+ ///
+ /// Gets the parameters actually used in the expression parsed.
+ ///
+ /// The used parameters.
+ [Obsolete("Use UsedParameters or DeclaredParameters")]
+ public IEnumerable Parameters => UsedParameters;
+
+ ///
+ /// Gets the parameters actually used in the expression parsed.
+ ///
+ /// The used parameters.
+ public IEnumerable UsedParameters => _parserArguments.UsedParameters;
+
+ ///
+ /// Gets the parameters declared when parsing the expression.
+ ///
+ /// The declared parameters.
+ public IEnumerable DeclaredParameters => _parserArguments.DeclaredParameters;
+
+ ///
+ /// Gets the references types in parsed expression.
+ ///
+ /// The references types.
+ public IEnumerable Types => _parserArguments.UsedTypes;
+
+ ///
+ /// Gets the identifiers in parsed expression.
+ ///
+ /// The identifiers.
+ public IEnumerable Identifiers => _parserArguments.UsedIdentifiers;
+
+ internal Lambda ToLambda()
+ {
+ return new Lambda(_expression, _parserArguments);
+ }
+
+ public override string ToString()
+ {
+ return ExpressionText;
+ }
+ }
+
+ public class ParseResult : ParseResult
+ {
+ internal ParseResult(ParseResult parseResult) : base(parseResult)
+ {
+ }
+ }
+}
diff --git a/src/DynamicExpresso.Core/ParserArguments.cs b/src/DynamicExpresso.Core/ParserArguments.cs
index 0f5d8649..1a1ba384 100644
--- a/src/DynamicExpresso.Core/ParserArguments.cs
+++ b/src/DynamicExpresso.Core/ParserArguments.cs
@@ -39,6 +39,9 @@ IEnumerable declaredParameters
throw new DuplicateParameterException(pe.Name);
}
}
+
+ if (!Settings.DetectUsedParameters)
+ _usedParameters = new HashSet(_declaredParameters.Values);
}
public ParserSettings Settings { get; private set;}
diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs
index a2fffa6c..ae6bb1ea 100644
--- a/src/DynamicExpresso.Core/Parsing/Parser.cs
+++ b/src/DynamicExpresso.Core/Parsing/Parser.cs
@@ -8,6 +8,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using DynamicExpresso.Exceptions;
+using DynamicExpresso.Reflection;
using DynamicExpresso.Resources;
using Microsoft.CSharp.RuntimeBinder;
@@ -3129,7 +3130,26 @@ public override Type Type
internal LambdaExpression EvalAs(Type delegateType)
{
- var lambdaExpr = _interpreter.ParseAsExpression(delegateType, _expressionText, _parameters.Select(p => p.Name).ToArray());
+ var parametersNames = _parameters.Select(p => p.Name).ToArray();
+ var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);
+
+ // return type is object means that we have no information beforehand
+ // => we force it to typeof(void) so that no conversion expression is emitted by the parser
+ // and the actual expression type is preserved
+ var returnType = delegateInfo.ReturnType;
+ if (returnType == typeof(object))
+ returnType = typeof(void);
+
+ var lambda = _interpreter.ParseRawExpression(_expressionText, returnType, delegateInfo.Parameters);
+
+ // change delegate return type to the actual type inferred during parsing
+ var types = delegateType.GetGenericArguments();
+ types[types.Length - 1] = lambda.Expression.Type;
+
+ var genericType = delegateType.GetGenericTypeDefinition();
+ var inferredDelegateType = genericType.MakeGenericType(types);
+ var lambdaExpr = Lambda(inferredDelegateType, lambda.Expression, lambda.DeclaredParameters.Select(p => p.Expression).ToArray());
+
_type = lambdaExpr.Type;
return lambdaExpr;
}
diff --git a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs
index 01e0b0c6..709a659b 100644
--- a/src/DynamicExpresso.Core/Parsing/ParserSettings.cs
+++ b/src/DynamicExpresso.Core/Parsing/ParserSettings.cs
@@ -31,6 +31,8 @@ public ParserSettings(bool caseInsensitive, bool lateBindObject)
DefaultNumberType = DefaultNumberType.Default;
LambdaExpressions = false;
+
+ DetectUsedParameters = false;
}
private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other.LateBindObject)
@@ -42,6 +44,7 @@ private ParserSettings(ParserSettings other) : this(other.CaseInsensitive, other
AssignmentOperators = other.AssignmentOperators;
DefaultNumberType = other.DefaultNumberType;
LambdaExpressions = other.LambdaExpressions;
+ DetectUsedParameters = other.DetectUsedParameters;
}
///
@@ -105,5 +108,11 @@ public bool LambdaExpressions
get;
set;
}
+
+ public bool DetectUsedParameters
+ {
+ get;
+ set;
+ }
}
}
diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs
index ba7e65d1..de194441 100644
--- a/test/DynamicExpresso.UnitTest/GithubIssues.cs
+++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs
@@ -446,6 +446,21 @@ public void GitHub_Issue_205()
Assert.AreEqual(-1, interpreter.Eval("(date1 - date2)?.Days"));
}
+ [Test]
+ public void GitHub_Issue_207()
+ {
+ var interpreter = new Interpreter(InterpreterOptions.Default ^ InterpreterOptions.DetectUsedParameters);
+ var parameter = new Parameter("x", typeof(int));
+ var expression = interpreter.Parse("x + 1", parameter).Expression;
+
+ var lambda = interpreter
+ .SetExpression("value", expression)
+ .Parse("value + 1", parameter);
+
+ var result = lambda.Invoke(1);
+ Assert.AreEqual(3, result);
+ }
+
[Test]
public void GitHub_Issue_217()
{
diff --git a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs
index ba021abb..5ae41ef6 100644
--- a/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs
+++ b/test/DynamicExpresso.UnitTest/LambdaExpressionTest.cs
@@ -231,6 +231,18 @@ public void Parent_scope_variable()
Assert.AreEqual(new[] { 4, 5, 6 }, results);
}
+ [Test]
+ public void Unused_lambda_parameter()
+ {
+ var target = new Interpreter(_options);
+ var list = new List { 1, 2, 3 };
+ target.SetVariable("myList", list);
+ var results = target.Eval>("myList.Select(i => 4)");
+
+ Assert.AreEqual(3, results.Count());
+ Assert.AreEqual(new[] { 4, 4, 4 }, results);
+ }
+
[Test]
public void Lambda_with_multiple_params()
{
diff --git a/test/DynamicExpresso.UnitTest/NullableTest.cs b/test/DynamicExpresso.UnitTest/NullableTest.cs
index 7348f827..fe885d51 100644
--- a/test/DynamicExpresso.UnitTest/NullableTest.cs
+++ b/test/DynamicExpresso.UnitTest/NullableTest.cs
@@ -241,45 +241,24 @@ public void NullableDateTimeOffset_DatetimeOffset()
interpreter.SetVariable("c", c, typeof(DateTimeOffset));
var expectedReturnType = typeof(bool);
- var expected = a < b;
- var lambda = interpreter.Parse("a < b");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- expected = a > b;
- lambda = interpreter.Parse("a > b");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- expected = a == b;
- lambda = interpreter.Parse("a == b");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- expected = a != b;
- lambda = interpreter.Parse("a != b");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- expected = b == c;
- lambda = interpreter.Parse("b == b");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- expected = b != c;
- lambda = interpreter.Parse("b != c");
- Assert.AreEqual(expected, lambda.Invoke());
- Assert.AreEqual(expectedReturnType, lambda.ReturnType);
-
- lambda = interpreter.Parse("a - b");
- Assert.AreEqual(a - b, lambda.Invoke());
- Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType);
+ Verify(interpreter, "a < b", a < b);
+ Verify(interpreter, "a > b", a > b);
+ Verify(interpreter, "a == b", a == b);
+ Verify(interpreter, "a != b", a != b);
+ Verify(interpreter, "b == b", b == b);
+ Verify(interpreter, "b != c", b != c);
+ Verify(interpreter, "a - b", a - b);
b = null;
interpreter.SetVariable("b", b, typeof(DateTimeOffset?));
- lambda = interpreter.Parse("a - b");
- Assert.AreEqual(a - b, lambda.Invoke());
- Assert.AreEqual(typeof(TimeSpan?), lambda.ReturnType);
+ Verify(interpreter, "a - b", a - b);
+ }
+
+ private static void Verify(Interpreter interpreter, string expression, T expected)
+ {
+ var parsed = interpreter.Parse(expression);
+ Assert.AreEqual(expected, parsed.Invoke());
+ Assert.AreEqual(typeof(T), parsed.Expression.Type);
}
}
}