diff --git a/ExpressionUtils/CompiledActivator.cs b/ExpressionUtils/CompiledActivator.cs index ec3513d..3ebf031 100644 --- a/ExpressionUtils/CompiledActivator.cs +++ b/ExpressionUtils/CompiledActivator.cs @@ -61,24 +61,5 @@ public static T Create(Type t) { return constructor.Invoke(); } } - - public static class ForAnyType { - private static ConcurrentDictionary> cachedNew = new ConcurrentDictionary>(); - - /// - /// Create a by invoking its default constructor. - /// This is much faster than (T)Activator.CreateInstance(t). - /// - public static object Create(Type t) { - Func constructor; - // We do not need to lock the dictionary; another thread can only overwrite it with the same value - if (!cachedNew.TryGetValue(t, out constructor)) { - - constructor = Expression.Lambda>(Expression.TypeAs(Expression.New(t), typeof(object))).Compile(); - cachedNew[t] = constructor; - } - return constructor.Invoke(); - } - } } } \ No newline at end of file diff --git a/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs b/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs index 87362aa..1233586 100644 --- a/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs +++ b/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ExpressionUtilsTest")] + namespace MiaPlaza.ExpressionUtils.Evaluating { /// @@ -40,8 +42,8 @@ public VariadicArrayParametersDelegate CachedCompileLambda(LambdaExpression lamb extractionResult.ConstantfreeExpression.Parameters.Concat(lambda.Parameters))) .Compile(); - var key = buildCacheKey(extractionResult, lambda.Parameters); - + var key = getClosureFreeKeyForCaching(extractionResult, lambda.Parameters); + delegates.TryAdd(key, compiled); constants = extractionResult.ExtractedConstants; } @@ -55,13 +57,32 @@ public VariadicArrayParametersDelegate CachedCompileLambda(LambdaExpression lamb DELEGATE IExpressionEvaluator.EvaluateTypedLambda(Expression expression) => CachedCompileTypedLambda(expression); public DELEGATE CachedCompileTypedLambda(Expression expression) where DELEGATE : class => CachedCompileLambda(expression).WrapDelegate(); - private LambdaExpression buildCacheKey(ConstantExtractor.ExtractionResult extractionResult, IReadOnlyCollection parameterExpressions) { - var e= ParameterSubstituter.SubstituteParameter(extractionResult.ConstantfreeExpression, + + /// + /// A closure free expression tree that can be used as a caching key. Can be used with the to compare + /// to the original lambda expression. + /// + private LambdaExpression getClosureFreeKeyForCaching(ConstantExtractor.ExtractionResult extractionResult, IReadOnlyCollection parameterExpressions) { + var e = SimpleParameterSubstituter.SubstituteParameter(extractionResult.ConstantfreeExpression, extractionResult.ConstantfreeExpression.Parameters.Select( - p => p.Type.IsValueType && !(p.Type.IsGenericType && p.Type.GetGenericTypeDefinition() == typeof(Nullable<>)) ? - (Expression) Expression.Constant(CompiledActivator.ForAnyType.Create(p.Type)) : - (Expression) Expression.TypeAs(Expression.Constant(null), p.Type))); + p => (Expression) Expression.Constant(getDefaultValue(p.Type), p.Type))); + return Expression.Lambda(e, parameterExpressions); } + + private static object getDefaultValue(Type t) { + if (t.IsValueType) { + return Activator.CreateInstance(t); + } + + return null; + } + + /// + /// Use for testing only. + /// + internal bool IsCached(LambdaExpression lambda) { + return delegates.ContainsKey(lambda); + } } } diff --git a/ExpressionUtils/ExpressionStructureIdentity.cs b/ExpressionUtils/ExpressionStructureIdentity.cs index 2484f00..847dfd9 100644 --- a/ExpressionUtils/ExpressionStructureIdentity.cs +++ b/ExpressionUtils/ExpressionStructureIdentity.cs @@ -237,7 +237,7 @@ public StructuralComparer(bool ignoreConstantsValues = false, int? hashCodeExpre public int GetHashCode(Expression tree) => GetNodeTypeStructureHashCode(tree, IgnoreConstantsValues, HashCodeExpressionDepth); - bool IEqualityComparer.Equals(Expression x, Expression y) => StructuralIdentical(x, y); + bool IEqualityComparer.Equals(Expression x, Expression y) => StructuralIdentical(x, y, IgnoreConstantsValues); } /// diff --git a/ExpressionUtils/ExpressionUtils.csproj b/ExpressionUtils/ExpressionUtils.csproj index af6e82f..45e7a69 100644 --- a/ExpressionUtils/ExpressionUtils.csproj +++ b/ExpressionUtils/ExpressionUtils.csproj @@ -8,9 +8,9 @@ MiaPlaza.ExpressionUtils.Properties Efficient Processing, Compilation, and Execution of Expression Trees at Runtime Copyright ©2017-2019 Miaplaza Inc. - 1.1.7 - 1.1.7 - 1.1.7 + 1.2.0 + 1.2.0 + 1.2.0 none Miaplaza Inc. https://github.com/Miaplaza/expression-utils diff --git a/ExpressionUtils/ParameterSubstituter.cs b/ExpressionUtils/ParameterSubstituter.cs index 5568dc6..6a0d758 100644 --- a/ExpressionUtils/ParameterSubstituter.cs +++ b/ExpressionUtils/ParameterSubstituter.cs @@ -21,14 +21,15 @@ namespace MiaPlaza.ExpressionUtils { /// To that end, the visitor also removes unecessary casts to find the most specific override /// possible (.) /// - public class ParameterSubstituter : ExpressionVisitor { - public static Expression SubstituteParameter(LambdaExpression expression, params Expression[] replacements) + public class ParameterSubstituter : SimpleParameterSubstituter { + + public static new Expression SubstituteParameter(LambdaExpression expression, params Expression[] replacements) => SubstituteParameter(expression, replacements as IReadOnlyCollection); - public static Expression SubstituteParameter(LambdaExpression expression, IEnumerable replacements) + public static new Expression SubstituteParameter(LambdaExpression expression, IEnumerable replacements) => SubstituteParameter(expression, replacements.ToList()); - public static Expression SubstituteParameter(LambdaExpression expression, IReadOnlyCollection replacements) { + public static new Expression SubstituteParameter(LambdaExpression expression, IReadOnlyCollection replacements) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } @@ -56,20 +57,7 @@ public static Expression SubstituteParameter(LambdaExpression expression, IReadO public static Expression SubstituteParameter(Expression expression, IReadOnlyDictionary replacements) => new ParameterSubstituter(replacements).Visit(expression); - readonly IReadOnlyDictionary replacements; - - ParameterSubstituter(IReadOnlyDictionary replacements) { - this.replacements = replacements; - } - - protected override Expression VisitParameter(ParameterExpression node) { - Expression replacement; - if (replacements.TryGetValue(node, out replacement)) { - return replacement; - } else { - return node; - } - } + ParameterSubstituter(IReadOnlyDictionary replacements) : base(replacements) { } protected override Expression VisitMember(MemberExpression node) { var baseCallResult = (MemberExpression)base.VisitMember(node); @@ -119,6 +107,10 @@ protected override Expression VisitUnary(UnaryExpression node) { return base.VisitUnary(node); } + protected override Expression VisitBinary(BinaryExpression node) { + return base.VisitBinary(node); + } + private static MethodInfo getImplementationToCallOn(Type t, MethodInfo method) { if (method.DeclaringType.IsInterface) { return method.GetImplementationInfo(t); diff --git a/ExpressionUtils/SimpleParameterSubstituter.cs b/ExpressionUtils/SimpleParameterSubstituter.cs new file mode 100644 index 0000000..b45364a --- /dev/null +++ b/ExpressionUtils/SimpleParameterSubstituter.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace MiaPlaza.ExpressionUtils { + /// + /// Replaces all parameters in an lambda-expression by other expressions, i.e., given a lambda + /// (t0, …, tn) → f(t0,…,tn) and expressions (x0,…,xn), it returns the expression f(x0,…,xn). + /// + /// + /// Does nothing else than that, esepicially does not change the structure of the expression or removes closures.false + /// If you want that and other optimizations, use see + /// + public class SimpleParameterSubstituter : ExpressionVisitor { + public static Expression SubstituteParameter(LambdaExpression expression, params Expression[] replacements) + => SubstituteParameter(expression, replacements as IReadOnlyCollection); + + public static Expression SubstituteParameter(LambdaExpression expression, IEnumerable replacements) + => SubstituteParameter(expression, replacements.ToList()); + + public static Expression SubstituteParameter(LambdaExpression expression, IReadOnlyCollection replacements) { + if (expression == null) { + throw new ArgumentNullException(nameof(expression)); + } + + if (replacements == null) { + throw new ArgumentNullException(nameof(replacements)); + } + + if (expression.Parameters.Count != replacements.Count) { + throw new ArgumentException($"Replacement count does not match parameter count ({replacements.Count} vs {expression.Parameters.Count})"); + } + + var dict = new Dictionary(); + + foreach (var tuple in expression.Parameters.Zip(replacements, (p, r) => new { parameter = p, replacement = r })) { + if (!tuple.parameter.Type.IsAssignableFrom(tuple.replacement.Type)) { + throw new ArgumentException($"The expression {tuple.replacement} cannot be used as replacement for the parameter {tuple.parameter}."); + } + dict[tuple.parameter] = tuple.replacement; + } + + return new SimpleParameterSubstituter(dict).Visit(expression.Body); + } + + public static Expression SubstituteParameter(Expression expression, IReadOnlyDictionary replacements) + => new SimpleParameterSubstituter(replacements).Visit(expression); + + readonly IReadOnlyDictionary replacements; + + protected SimpleParameterSubstituter(IReadOnlyDictionary replacements) { + this.replacements = replacements; + } + + protected override Expression VisitParameter(ParameterExpression node) { + Expression replacement; + if (replacements.TryGetValue(node, out replacement)) { + return replacement; + } else { + return node; + } + } + } +} diff --git a/ExpressionUtilsTest/CachedExpressionCompilerTestEvaluator.cs b/ExpressionUtilsTest/CachedExpressionCompilerTestEvaluator.cs new file mode 100644 index 0000000..f37886a --- /dev/null +++ b/ExpressionUtilsTest/CachedExpressionCompilerTestEvaluator.cs @@ -0,0 +1,26 @@ +using System.Linq.Expressions; +using MiaPlaza.ExpressionUtils; +using MiaPlaza.ExpressionUtils.Evaluating; +using NUnit.Framework; + +namespace MiaPlaza.Test.ExpressionUtilsTest { + public class CachedExpressionCompilerTestEvaluator : IExpressionEvaluator { + + public object Evaluate(Expression unparametrizedExpression) { + var lambda = Expression.Lambda(unparametrizedExpression); + return this.EvaluateLambda(lambda)(); + } + + public VariadicArrayParametersDelegate EvaluateLambda(LambdaExpression lambdaExpression) { + var result = ((IExpressionEvaluator)CachedExpressionCompiler.Instance).EvaluateLambda(lambdaExpression); + Assert.IsTrue(CachedExpressionCompiler.Instance.IsCached(lambdaExpression)); + return result; + } + + public DELEGATE EvaluateTypedLambda(Expression expression) where DELEGATE : class { + + var result = this.EvaluateLambda(expression); + return result.WrapDelegate(); + } + } +} diff --git a/ExpressionUtilsTest/ExpressionEvaluation.cs b/ExpressionUtilsTest/ExpressionEvaluation.cs index a38d9f5..d3dc598 100644 --- a/ExpressionUtilsTest/ExpressionEvaluation.cs +++ b/ExpressionUtilsTest/ExpressionEvaluation.cs @@ -19,6 +19,7 @@ class ExpressionEvaluation { new TestFixtureData(ExpressionCompiler.Instance), new TestFixtureData(CachedExpressionCompiler.Instance), new TestFixtureData(ExpressionInterpreter.Instance), + new TestFixtureData(new CachedExpressionCompilerTestEvaluator()), }; private readonly IExpressionEvaluator evaluator; @@ -119,6 +120,14 @@ public void TestNullableEnumToIntConvertExpression() { Assert.Catch(() => evaluator.Evaluate(expression.Body)); } + [Test] + public void TestNullableEnumExpression() { + Expression> expA = (MyEnum? t) => t == MyEnum.First; + + Assert.IsTrue((bool)evaluator.EvaluateLambda(expA)(MyEnum.First)); + Assert.IsFalse((bool)evaluator.EvaluateLambda(expA)(MyEnum.Second)); + } + public static readonly IEnumerable TestOffsets = new[] { 1, 100, @@ -206,5 +215,15 @@ public void TestNullableEquality() { Assert.That((bool)evaluator.EvaluateLambda(hasValue)(DateTime.Now)); Assert.IsFalse((bool)(evaluator.EvaluateLambda(hasValue)((DateTime?)null))); } + + class ParentClass {} + class ChildClass : ParentClass {} + + [Test] + public void TestConvertExpresssion() { + var exp = Expression.Convert(Expression.Constant(new ChildClass()), typeof(ParentClass)); + + evaluator.Evaluate(exp); + } } } diff --git a/ExpressionUtilsTest/StructuralIdentity.cs b/ExpressionUtilsTest/StructuralIdentity.cs index 4f31bb5..d33f9f1 100644 --- a/ExpressionUtilsTest/StructuralIdentity.cs +++ b/ExpressionUtilsTest/StructuralIdentity.cs @@ -188,5 +188,14 @@ public void ComplexLambda() { Assert.IsTrue(expA.StructuralIdentical(expB)); } + + [Test] + public void TestClosureLambda() { + int variable = 7; + Expression> expA = (int a) => a != variable; + Expression> expB = (int a) => a != 8; + + Assert.IsFalse(expA.StructuralIdentical(expB, true)); + } } }