diff --git a/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs b/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs index 1233586..6691c15 100644 --- a/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs +++ b/ExpressionUtils/Evaluating/CachedExpressionCompiler.cs @@ -21,13 +21,15 @@ namespace MiaPlaza.ExpressionUtils.Evaluating { /// the result via a closure and a delegate capturing the actual parameters of the original expression is returned. /// public class CachedExpressionCompiler : IExpressionEvaluator { - static ConcurrentDictionary delegates = new ConcurrentDictionary(new ExpressionComparing.StructuralComparer(ignoreConstantsValues: true)); + static ConcurrentDictionary delegates = new ConcurrentDictionary(new LambdaExpressionComparer()); + public static readonly CachedExpressionCompiler Instance = new CachedExpressionCompiler(); private CachedExpressionCompiler() { } - VariadicArrayParametersDelegate IExpressionEvaluator.EvaluateLambda(LambdaExpression lambdaExpression) => CachedCompileLambda(lambdaExpression); - public VariadicArrayParametersDelegate CachedCompileLambda(LambdaExpression lambda) { + public VariadicArrayParametersDelegate EvaluateLambda(LambdaExpression lambdaExpression) => CachedCompileLambda(lambdaExpression); + + public VariadicArrayParametersDelegate CachedCompileLambda(LambdaParts lambda) { IReadOnlyList constants; ParameterListDelegate compiled; @@ -51,23 +53,23 @@ public VariadicArrayParametersDelegate CachedCompileLambda(LambdaExpression lamb return args => compiled(constants.Concat(args).ToArray()); } - object IExpressionEvaluator.Evaluate(Expression unparametrizedExpression) => CachedCompile(unparametrizedExpression); - public object CachedCompile(Expression unparametrizedExpression) => CachedCompileLambda(Expression.Lambda(unparametrizedExpression))(); + public object Evaluate(Expression unparametrizedExpression) => CachedCompile(unparametrizedExpression); + public object CachedCompile(Expression unparametrizedExpression) => CachedCompileLambda(new LambdaParts { Body = unparametrizedExpression, Parameters = Array.Empty() })(); - DELEGATE IExpressionEvaluator.EvaluateTypedLambda(Expression expression) => CachedCompileTypedLambda(expression); + public DELEGATE EvaluateTypedLambda(Expression expression) where DELEGATE : class => CachedCompileTypedLambda(expression); public DELEGATE CachedCompileTypedLambda(Expression expression) where DELEGATE : class => CachedCompileLambda(expression).WrapDelegate(); /// - /// 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. + /// Creates a closure-free key for caching, represented as a tuple of the expression body and parameter collection. + /// Can be used with the to compare to the original lambda expression. /// - private LambdaExpression getClosureFreeKeyForCaching(ConstantExtractor.ExtractionResult extractionResult, IReadOnlyCollection parameterExpressions) { + private LambdaParts getClosureFreeKeyForCaching(ConstantExtractor.ExtractionResult extractionResult, IReadOnlyCollection parameterExpressions) { var e = SimpleParameterSubstituter.SubstituteParameter(extractionResult.ConstantfreeExpression, extractionResult.ConstantfreeExpression.Parameters.Select( p => (Expression) Expression.Constant(getDefaultValue(p.Type), p.Type))); - - return Expression.Lambda(e, parameterExpressions); + + return new LambdaParts { Body = e, Parameters = parameterExpressions }; } private static object getDefaultValue(Type t) { @@ -84,5 +86,27 @@ private static object getDefaultValue(Type t) { internal bool IsCached(LambdaExpression lambda) { return delegates.ContainsKey(lambda); } + + /// + /// Previously as a key for we were using objects and as a comparer. + /// But that required us to make calls to which contains global lock inside, and that started to become a problem. + /// So instead we now use as a key and pass body and parameters separately, to reduce number of calls to . + /// + private class LambdaExpressionComparer : IEqualityComparer { + private IEqualityComparer expressionComparer = new ExpressionComparing.StructuralComparer(ignoreConstantsValues: true); + + public bool Equals(LambdaParts x, LambdaParts y) { + return x.Body.Type == y.Body.Type + && x.Parameters.SequenceEqualOrBothNull(y.Parameters, expressionComparer.Equals) + && expressionComparer.Equals(x.Body, y.Body); + } + + public int GetHashCode(LambdaParts obj) { + var hash = Hashing.FnvOffset; + Hashing.Hash(ref hash, obj.Body.Type.GetHashCode()); + Hashing.Hash(ref hash, expressionComparer.GetHashCode(obj.Body)); + return hash; + } + } } } diff --git a/ExpressionUtils/ExpressionUtils.csproj b/ExpressionUtils/ExpressionUtils.csproj index 45e7a69..7a487b3 100644 --- a/ExpressionUtils/ExpressionUtils.csproj +++ b/ExpressionUtils/ExpressionUtils.csproj @@ -19,6 +19,8 @@ MIT https://github.com/Miaplaza/expression-utils Expression;Expressions;Linq.Expressions;Evaluation;Compile;Compiler;Execute;Execution;Compare;Comparison + embedded + true diff --git a/ExpressionUtils/LambdaParts.cs b/ExpressionUtils/LambdaParts.cs new file mode 100644 index 0000000..3d9ca5c --- /dev/null +++ b/ExpressionUtils/LambdaParts.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace MiaPlaza.ExpressionUtils { + + /// + /// Sometimes we don't have actual object, but instead and that represent lambda. + /// We could call to get it, but such calls can be expensive due to locking that is used inside. + /// So this object plays a role of a container that holds the data that is needed to create lambda expression. + /// + /// + /// Before objects were used to fullfill that role. This was added to replace those. + /// + public class LambdaParts { + public Expression Body { get; set; } + public IReadOnlyCollection Parameters { get; set; } + + public static implicit operator LambdaParts(LambdaExpression lambda) + => lambda is null ? null : new LambdaParts { Body = lambda.Body, Parameters = lambda.Parameters }; + } +} diff --git a/ExpressionUtils/ParameterSubstituter.cs b/ExpressionUtils/ParameterSubstituter.cs index 6a0d758..f589b23 100644 --- a/ExpressionUtils/ParameterSubstituter.cs +++ b/ExpressionUtils/ParameterSubstituter.cs @@ -26,11 +26,8 @@ public class ParameterSubstituter : SimpleParameterSubstituter { public static new Expression SubstituteParameter(LambdaExpression expression, params Expression[] replacements) => SubstituteParameter(expression, replacements as IReadOnlyCollection); - public static new Expression SubstituteParameter(LambdaExpression expression, IEnumerable replacements) - => SubstituteParameter(expression, replacements.ToList()); - - public static new Expression SubstituteParameter(LambdaExpression expression, IReadOnlyCollection replacements) { - if (expression == null) { + public static Expression SubstituteParameter(LambdaParts expression, IReadOnlyCollection replacements) { + if (expression == null || expression.Body == null || expression.Parameters == null) { throw new ArgumentNullException(nameof(expression)); }