├── .hgignore ├── Mindbox.Expressions ├── Evaluators │ ├── IExpressionEvaluator.cs │ ├── InterpretingExpressionEvaluator.cs │ ├── CompilingExpressionEvaluator.cs │ └── Caching │ │ ├── ClosureCapturedValuesVisitor.cs │ │ ├── ClosureCapturedValuesProvider.cs │ │ ├── ClosureCapturedValuesParametrizer.cs │ │ ├── CachingCompilingExpressionEvaluator.cs │ │ └── MindboxExpressionStringBuilder.cs ├── ExpressionsConfiguration.cs ├── AssemblyInfo.cs ├── ExpressionFunctions │ ├── ExpressionFunction.cs │ └── ExpressionFunctionFactory.cs ├── Mindbox.Expressions.csproj ├── ExpressionParameterPresenceDetector.cs ├── EvaluationScope.cs ├── ExpressionParameterSubstitutor.cs ├── ExpressionExpander.cs ├── ExpressionVisitor.cs └── ReflectionExpressions.cs ├── Mindbox.Expressions.Tests ├── packages.config ├── AssertException.cs ├── Mindbox.Expressions.Tests.csproj ├── ExpressionFunctionTests.cs ├── EvaluationScopeTests.cs ├── EvaluateTests.cs ├── ReflectionExpressionsTests.cs ├── BooleanExpressionsTests.cs └── ExpandExpressionTests.cs ├── README.md ├── .github └── workflows │ ├── pull-request.yml │ └── publish.yml ├── LICENSE ├── Mindbox.Expressions.sln ├── package.cmd └── .gitignore /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.suo 3 | obj 4 | bin 5 | .git 6 | TestResults 7 | packages 8 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/IExpressionEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Mindbox.Expressions 4 | { 5 | public interface IExpressionEvaluator 6 | { 7 | object Evaluate(Expression expression); 8 | } 9 | } -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | expressions 2 | =========== 3 | 4 | Allows to include lambda expressions into each other in c# and Visual Basic. Also allows to avoid using string constants in reflection thus enabling easy refactoring. 5 | 6 | See wiki at https://github.com/mindbox-cloud/expressions/wiki 7 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionsConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Mindbox.Expressions 4 | { 5 | public static class ExpressionsConfiguration 6 | { 7 | public static Func ExpressionEvaluatorFactory { get; set; } = 8 | () => CompilingExpressionEvaluator.Instance; 9 | } 10 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/InterpretingExpressionEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Mindbox.Expressions 4 | { 5 | public class InterpretingExpressionEvaluator : IExpressionEvaluator 6 | { 7 | public object Evaluate(Expression expression) 8 | { 9 | return EvaluationScope.Empty.TryEvaluate(expression); 10 | } 11 | 12 | public static InterpretingExpressionEvaluator Instance { get; } = new InterpretingExpressionEvaluator(); 13 | } 14 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/CompilingExpressionEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Mindbox.Expressions 4 | { 5 | public class CompilingExpressionEvaluator : IExpressionEvaluator 6 | { 7 | public object Evaluate(Expression expression) 8 | { 9 | return Expression.Lambda(expression).Compile() 10 | .DynamicInvoke(); 11 | } 12 | 13 | public static CompilingExpressionEvaluator Instance { get; } = new CompilingExpressionEvaluator(); 14 | } 15 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Mindbox.Expressions")] 5 | [assembly: AssemblyCompany("Mindbox")] 6 | [assembly: AssemblyProduct("Mindbox.Expressions")] 7 | [assembly: AssemblyDescription("Allows to include lambda expressions into each other in c# and Visual Basic. " + 8 | "Also allows to avoid using string constants in reflection thus enabling easy refactoring.")] 9 | [assembly: AssemblyCopyright("Copyright © Mindbox 2014")] 10 | [assembly: AssemblyVersion("3.0.1")] 11 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionFunctions/ExpressionFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Mindbox.Expressions 5 | { 6 | internal class ExpressionFunction 7 | { 8 | private readonly Expression expression; 9 | 10 | public ExpressionFunction(Expression expression) 11 | { 12 | this.expression = expression; 13 | } 14 | 15 | internal Delegate EvaluationFunction => (Func) Evaluate; 16 | 17 | private TResult Evaluate() 18 | { 19 | return (TResult)EvaluationScope.Empty.TryEvaluate(expression); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/AssertException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Mindbox.Expressions.Tests 5 | { 6 | public static class AssertException 7 | { 8 | public static void Throws(Action action, Action assertion) 9 | where TException : Exception 10 | { 11 | try 12 | { 13 | action(); 14 | } 15 | catch (TException e) 16 | { 17 | assertion(e); 18 | return; 19 | } 20 | 21 | Assert.Fail($"Expected exception of type {typeof(TException)}, but is has never occured"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v1 11 | 12 | - name: Setup .NET 6 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: 6.0.x 16 | 17 | - name: Install dependencies 18 | run: dotnet restore 19 | 20 | - name: Build 21 | run: dotnet build ./Mindbox.Expressions.sln --configuration Release --no-restore 22 | 23 | - name: Test 24 | run: dotnet test --no-restore 25 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Mindbox.Expressions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Mindbox 6 | 3.3.1 7 | Mindbox.Expressions 8 | Allows to include lambda expressions into each other in c# and Visual Basic. Also allows to avoid using string constants in reflection thus enabling easy refactoring. 9 | Mindbox 2018 10 | Mindbox 11 | false 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/Caching/ClosureCapturedValuesVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Mindbox.Expressions 5 | { 6 | internal abstract class ClosureCapturedValuesVisitor : ExpressionVisitor 7 | { 8 | protected ClosureCapturedValuesVisitor() 9 | { 10 | 11 | } 12 | 13 | protected sealed override Expression VisitConstant(ConstantExpression node) 14 | { 15 | if (node == null) 16 | throw new ArgumentNullException(nameof(node)); 17 | 18 | if (node.Value == null) 19 | return base.VisitConstant(node); 20 | 21 | var replacedQuery = TryProcessClosure(node); 22 | if (replacedQuery != null) 23 | return replacedQuery; 24 | 25 | return base.VisitConstant(node); 26 | } 27 | 28 | protected abstract Expression TryProcessClosure(ConstantExpression node); 29 | } 30 | } -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Main Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | 15 | - name: Setup .NET 6 16 | uses: actions/setup-dotnet@v1 17 | with: 18 | dotnet-version: 6.0.x 19 | 20 | - name: Install dependencies 21 | run: dotnet restore 22 | 23 | - name: Build 24 | run: dotnet build ./Mindbox.Expressions.sln --configuration Release --no-restore 25 | 26 | - name: Test 27 | run: dotnet test --no-restore 28 | 29 | - name: Pack 30 | run: dotnet pack ./Mindbox.Expressions.sln -c Release 31 | 32 | - name: Publish 33 | run: dotnet nuget push **/*.nupkg -k ${{secrets.MINDBOX_NUGET_AUTH_TOKEN}} -s https://api.nuget.org/v3/index.json 34 | -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/Mindbox.Expressions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Mindbox 5 | Mindbox.Expressions.Tests 6 | Copyright © Mindbox 2014 7 | Mindbox.Expressions.Tests 8 | Mindbox.Expressions.Tests 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/Caching/ClosureCapturedValuesProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace Mindbox.Expressions 6 | { 7 | internal class ClosureCapturedValuesProvider : ClosureCapturedValuesVisitor 8 | { 9 | public static object[] GetCapturedValues(Expression expression) 10 | { 11 | var visitor = new ClosureCapturedValuesProvider(); 12 | visitor.Visit(expression); 13 | 14 | return visitor.capturedValues.ToArray(); 15 | } 16 | 17 | private ClosureCapturedValuesProvider() 18 | { 19 | 20 | } 21 | 22 | private readonly List capturedValues = new List(); 23 | 24 | protected override Expression TryProcessClosure(ConstantExpression node) 25 | { 26 | if (node == null) 27 | throw new ArgumentNullException(nameof(node)); 28 | 29 | capturedValues.Add(node.Value); 30 | 31 | return null; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 mindbox-moscow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionFunctions/ExpressionFunctionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace Mindbox.Expressions 7 | { 8 | public static class ExpressionFunctionFactory 9 | { 10 | private static readonly ConcurrentDictionary> FunctionFactories = 11 | new ConcurrentDictionary>(); 12 | 13 | public static Delegate Create(Expression expression) 14 | { 15 | var type = expression.Type; 16 | return FunctionFactories.GetOrAdd(type, CreateExpressionFunctionFactory).Invoke(expression); 17 | } 18 | 19 | private static Func CreateExpressionFunctionFactory(Type expressionType) 20 | { 21 | var evaluator = typeof(ExpressionFunction<>).MakeGenericType(expressionType); 22 | var constructor = evaluator.GetConstructors().Single(); 23 | 24 | var parameter = Expression.Parameter(typeof(Expression), "expression"); 25 | 26 | var newEvaluatorExpression = Expression.New(constructor, parameter); 27 | 28 | var delegateGetter = Expression.Property(newEvaluatorExpression, 29 | nameof(ExpressionFunction.EvaluationFunction)); 30 | return (Func)Expression.Lambda(delegateGetter, parameter) 31 | .Compile(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/Caching/ClosureCapturedValuesParametrizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Mindbox.Expressions 5 | { 6 | internal class ClosureCapturedValuesParametrizer : ClosureCapturedValuesVisitor 7 | { 8 | public static Expression GetParametrizedExpression( 9 | Expression expression, 10 | ParameterExpression arrayOfValues) 11 | { 12 | var visitor = new ClosureCapturedValuesParametrizer(arrayOfValues); 13 | var parametrizedBody = visitor.Visit(expression); 14 | return parametrizedBody.Type.IsPrimitive 15 | ? Expression.Convert(parametrizedBody, typeof(object)) 16 | : parametrizedBody; 17 | } 18 | 19 | private readonly ParameterExpression arrayOfValues; 20 | 21 | private int visitedIndex = 0; 22 | 23 | private ClosureCapturedValuesParametrizer( 24 | ParameterExpression arrayOfValues) 25 | { 26 | this.arrayOfValues = arrayOfValues; 27 | } 28 | 29 | protected override Expression TryProcessClosure(ConstantExpression node) 30 | { 31 | return CreateIndexExpression(node); 32 | } 33 | 34 | private UnaryExpression CreateIndexExpression(ConstantExpression node) 35 | { 36 | var indexExpression = Expression.Convert( 37 | Expression.ArrayIndex(arrayOfValues, Expression.Constant(visitedIndex)), 38 | node.Type); 39 | visitedIndex++; 40 | return indexExpression; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionParameterPresenceDetector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace Mindbox.Expressions 8 | { 9 | public sealed class ExpressionParameterPresenceDetector : ExpressionVisitor 10 | { 11 | public static bool DoesExpressionHaveParameters(Expression expression) 12 | { 13 | var detector = new ExpressionParameterPresenceDetector(); 14 | detector.Visit(expression); 15 | return detector.doesExpressionHaveParameters; 16 | } 17 | 18 | 19 | private ExpressionParameterPresenceDetector() { } 20 | 21 | 22 | private bool doesExpressionHaveParameters; 23 | private readonly List allowedParameters = new List(); 24 | 25 | 26 | protected override Expression VisitParameter(ParameterExpression node) 27 | { 28 | if (node == null) 29 | throw new ArgumentNullException("node"); 30 | 31 | if (!allowedParameters.Contains(node)) 32 | doesExpressionHaveParameters = true; 33 | 34 | return base.VisitParameter(node); 35 | } 36 | 37 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 38 | protected override Expression VisitLambda(Expression node) 39 | { 40 | if (node == null) 41 | throw new ArgumentNullException("node"); 42 | 43 | allowedParameters.AddRange(node.Parameters); 44 | var result = base.VisitLambda(node); 45 | foreach (var parameter in node.Parameters) 46 | allowedParameters.Remove(parameter); 47 | return result; 48 | } 49 | #else 50 | protected override Expression VisitLambda(LambdaExpression node) 51 | { 52 | if (node == null) 53 | throw new ArgumentNullException("node"); 54 | 55 | allowedParameters.AddRange(node.Parameters); 56 | var result = base.VisitLambda(node); 57 | foreach (var parameter in node.Parameters) 58 | allowedParameters.Remove(parameter); 59 | return result; 60 | } 61 | #endif 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/Caching/CachingCompilingExpressionEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq.Expressions; 4 | using System.Resources; 5 | 6 | namespace Mindbox.Expressions 7 | { 8 | public class CachingCompilingExpressionEvaluator : IExpressionEvaluator 9 | { 10 | private CachingCompilingExpressionEvaluator() 11 | { 12 | 13 | } 14 | 15 | private readonly ConcurrentDictionary> compiledExpressionsCache = 16 | new ConcurrentDictionary>(); 17 | 18 | public object Evaluate(Expression expression) 19 | { 20 | var cacheKey = MindboxExpressionStringBuilder.ExpressionToString(expression); 21 | var cachedDelegate = compiledExpressionsCache.GetOrAdd(cacheKey, key => 22 | { 23 | var arrayOfValuesParameter = Expression.Parameter(typeof(object[])); 24 | var parametrizedBody = Parametrize(expression, arrayOfValuesParameter); 25 | if (parametrizedBody.Type.IsValueType) 26 | { 27 | parametrizedBody = Expression.Convert(parametrizedBody, typeof(object)); 28 | } 29 | 30 | var effectiveQuery = Expression.Lambda>( 31 | parametrizedBody, 32 | arrayOfValuesParameter 33 | ); 34 | 35 | return effectiveQuery.Compile(); 36 | }); 37 | 38 | var capturedValues = GetCapturedValues(expression); 39 | return cachedDelegate(capturedValues); 40 | } 41 | 42 | private static Expression Parametrize( 43 | Expression expression, 44 | ParameterExpression arrayOfValuesParameter) 45 | { 46 | return ClosureCapturedValuesParametrizer.GetParametrizedExpression( 47 | expression, 48 | arrayOfValuesParameter); 49 | } 50 | 51 | private static object[] GetCapturedValues(Expression expression) 52 | { 53 | return ClosureCapturedValuesProvider.GetCapturedValues(expression); 54 | } 55 | 56 | public static CachingCompilingExpressionEvaluator Instance { get; } = new CachingCompilingExpressionEvaluator(); 57 | } 58 | } -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/ExpressionFunctionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Mindbox.Expressions.Tests 5 | { 6 | [TestClass] 7 | public class ExpressionFunctionTests 8 | { 9 | private static object EvaluateViaExpressionFunction(Expression expression) 10 | { 11 | var evaluator = ExpressionFunctionFactory.Create(expression); 12 | 13 | return evaluator.DynamicInvoke(); 14 | } 15 | 16 | [TestMethod] 17 | public void TestEvaluateObject() 18 | { 19 | var obj = new object(); 20 | 21 | var result = EvaluateViaExpressionFunction(Expression.Constant(obj)); 22 | 23 | Assert.AreEqual(obj, result); 24 | } 25 | 26 | [TestMethod] 27 | public void TestEvaluateInt() 28 | { 29 | var result = EvaluateViaExpressionFunction(Expression.Constant(1)); 30 | 31 | Assert.AreEqual(1, result); 32 | } 33 | 34 | [TestMethod] 35 | public void TestEvaluateNullableInt() 36 | { 37 | var result = EvaluateViaExpressionFunction(Expression.Constant((int?)1, typeof(int?))); 38 | 39 | Assert.IsInstanceOfType(result, typeof(int?)); 40 | Assert.AreEqual((int?)1, result); 41 | } 42 | 43 | [TestMethod] 44 | public void TestEvaluateNullableIntWithNull() 45 | { 46 | var result = EvaluateViaExpressionFunction(Expression.Constant(null, typeof(int?))); 47 | 48 | Assert.IsNull(result); 49 | } 50 | 51 | [TestMethod] 52 | public void TestEvaluateConvertIntToNullableInt() 53 | { 54 | var result = EvaluateViaExpressionFunction( 55 | Expression.Convert( 56 | Expression.Constant(1), 57 | typeof(int?) 58 | ) 59 | ); 60 | 61 | Assert.IsInstanceOfType(result, typeof(int?)); 62 | Assert.AreEqual((int?)1, result); 63 | } 64 | 65 | [TestMethod] 66 | public void TestEvaluateConvertNullToNullableInt() 67 | { 68 | var result = EvaluateViaExpressionFunction( 69 | Expression.Convert( 70 | Expression.Constant(null), 71 | typeof(int?) 72 | ) 73 | ); 74 | 75 | Assert.IsNull(result); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Mindbox.Expressions/EvaluationScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace Mindbox.Expressions 6 | { 7 | public class EvaluationScope 8 | { 9 | public static EvaluationScope Empty { get; } = new EvaluationScope(); 10 | 11 | public object TryEvaluate(Expression expression) 12 | { 13 | switch (expression) 14 | { 15 | case MethodCallExpression methodCallExpression: 16 | return EvaluateMethod(methodCallExpression); 17 | case MemberExpression memberExpression: 18 | switch (memberExpression.Member) 19 | { 20 | case FieldInfo fieldInfo when fieldInfo.IsStatic: 21 | return fieldInfo.GetValue(null); 22 | case FieldInfo fieldInfo: 23 | return fieldInfo.GetValue(TryEvaluate(memberExpression.Expression)); 24 | case PropertyInfo propertyInfo when propertyInfo.GetMethod.IsStatic: 25 | return propertyInfo.GetValue(null); 26 | case PropertyInfo propertyInfo: 27 | return propertyInfo.GetValue(TryEvaluate(memberExpression.Expression)); 28 | } 29 | 30 | break; 31 | case ConstantExpression constantExpression: 32 | return constantExpression.Value; 33 | } 34 | 35 | return CachingCompilingExpressionEvaluator.Instance.Evaluate(expression); 36 | } 37 | 38 | private object EvaluateMethod(MethodCallExpression methodCallExpression) 39 | { 40 | var argumentsCount = methodCallExpression.Arguments.Count; 41 | var arguments = argumentsCount == 0 ? Array.Empty() : new object[argumentsCount]; 42 | 43 | for (var index = 0; index < methodCallExpression.Arguments.Count; index++) 44 | { 45 | var argument = methodCallExpression.Arguments[index]; 46 | var argumentValue = TryEvaluate(argument); 47 | 48 | arguments[index] = argumentValue; 49 | } 50 | 51 | if (methodCallExpression.Method.IsStatic) 52 | return methodCallExpression.Method.Invoke(null, arguments); 53 | 54 | var target = TryEvaluate(methodCallExpression.Object); 55 | return methodCallExpression.Method.Invoke(target, arguments); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Mindbox.Expressions.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mindbox.Expressions", "Mindbox.Expressions\Mindbox.Expressions.csproj", "{518F49A8-875A-477F-8B13-15E2051B8516}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mindbox.Expressions.Tests", "Mindbox.Expressions.Tests\Mindbox.Expressions.Tests.csproj", "{E1C9426A-4595-47B7-AA81-F7CEA242BF5F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x86 = Debug|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {518F49A8-875A-477F-8B13-15E2051B8516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {518F49A8-875A-477F-8B13-15E2051B8516}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {518F49A8-875A-477F-8B13-15E2051B8516}.Debug|x86.ActiveCfg = Debug|Any CPU 21 | {518F49A8-875A-477F-8B13-15E2051B8516}.Debug|x86.Build.0 = Debug|Any CPU 22 | {518F49A8-875A-477F-8B13-15E2051B8516}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {518F49A8-875A-477F-8B13-15E2051B8516}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {518F49A8-875A-477F-8B13-15E2051B8516}.Release|x86.ActiveCfg = Release|Any CPU 25 | {518F49A8-875A-477F-8B13-15E2051B8516}.Release|x86.Build.0 = Release|Any CPU 26 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Debug|x86.Build.0 = Debug|Any CPU 30 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Release|x86.ActiveCfg = Release|Any CPU 33 | {E1C9426A-4595-47B7-AA81-F7CEA242BF5F}.Release|x86.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {94B7FEE5-94A0-4560-B79A-B1CCE77D0DED} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionParameterSubstitutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace Mindbox.Expressions 8 | { 9 | internal sealed class ExpressionParameterSubstitutor : ExpressionVisitor 10 | { 11 | 12 | public static Expression SubstituteParameters( 13 | Expression expression, 14 | IDictionary parameterSubstitutions) 15 | { 16 | if (parameterSubstitutions == null) 17 | throw new ArgumentNullException("parameterSubstitutions"); 18 | 19 | return new ExpressionParameterSubstitutor(parameterSubstitutions).Visit(expression); 20 | } 21 | 22 | public static Expression SubstituteParameter( 23 | Expression expression, 24 | ParameterExpression oldParameter, 25 | Expression newParameter) 26 | { 27 | return SubstituteParameters( 28 | expression, 29 | new Dictionary 30 | { 31 | { 32 | oldParameter, 33 | newParameter 34 | } 35 | }); 36 | } 37 | 38 | 39 | private readonly Dictionary parameterSubstitutions; 40 | 41 | 42 | private ExpressionParameterSubstitutor(IDictionary parameterSubstitutions) 43 | { 44 | if (parameterSubstitutions == null) 45 | throw new ArgumentNullException("parameterSubstitutions"); 46 | 47 | this.parameterSubstitutions = new Dictionary(parameterSubstitutions); 48 | } 49 | 50 | 51 | protected override Expression VisitParameter(ParameterExpression node) 52 | { 53 | if (node == null) 54 | throw new ArgumentNullException("node"); 55 | 56 | Expression substitution; 57 | return parameterSubstitutions.TryGetValue(node, out substitution) ? 58 | SubstituteParameters(substitution, new Dictionary()) : 59 | base.VisitParameter(node); 60 | } 61 | 62 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 63 | protected override Expression VisitLambda(Expression node) 64 | { 65 | if (node == null) 66 | throw new ArgumentNullException("node"); 67 | 68 | var newParameters = new List(node.Parameters.Count); 69 | 70 | foreach (var oldParameter in node.Parameters) 71 | { 72 | var newParameter = Expression.Parameter(oldParameter.Type, oldParameter.Name); 73 | newParameters.Add(newParameter); 74 | parameterSubstitutions.Add(oldParameter, newParameter); 75 | } 76 | 77 | return node.Update(Visit(node.Body), newParameters); 78 | } 79 | #else 80 | protected override Expression VisitLambda(LambdaExpression node) 81 | { 82 | if (node == null) 83 | throw new ArgumentNullException("node"); 84 | 85 | var newParameters = new List(node.Parameters.Count); 86 | 87 | foreach (var oldParameter in node.Parameters) 88 | { 89 | var newParameter = Expression.Parameter(oldParameter.Type, oldParameter.Name); 90 | newParameters.Add(newParameter); 91 | parameterSubstitutions.Add(oldParameter, newParameter); 92 | } 93 | 94 | return Expression.Lambda(node.Type, Visit(node.Body), newParameters); 95 | } 96 | #endif 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /package.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist obj md obj 4 | cd obj 5 | del *.* /q 6 | cd .. 7 | 8 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=Release35 /t:Clean,Rebuild 9 | if %errorlevel% neq 0 exit /b 1 10 | "%VS120COMNTOOLS%..\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" Mindbox.Expressions.Tests\bin\Release35\Mindbox.Expressions.Tests.dll /InIsolation 11 | if %errorlevel% neq 0 exit /b 2 12 | 13 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=Release40 /t:Rebuild 14 | if %errorlevel% neq 0 exit /b 1 15 | "%VS120COMNTOOLS%..\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" Mindbox.Expressions.Tests\bin\Release40\Mindbox.Expressions.Tests.dll /InIsolation 16 | if %errorlevel% neq 0 exit /b 2 17 | 18 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=Release45 /t:Rebuild 19 | if %errorlevel% neq 0 exit /b 1 20 | "%VS120COMNTOOLS%..\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" Mindbox.Expressions.Tests\bin\Release45\Mindbox.Expressions.Tests.dll /InIsolation 21 | if %errorlevel% neq 0 exit /b 2 22 | 23 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseSL3 /t:Rebuild 24 | if %errorlevel% neq 0 exit /b 1 25 | StatLight.exe -x=".\Mindbox.Expressions.Tests\bin\ReleaseSL3\Mindbox.Expressions.Tests.xap" 26 | if %errorlevel% neq 0 exit /b 2 27 | 28 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseSL4 /t:Rebuild 29 | if %errorlevel% neq 0 exit /b 1 30 | StatLight.exe -x=".\Mindbox.Expressions.Tests\bin\ReleaseSL4\Mindbox.Expressions.Tests.xap" 31 | if %errorlevel% neq 0 exit /b 2 32 | 33 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseSL5 /t:Rebuild 34 | if %errorlevel% neq 0 exit /b 1 35 | StatLight.exe -x=".\Mindbox.Expressions.Tests\bin\ReleaseSL5\Mindbox.Expressions.Tests.xap" 36 | if %errorlevel% neq 0 exit /b 2 37 | 38 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseWP71 /t:Rebuild 39 | if %errorlevel% neq 0 exit /b 1 40 | 41 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseWP8 /p:Platform=x86 /t:Rebuild 42 | if %errorlevel% neq 0 exit /b 1 43 | "%VS120COMNTOOLS%..\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" Mindbox.Expressions.Tests\bin\ReleaseWP8\Mindbox.Expressions.Tests.xap /InIsolation 44 | if %errorlevel% neq 0 exit /b 2 45 | 46 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseWPA81 /t:Rebuild 47 | if %errorlevel% neq 0 exit /b 1 48 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleaseCore45 /t:Rebuild 49 | if %errorlevel% neq 0 exit /b 1 50 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleasePortable36 /t:Rebuild 51 | if %errorlevel% neq 0 exit /b 1 52 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleasePortable88 /t:Rebuild 53 | if %errorlevel% neq 0 exit /b 1 54 | %windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe /p:Configuration=ReleasePortable328 /t:Rebuild 55 | if %errorlevel% neq 0 exit /b 1 56 | 57 | cd obj 58 | NuGet pack ..\Mindbox.Expressions\Mindbox.Expressions.csproj -Symbols 59 | if %errorlevel% neq 0 exit /b 3 60 | NuGet pack ..\Mindbox.Expressions\Mindbox.Expressions.csproj -Exclude **/*.pdb 61 | if %errorlevel% neq 0 exit /b 4 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | **/nuget.exe 247 | 248 | # FAKE - F# Make 249 | .fake/ 250 | 251 | # JetBrains Rider 252 | .idea/ 253 | *.sln.iml 254 | *.orig 255 | 256 | **/.DS_Store 257 | 258 | 259 | -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/EvaluationScopeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq.Expressions; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Mindbox.Expressions.Tests 8 | { 9 | [TestClass] 10 | public class EvaluationScopeTests 11 | { 12 | class Holder 13 | { 14 | public byte Property => 2; 15 | 16 | public static short StaticProperty => 4; 17 | 18 | public int Field = 8; 19 | 20 | public static long StaticField = 16; 21 | 22 | public long Sum(int a, short b) => a + b; 23 | 24 | public static long StaticSum(int a, short b) => a + b; 25 | } 26 | 27 | private readonly Holder holder = new Holder(); 28 | 29 | 30 | [TestMethod] 31 | public void ConstantExpression() 32 | { 33 | AssertIdenticalResult(() => 1); 34 | } 35 | 36 | [TestMethod] 37 | public void PropertyExpression() 38 | { 39 | AssertIdenticalResult(() => holder.Property); 40 | } 41 | 42 | [TestMethod] 43 | public void StaticPropertyExpression() 44 | { 45 | AssertIdenticalResult(() => Holder.StaticProperty); 46 | } 47 | 48 | [TestMethod] 49 | public void FieldExpression() 50 | { 51 | AssertIdenticalResult(() => holder.Field); 52 | } 53 | 54 | [TestMethod] 55 | public void StaticFieldExpression() 56 | { 57 | AssertIdenticalResult(() => Holder.StaticField); 58 | } 59 | 60 | [TestMethod] 61 | public void MethodCallExpression() 62 | { 63 | AssertIdenticalResult(() => holder.Sum(3, 11)); 64 | } 65 | 66 | [TestMethod] 67 | public void StaticMethodCallExpression() 68 | { 69 | AssertIdenticalResult(() => Holder.StaticSum(7, 13)); 70 | } 71 | 72 | [TestMethod] 73 | public void IndexerMethodCallExpression() 74 | { 75 | var array = new [,] { { 1, 2 }, { 3, 4 } }; 76 | 77 | AssertIdenticalResult(() => array[1, 0]); 78 | } 79 | 80 | [TestMethod] 81 | public void ArrayLengthExpression() 82 | { 83 | var array = new [] { 1, 2, 3 }; 84 | 85 | AssertIdenticalResult(() => array.Length); 86 | } 87 | 88 | struct Convertible 89 | { 90 | public static implicit operator string(Convertible c) 91 | { 92 | return "-10"; 93 | } 94 | 95 | public static explicit operator int(Convertible c) 96 | { 97 | return 10; 98 | } 99 | } 100 | 101 | [TestMethod] 102 | public void UnaryExpression_Convert_ImplicitMethod() 103 | { 104 | AssertIdenticalResult(() => new Convertible()); 105 | } 106 | 107 | [TestMethod] 108 | public void UnaryExpression_Convert_ExplicitMethod() 109 | { 110 | AssertIdenticalResult(() => (int) new Convertible()); 111 | } 112 | 113 | [TestMethod] 114 | public void UnaryExpression_Convert_IntToLong() 115 | { 116 | AssertIdenticalResult(Expression.Lambda>( 117 | Expression.Convert( 118 | Expression.Constant(1, typeof(int)), 119 | typeof(long)))); 120 | } 121 | 122 | [TestMethod] 123 | public void UnaryExpression_Convert_IntToNullableInt() 124 | { 125 | AssertIdenticalResult(Expression.Lambda>( 126 | Expression.Convert( 127 | Expression.Constant(1, typeof(int)), 128 | typeof(int?)))); 129 | } 130 | 131 | [TestMethod] 132 | public void UnaryExpression_Convert_IntToNullableLong() 133 | { 134 | AssertIdenticalResult( 135 | Expression.Lambda>( 136 | Expression.Convert( 137 | Expression.Constant(1, typeof(int)), 138 | typeof(long?)))); 139 | } 140 | 141 | [TestMethod] 142 | public void UnaryExpression_Convert_ValueTypeToObject() 143 | { 144 | AssertIdenticalResult(() => (object) 1); 145 | } 146 | 147 | [TestMethod] 148 | public void UnaryExpression_Convert_ObjectToValueType() 149 | { 150 | var boxedInt = (object) 1; 151 | AssertIdenticalResult(() => (int)boxedInt); 152 | } 153 | 154 | class Super { } 155 | 156 | class Sub : Super { } 157 | 158 | [TestMethod] 159 | public void UnaryExpression_Convert_ObjectToObject_CastSubclassToSuperclass() 160 | { 161 | var obj = new Sub(); 162 | AssertIdenticalResult(() => (Super)obj); 163 | } 164 | 165 | [TestMethod] 166 | public void UnaryExpression_Convert_ObjectToObject_CastSuperclassToSubclass() 167 | { 168 | Super obj = new Sub(); 169 | AssertIdenticalResult(() => (Sub)obj); 170 | } 171 | 172 | [TestMethod] 173 | public void UnaryExpression_Convert_EvaluateValueCastThrows() 174 | { 175 | var obj = (object)new KeyValuePair(); 176 | AssertIdenticalException(() => (KeyValuePair)obj); 177 | } 178 | 179 | struct ValueStruct 180 | { 181 | public int Value { get; set; } 182 | 183 | public static ValueStruct operator -(ValueStruct negated) 184 | { 185 | return new ValueStruct 186 | { 187 | Value = -negated.Value 188 | }; 189 | } 190 | } 191 | 192 | [TestMethod] 193 | public void UnaryExpression_OperatorCall() 194 | { 195 | AssertIdenticalResult(() => -new ValueStruct { Value = 1 }); 196 | } 197 | 198 | [TestMethod] 199 | public void UnaryExpression_OperatorCall_LiftedToNull_NonNullOperand() 200 | { 201 | AssertIdenticalResult(() => -new ValueStruct { Value = 1 }); 202 | } 203 | 204 | [TestMethod] 205 | public void UnaryExpression_OperatorCall_LiftedToNull_NullOperand() 206 | { 207 | var nullValueStruct = (ValueStruct?) null; 208 | AssertIdenticalResult(() => -nullValueStruct); 209 | } 210 | 211 | private static void AssertIdenticalResult(Expression> expression) 212 | { 213 | var evaluatedResult = (TResult)EvaluationScope.Empty.TryEvaluate(expression.Body); 214 | 215 | var compiledExpressionResult = expression.Compile()(); 216 | 217 | Assert.AreEqual(compiledExpressionResult, evaluatedResult); 218 | } 219 | 220 | private static void AssertIdenticalException(Expression> expression) 221 | where TException : Exception 222 | { 223 | Assert.ThrowsException(() => EvaluationScope.Empty.TryEvaluate(expression.Body)); 224 | Assert.ThrowsException(() => expression.Compile()()); 225 | 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/EvaluateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | #if NETFX_CORE || WINDOWS_PHONE 7 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 8 | #else 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | #endif 11 | 12 | namespace Mindbox.Expressions.Tests 13 | { 14 | [TestClass] 15 | public class EvaluateTests 16 | { 17 | [TestMethod] 18 | public void Evaluate0ArgumentsTest() 19 | { 20 | Expression> f1 = () => 5; 21 | 22 | Assert.AreEqual(5, f1.Evaluate()); 23 | } 24 | 25 | [TestMethod] 26 | public void Evaluate1ArgumentsTest() 27 | { 28 | Expression> f1 = x => x + 1; 29 | 30 | Assert.AreEqual(6, f1.Evaluate(5)); 31 | } 32 | 33 | [TestMethod] 34 | public void Evaluate2ArgumentsTest() 35 | { 36 | Expression> f1 = (x1, x2) => x1 + x2 * 2; 37 | 38 | Assert.AreEqual(5, f1.Evaluate(1, 2)); 39 | } 40 | 41 | [TestMethod] 42 | public void Evaluate3ArgumentsTest() 43 | { 44 | Expression> f1 = (x1, x2, x3) => x1 + x2 * 2 + x3 * 3; 45 | 46 | Assert.AreEqual(14, f1.Evaluate(1, 2, 3)); 47 | } 48 | 49 | [TestMethod] 50 | public void Evaluate4ArgumentsTest() 51 | { 52 | Expression> f1 = (x1, x2, x3, x4) => x1 + x2 * 2 + x3 * 3 + x4 * 4; 53 | 54 | Assert.AreEqual(30, f1.Evaluate(1, 2, 3, 4)); 55 | } 56 | 57 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 58 | [TestMethod] 59 | public void Evaluate5ArgumentsTest() 60 | { 61 | Expression> f1 = (x1, x2, x3, x4, x5) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5; 62 | 63 | Assert.AreEqual(35, f1.Evaluate(1, 2, 3, 4, 5)); 64 | } 65 | 66 | [TestMethod] 67 | public void Evaluate6ArgumentsTest() 68 | { 69 | Expression> f1 = 70 | (x1, x2, x3, x4, x5, x6) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6; 71 | 72 | Assert.AreEqual(41, f1.Evaluate(1, 2, 3, 4, 5, 6)); 73 | } 74 | 75 | [TestMethod] 76 | public void Evaluate7ArgumentsTest() 77 | { 78 | Expression> f1 = 79 | (x1, x2, x3, x4, x5, x6, x7) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7; 80 | 81 | Assert.AreEqual(48, f1.Evaluate(1, 2, 3, 4, 5, 6, 7)); 82 | } 83 | 84 | [TestMethod] 85 | public void Evaluate8ArgumentsTest() 86 | { 87 | Expression> f1 = 88 | (x1, x2, x3, x4, x5, x6, x7, x8) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8; 89 | 90 | Assert.AreEqual(56, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8)); 91 | } 92 | 93 | [TestMethod] 94 | public void Evaluate9ArgumentsTest() 95 | { 96 | Expression> f1 = 97 | (x1, x2, x3, x4, x5, x6, x7, x8, x9) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9; 98 | 99 | Assert.AreEqual(65, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9)); 100 | } 101 | 102 | [TestMethod] 103 | public void Evaluate10ArgumentsTest() 104 | { 105 | Expression> f1 = 106 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10; 107 | 108 | Assert.AreEqual(75, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 109 | } 110 | 111 | [TestMethod] 112 | public void Evaluate11ArgumentsTest() 113 | { 114 | Expression> f1 = 115 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => 116 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11; 117 | 118 | Assert.AreEqual(86, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); 119 | } 120 | 121 | [TestMethod] 122 | public void Evaluate12ArgumentsTest() 123 | { 124 | Expression> f1 = 125 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => 126 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12; 127 | 128 | Assert.AreEqual(98, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 129 | } 130 | 131 | [TestMethod] 132 | public void Evaluate13ArgumentsTest() 133 | { 134 | Expression> f1 = 135 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => 136 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 - x13; 137 | 138 | Assert.AreEqual(85, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); 139 | } 140 | 141 | [TestMethod] 142 | public void Evaluate14ArgumentsTest() 143 | { 144 | Expression> f1 = 145 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => 146 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 - x13 - x14; 147 | 148 | Assert.AreEqual(71, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); 149 | } 150 | 151 | [TestMethod] 152 | public void Evaluate15ArgumentsTest() 153 | { 154 | Expression> f1 = 155 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => 156 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 - x13 - x14 - x15; 157 | 158 | Assert.AreEqual(56, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); 159 | } 160 | 161 | [TestMethod] 162 | public void Evaluate16ArgumentsTest() 163 | { 164 | Expression> f1 = 165 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => 166 | x1 + x2 * 2 + x3 * 3 + x4 * 4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 - x13 - x14 - x15 - x16; 167 | 168 | Assert.AreEqual(40, f1.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 169 | } 170 | #endif 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/ReflectionExpressionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | #if NETFX_CORE || WINDOWS_PHONE 7 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 8 | #else 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | #endif 11 | 12 | namespace Mindbox.Expressions.Tests 13 | { 14 | [TestClass] 15 | public class ReflectionExpressionsTests 16 | { 17 | [TestMethod] 18 | public void GetMethodInfoInstanceFunctionTest() 19 | { 20 | var result = ReflectionExpressions.GetMethodInfo(methodObject => methodObject.Method1()); 21 | Assert.AreEqual("Method1", result.Name); 22 | } 23 | 24 | [TestMethod] 25 | public void GetMethodInfoInstanceFunctionVariableTest() 26 | { 27 | Expression> expression = methodObject => methodObject.Method1(); 28 | var result = ReflectionExpressions.GetMethodInfo(expression); 29 | Assert.AreEqual("Method1", result.Name); 30 | } 31 | 32 | [TestMethod] 33 | public void GetMethodInfoInstanceFunctionWithArgumentsTest() 34 | { 35 | var result = ReflectionExpressions.GetMethodInfo(methodObject => 36 | methodObject.Method2(default(string), default(int))); 37 | Assert.AreEqual("Method2", result.Name); 38 | } 39 | 40 | [TestMethod] 41 | public void GetMethodInfoInstanceVoidTest() 42 | { 43 | var result = ReflectionExpressions.GetMethodInfo(methodObject => methodObject.Method3()); 44 | Assert.AreEqual("Method3", result.Name); 45 | } 46 | 47 | [TestMethod] 48 | public void GetMethodInfoStaticVoidTest() 49 | { 50 | var result = ReflectionExpressions.GetMethodInfo(() => Test8.Method4()); 51 | Assert.AreEqual("Method4", result.Name); 52 | } 53 | 54 | [TestMethod] 55 | public void GetMethodInfoStaticFunctionTest() 56 | { 57 | var result = ReflectionExpressions.GetMethodInfo(() => Test8.Method5()); 58 | Assert.AreEqual("Method5", result.Name); 59 | } 60 | 61 | [TestMethod] 62 | public void GetMethodInfoStaticFunctionVariableTest() 63 | { 64 | Expression> expression = () => Test8.Method5(); 65 | var result = ReflectionExpressions.GetMethodInfo(expression); 66 | Assert.AreEqual("Method5", result.Name); 67 | } 68 | 69 | [TestMethod] 70 | public void TryGetPropertyNameTest() 71 | { 72 | Expression> expression = test => test.X; 73 | Assert.AreEqual("X", ReflectionExpressions.TryGetPropertyName(expression)); 74 | } 75 | 76 | [TestMethod] 77 | public void TryGetPropertyNameGenericInterfaceConstraintTest() 78 | { 79 | TryGetPropertyNameGeneric(); 80 | } 81 | 82 | [TestMethod] 83 | public void TryGetIndexedPropertyNameTest() 84 | { 85 | Expression> expression = test => test[default(string)]; 86 | Assert.AreEqual("Item", ReflectionExpressions.TryGetPropertyName(expression)); 87 | } 88 | 89 | [TestMethod] 90 | public void TryGetFieldNameTest() 91 | { 92 | Expression> expression = test => test.y; 93 | Assert.AreEqual("y", ReflectionExpressions.TryGetFieldName(expression)); 94 | } 95 | 96 | [TestMethod] 97 | public void TryGetMethodNameNullTest1() 98 | { 99 | Assert.IsNull(ReflectionExpressions.TryGetMethodName((Expression>)null)); 100 | } 101 | 102 | [TestMethod] 103 | public void TryGetMethodNameNullTest2() 104 | { 105 | Assert.IsNull(ReflectionExpressions.TryGetMethodName((Expression>)null)); 106 | } 107 | 108 | [TestMethod] 109 | public void TryGetMethodNameNullTest3() 110 | { 111 | Assert.IsNull(ReflectionExpressions.TryGetMethodName((Expression>)null)); 112 | } 113 | 114 | [TestMethod] 115 | public void TryGetMethodNameNullTest4() 116 | { 117 | Assert.IsNull(ReflectionExpressions.TryGetMethodName((Expression)null)); 118 | } 119 | 120 | [TestMethod] 121 | public void TryGetMethodNameNullTest5() 122 | { 123 | Assert.IsNull(ReflectionExpressions.TryGetMethodName((LambdaExpression)null)); 124 | } 125 | 126 | [TestMethod] 127 | public void TryGetPropertyNameNullTest1() 128 | { 129 | Assert.IsNull(ReflectionExpressions.TryGetPropertyName((Expression>)null)); 130 | } 131 | 132 | [TestMethod] 133 | public void TryGetPropertyNameNullTest2() 134 | { 135 | Assert.IsNull(ReflectionExpressions.TryGetPropertyName((Expression>)null)); 136 | } 137 | 138 | [TestMethod] 139 | public void TryGetPropertyNameNullTest3() 140 | { 141 | Assert.IsNull(ReflectionExpressions.TryGetPropertyName((LambdaExpression)null)); 142 | } 143 | 144 | [TestMethod] 145 | public void TryGetFieldNameNullTest1() 146 | { 147 | Assert.IsNull(ReflectionExpressions.TryGetFieldName((Expression>)null)); 148 | } 149 | 150 | [TestMethod] 151 | public void TryGetFieldNameNullTest2() 152 | { 153 | Assert.IsNull(ReflectionExpressions.TryGetFieldName((Expression>)null)); 154 | } 155 | 156 | [TestMethod] 157 | public void TryGetFieldNameNullTest3() 158 | { 159 | Assert.IsNull(ReflectionExpressions.TryGetFieldName((LambdaExpression)null)); 160 | } 161 | 162 | 163 | // When using interface and new() generic constraints, expression contains Convert to the interface. 164 | private void TryGetPropertyNameGeneric() 165 | where T : ITest1, new() 166 | { 167 | Expression> expression = test => test.Y; 168 | Assert.AreEqual("Y", ReflectionExpressions.TryGetPropertyName(expression)); 169 | } 170 | 171 | 172 | private interface ITest1 173 | { 174 | int Y { get; } 175 | Test2 Z { get; } 176 | } 177 | 178 | private class Test2 179 | { 180 | public int y = 1; 181 | 182 | public int X { get; set; } 183 | 184 | public int this[string key] 185 | { 186 | get { return key == null ? 0 : key.Length; } 187 | } 188 | } 189 | 190 | private class Test3 : Test2, ITest1 191 | { 192 | int ITest1.Y 193 | { 194 | get 195 | { 196 | return 0; 197 | } 198 | } 199 | 200 | Test2 ITest1.Z 201 | { 202 | get 203 | { 204 | return null; 205 | } 206 | } 207 | } 208 | 209 | private abstract class Test8 210 | { 211 | public static void Method4() 212 | { 213 | } 214 | 215 | public static int Method5() 216 | { 217 | return 0; 218 | } 219 | 220 | 221 | public abstract int Method1(); 222 | public abstract int Method2(string x, int y); 223 | public abstract void Method3(); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionExpander.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace Mindbox.Expressions 10 | { 11 | internal sealed class ExpressionExpander : ExpressionVisitor 12 | { 13 | private static readonly MethodInfo MethodInfoCreateDelegateMethod = 14 | #if NET45 || SL5 || CORE45 || WP8 || WINDOWS_PHONE_APP 15 | ReflectionExpressions.GetMethodInfo(methodInfo => 16 | methodInfo.CreateDelegate(default(Type), default(object))); 17 | #else 18 | typeof(MethodInfo).GetMethod( 19 | "CreateDelegate", 20 | new[] 21 | { 22 | typeof(Type), 23 | typeof(object) 24 | }); 25 | #endif 26 | 27 | private static readonly MethodInfo DelegateCreateDelegateMethod = 28 | #if NET35 || SL3 || WINDOWS_PHONE || PORTABLE36 || PORTABLE88 || PORTABLE328 29 | ReflectionExpressions.GetMethodInfo(() => 30 | Delegate.CreateDelegate(default(Type), default(object), default(MethodInfo))); 31 | #else 32 | typeof(Delegate) 33 | .GetTypeInfo() 34 | .GetDeclaredMethods("CreateDelegate") 35 | .SingleOrDefault(method => method 36 | .GetParameters() 37 | .Select(parameter => parameter.ParameterType) 38 | .SequenceEqual(new[] 39 | { 40 | typeof(Type), 41 | typeof(object), 42 | typeof(MethodInfo) 43 | })); 44 | #endif 45 | 46 | private static readonly string EvaluateMethodName = 47 | ReflectionExpressions.GetMethodName>>(expression => expression.Evaluate()); 48 | 49 | private static readonly string InvokeMethodName = 50 | ReflectionExpressions.GetMethodName(action => action.Invoke()); 51 | 52 | private static readonly string CompileMethodName = 53 | ReflectionExpressions.GetMethodName>>(expression => expression.Compile()); 54 | 55 | 56 | public static Expression ExpandExpression(Expression expression) 57 | { 58 | if (expression == null) 59 | throw new ArgumentNullException("expression"); 60 | 61 | return new ExpressionExpander().Visit(expression); 62 | } 63 | 64 | 65 | private static LambdaExpression GetLambdaExpressionFromExpression(Expression expression) 66 | { 67 | if (expression == null) 68 | throw new ArgumentNullException("expression"); 69 | 70 | if (expression.NodeType == ExpressionType.Quote) 71 | return (LambdaExpression)((UnaryExpression)expression).Operand; 72 | 73 | if (ExpressionParameterPresenceDetector.DoesExpressionHaveParameters(expression)) 74 | throw new InvalidOperationException( 75 | "Expression isn't expandable due to usage of " + 76 | $"{nameof(Extensions.Evaluate)} or {nameof(LambdaExpression.Compile)} on expression, " + 77 | "that can't be obtained because it depends on outer lambda expression parameter."); 78 | 79 | var result = (LambdaExpression)ExpressionsConfiguration.ExpressionEvaluatorFactory().Evaluate(expression); 80 | if (result == null) 81 | throw new InvalidOperationException($"Usage of {nameof(Extensions.Evaluate)} on null expression is invalid"); 82 | 83 | return result; 84 | } 85 | 86 | private static bool IsEvaluateMethod(MethodInfo method) 87 | { 88 | if (method == null) 89 | throw new ArgumentNullException("method"); 90 | 91 | return (method.DeclaringType == typeof(Extensions)) && (method.Name == EvaluateMethodName); 92 | } 93 | 94 | private static bool IsCompileMethod(MethodInfo method) 95 | { 96 | if (method == null) 97 | throw new ArgumentNullException("method"); 98 | 99 | return (method.DeclaringType != null) && 100 | #if NET45 || CORE45 || WINDOWS_PHONE_APP 101 | method.DeclaringType.IsConstructedGenericType && 102 | #else 103 | method.DeclaringType.IsGenericType && 104 | !method.DeclaringType.IsGenericTypeDefinition && 105 | #endif 106 | (method.DeclaringType.GetGenericTypeDefinition() == typeof(Expression<>)) && 107 | (method.Name == CompileMethodName); 108 | } 109 | 110 | 111 | private ExpressionExpander() { } 112 | 113 | 114 | protected override Expression VisitInvocation(InvocationExpression node) 115 | { 116 | if (node == null) 117 | throw new ArgumentNullException("node"); 118 | 119 | var baseResult = (InvocationExpression)base.VisitInvocation(node); 120 | 121 | if (baseResult.Expression.NodeType == ExpressionType.Call) 122 | { 123 | var methodCallExpression = (MethodCallExpression)baseResult.Expression; 124 | 125 | if (IsCompileMethod(methodCallExpression.Method)) 126 | { 127 | return SubstituteExpression(methodCallExpression.Object, baseResult.Arguments); 128 | } 129 | } 130 | 131 | return baseResult; 132 | } 133 | 134 | protected override Expression VisitMethodCall(MethodCallExpression node) 135 | { 136 | if (node == null) 137 | throw new ArgumentNullException("node"); 138 | 139 | var baseResult = (MethodCallExpression)base.VisitMethodCall(node); 140 | 141 | if (IsEvaluateMethod(baseResult.Method)) 142 | { 143 | return SubstituteExpression(baseResult.Arguments[0], baseResult.Arguments.Skip(1).ToList()); 144 | } 145 | 146 | if ((baseResult.Method.DeclaringType != null) && 147 | #if NET35 || SL3 || WINDOWS_PHONE || PORTABLE36 || PORTABLE88 || PORTABLE328 148 | (baseResult.Method.DeclaringType.BaseType == 149 | #else 150 | (baseResult.Method.DeclaringType.GetTypeInfo().BaseType == 151 | #endif 152 | typeof(MulticastDelegate)) && 153 | (baseResult.Method.Name == InvokeMethodName) && 154 | (baseResult.Object != null) && 155 | (baseResult.Object.NodeType == ExpressionType.Call)) 156 | { 157 | var methodCallExpression = (MethodCallExpression)baseResult.Object; 158 | 159 | if (IsCompileMethod(methodCallExpression.Method)) 160 | { 161 | return SubstituteExpression(methodCallExpression.Object, baseResult.Arguments); 162 | } 163 | } 164 | 165 | if ((baseResult.Method == MethodInfoCreateDelegateMethod) && (baseResult.Object.NodeType == ExpressionType.Constant)) 166 | { 167 | var constantExpression = (ConstantExpression)baseResult.Object; 168 | if (IsEvaluateMethod((MethodInfo)constantExpression.Value)) 169 | { 170 | var innerExpression = GetLambdaExpressionFromExpression(baseResult.Arguments[1]); 171 | 172 | return Visit(ExpressionParameterSubstitutor.SubstituteParameters( 173 | innerExpression, 174 | new Dictionary())); 175 | } 176 | } 177 | 178 | if ((baseResult.Method == DelegateCreateDelegateMethod) && 179 | (baseResult.Arguments[2].NodeType == ExpressionType.Constant)) 180 | { 181 | var constantExpression = (ConstantExpression)baseResult.Arguments[2]; 182 | if (IsEvaluateMethod((MethodInfo)constantExpression.Value)) 183 | { 184 | var innerExpression = GetLambdaExpressionFromExpression(baseResult.Arguments[1]); 185 | 186 | return Visit(ExpressionParameterSubstitutor.SubstituteParameters( 187 | innerExpression, 188 | new Dictionary())); 189 | } 190 | } 191 | 192 | return baseResult; 193 | } 194 | 195 | protected override Expression VisitUnary(UnaryExpression node) 196 | { 197 | if (node == null) 198 | throw new ArgumentNullException("node"); 199 | 200 | var baseResult = base.VisitUnary(node); 201 | if (baseResult.NodeType == ExpressionType.Convert) 202 | { 203 | var baseResultUnary = (UnaryExpression)baseResult; 204 | if ((baseResultUnary.Type == baseResultUnary.Operand.Type) && 205 | (baseResultUnary.Method == null) && 206 | !baseResultUnary.IsLifted && 207 | !baseResultUnary.IsLiftedToNull) 208 | return baseResultUnary.Operand; 209 | } 210 | 211 | return baseResult; 212 | } 213 | 214 | private Expression SubstituteExpression( 215 | Expression expressionExpression, 216 | #if NET45 || CORE45 || WINDOWS_PHONE_APP 217 | IReadOnlyList arguments 218 | #else 219 | IList arguments 220 | #endif 221 | ) 222 | { 223 | if (expressionExpression == null) 224 | throw new ArgumentNullException("expressionExpression"); 225 | if (arguments == null) 226 | throw new ArgumentNullException("arguments"); 227 | 228 | var lambdaExpression = GetLambdaExpressionFromExpression(expressionExpression); 229 | 230 | if (lambdaExpression.Parameters.Count != arguments.Count) 231 | throw new ArgumentException("Argument count doesn't match parameter count."); 232 | 233 | var visitedLambdaExpression = (LambdaExpression)Visit(lambdaExpression); 234 | 235 | var parameterSubstitutions = new Dictionary(); 236 | for (var parameterIndex = 0; 237 | parameterIndex < visitedLambdaExpression.Parameters.Count; 238 | parameterIndex++) 239 | { 240 | var originalParameter = visitedLambdaExpression.Parameters[parameterIndex]; 241 | var replacedParameter = arguments[parameterIndex]; 242 | parameterSubstitutions.Add(originalParameter, replacedParameter); 243 | } 244 | 245 | return Visit(ExpressionParameterSubstitutor.SubstituteParameters( 246 | visitedLambdaExpression.Body, 247 | parameterSubstitutions)); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | 8 | namespace Mindbox.Expressions 9 | { 10 | #if !NET40 && !SL4 && !CORE45 && !WP8 && !WINDOWS_PHONE_APP && !PORTABLE36 && !PORTABLE328 11 | /// 12 | /// Represents a visitor or rewriter for expression trees. 13 | /// Not present in .NET 4.0 and higher versions of the library 14 | /// (use System.Linq.Expressions.ExpressionVisitor from System.Core.dll). 15 | /// 16 | /// 17 | /// From MSDN: http://msdn.microsoft.com/en-us/library/bb882521(v=vs.90).aspx 18 | /// 19 | public abstract class ExpressionVisitor 20 | { 21 | /// 22 | /// Dispatches the expression to one of the more specialized visit methods in this class. 23 | /// 24 | /// The expression to visit. 25 | /// The modified expression, if it or any subexpression was modified; 26 | /// otherwise, returns the original expression. 27 | protected virtual Expression Visit(Expression exp) 28 | { 29 | if (exp == null) 30 | return null; 31 | 32 | switch (exp.NodeType) 33 | { 34 | case ExpressionType.Negate: 35 | case ExpressionType.NegateChecked: 36 | case ExpressionType.Not: 37 | case ExpressionType.Convert: 38 | case ExpressionType.ConvertChecked: 39 | case ExpressionType.ArrayLength: 40 | case ExpressionType.Quote: 41 | case ExpressionType.TypeAs: 42 | return VisitUnary((UnaryExpression)exp); 43 | 44 | case ExpressionType.Add: 45 | case ExpressionType.AddChecked: 46 | case ExpressionType.Subtract: 47 | case ExpressionType.SubtractChecked: 48 | case ExpressionType.Multiply: 49 | case ExpressionType.MultiplyChecked: 50 | case ExpressionType.Divide: 51 | case ExpressionType.Modulo: 52 | case ExpressionType.And: 53 | case ExpressionType.AndAlso: 54 | case ExpressionType.Or: 55 | case ExpressionType.OrElse: 56 | case ExpressionType.LessThan: 57 | case ExpressionType.LessThanOrEqual: 58 | case ExpressionType.GreaterThan: 59 | case ExpressionType.GreaterThanOrEqual: 60 | case ExpressionType.Equal: 61 | case ExpressionType.NotEqual: 62 | case ExpressionType.Coalesce: 63 | case ExpressionType.ArrayIndex: 64 | case ExpressionType.RightShift: 65 | case ExpressionType.LeftShift: 66 | case ExpressionType.ExclusiveOr: 67 | return VisitBinary((BinaryExpression)exp); 68 | 69 | case ExpressionType.TypeIs: 70 | return VisitTypeIs((TypeBinaryExpression)exp); 71 | 72 | case ExpressionType.Conditional: 73 | return VisitConditional((ConditionalExpression)exp); 74 | 75 | case ExpressionType.Constant: 76 | return VisitConstant((ConstantExpression)exp); 77 | 78 | case ExpressionType.Parameter: 79 | return VisitParameter((ParameterExpression)exp); 80 | 81 | case ExpressionType.MemberAccess: 82 | return VisitMemberAccess((MemberExpression)exp); 83 | 84 | case ExpressionType.Call: 85 | return VisitMethodCall((MethodCallExpression)exp); 86 | 87 | case ExpressionType.Lambda: 88 | return VisitLambda((LambdaExpression)exp); 89 | 90 | case ExpressionType.New: 91 | return VisitNew((NewExpression)exp); 92 | 93 | case ExpressionType.NewArrayInit: 94 | case ExpressionType.NewArrayBounds: 95 | return VisitNewArray((NewArrayExpression)exp); 96 | 97 | case ExpressionType.Invoke: 98 | return VisitInvocation((InvocationExpression)exp); 99 | 100 | case ExpressionType.MemberInit: 101 | return VisitMemberInit((MemberInitExpression)exp); 102 | 103 | case ExpressionType.ListInit: 104 | return VisitListInit((ListInitExpression)exp); 105 | 106 | // Expression types can be extended by third-party libraries (for example, EntityFramework Core). 107 | // These extensions are assumed not expandable, but valid. 108 | case ExpressionType.Extension: 109 | return exp; 110 | 111 | default: 112 | throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType)); 113 | } 114 | } 115 | 116 | /// 117 | /// Visits the children of the MemberBinding. 118 | /// 119 | /// The expression to visit. 120 | /// The modified expression, if it or any subexpression was modified; 121 | /// otherwise, returns the original expression. 122 | protected virtual MemberBinding VisitBinding(MemberBinding binding) 123 | { 124 | if (binding == null) 125 | throw new ArgumentNullException("binding"); 126 | 127 | switch (binding.BindingType) 128 | { 129 | case MemberBindingType.Assignment: 130 | return VisitMemberAssignment((MemberAssignment)binding); 131 | 132 | case MemberBindingType.MemberBinding: 133 | return VisitMemberMemberBinding((MemberMemberBinding)binding); 134 | 135 | case MemberBindingType.ListBinding: 136 | return VisitMemberListBinding((MemberListBinding)binding); 137 | 138 | default: 139 | throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType)); 140 | } 141 | } 142 | 143 | /// 144 | /// Visits the children of the ElementInit. 145 | /// 146 | /// The expression to visit. 147 | /// The modified expression, if it or any subexpression was modified; 148 | /// otherwise, returns the original expression. 149 | protected virtual ElementInit VisitElementInitializer(ElementInit initializer) 150 | { 151 | if (initializer == null) 152 | throw new ArgumentNullException("initializer"); 153 | 154 | var arguments = VisitExpressionList(initializer.Arguments); 155 | return arguments == initializer.Arguments ? initializer : Expression.ElementInit(initializer.AddMethod, arguments); 156 | } 157 | 158 | /// 159 | /// Visits the children of the UnaryExpression. 160 | /// 161 | /// The expression to visit. 162 | /// The modified expression, if it or any subexpression was modified; 163 | /// otherwise, returns the original expression. 164 | protected virtual Expression VisitUnary(UnaryExpression u) 165 | { 166 | if (u == null) 167 | throw new ArgumentNullException("u"); 168 | 169 | var operand = Visit(u.Operand); 170 | return operand == u.Operand ? u : Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method); 171 | } 172 | 173 | /// 174 | /// Visits the children of the BinaryExpression. 175 | /// 176 | /// The expression to visit. 177 | /// The modified expression, if it or any subexpression was modified; 178 | /// otherwise, returns the original expression. 179 | protected virtual Expression VisitBinary(BinaryExpression b) 180 | { 181 | if (b == null) 182 | throw new ArgumentNullException("b"); 183 | 184 | var left = Visit(b.Left); 185 | var right = Visit(b.Right); 186 | var conversion = Visit(b.Conversion); 187 | if (left == b.Left && right == b.Right && conversion == b.Conversion) 188 | return b; 189 | if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) 190 | return Expression.Coalesce(left, right, conversion as LambdaExpression); 191 | return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); 192 | } 193 | 194 | /// 195 | /// Visits the children of the TypeBinaryExpression. 196 | /// 197 | /// The expression to visit. 198 | /// The modified expression, if it or any subexpression was modified; 199 | /// otherwise, returns the original expression. 200 | protected virtual Expression VisitTypeIs(TypeBinaryExpression b) 201 | { 202 | if (b == null) 203 | throw new ArgumentNullException("b"); 204 | 205 | var expr = Visit(b.Expression); 206 | return expr == b.Expression ? b : Expression.TypeIs(expr, b.TypeOperand); 207 | } 208 | 209 | /// 210 | /// Visits the ConstantExpression. 211 | /// 212 | /// The expression to visit. 213 | /// The modified expression, if it or any subexpression was modified; 214 | /// otherwise, returns the original expression. 215 | protected virtual Expression VisitConstant(ConstantExpression c) 216 | { 217 | if (c == null) 218 | throw new ArgumentNullException("c"); 219 | 220 | return c; 221 | } 222 | 223 | /// 224 | /// Visits the children of the ConditionalExpression. 225 | /// 226 | /// The expression to visit. 227 | /// The modified expression, if it or any subexpression was modified; 228 | /// otherwise, returns the original expression. 229 | protected virtual Expression VisitConditional(ConditionalExpression c) 230 | { 231 | if (c == null) 232 | throw new ArgumentNullException("c"); 233 | 234 | var test = Visit(c.Test); 235 | var ifTrue = Visit(c.IfTrue); 236 | var ifFalse = Visit(c.IfFalse); 237 | return test == c.Test && ifTrue == c.IfTrue && ifFalse == c.IfFalse ? c : Expression.Condition(test, ifTrue, ifFalse); 238 | } 239 | 240 | /// 241 | /// Visits the ParameterExpression. 242 | /// 243 | /// The expression to visit. 244 | /// The modified expression, if it or any subexpression was modified; 245 | /// otherwise, returns the original expression. 246 | protected virtual Expression VisitParameter(ParameterExpression p) 247 | { 248 | if (p == null) 249 | throw new ArgumentNullException("p"); 250 | 251 | return p; 252 | } 253 | 254 | /// 255 | /// Visits the children of the MemberExpression. 256 | /// 257 | /// The expression to visit. 258 | /// The modified expression, if it or any subexpression was modified; 259 | /// otherwise, returns the original expression. 260 | protected virtual Expression VisitMemberAccess(MemberExpression m) 261 | { 262 | if (m == null) 263 | throw new ArgumentNullException("m"); 264 | 265 | var exp = Visit(m.Expression); 266 | return exp == m.Expression ? m : Expression.MakeMemberAccess(exp, m.Member); 267 | } 268 | 269 | /// 270 | /// Visits the children of the MethodCallExpression. 271 | /// 272 | /// The expression to visit. 273 | /// The modified expression, if it or any subexpression was modified; 274 | /// otherwise, returns the original expression. 275 | protected virtual Expression VisitMethodCall(MethodCallExpression m) 276 | { 277 | if (m == null) 278 | throw new ArgumentNullException("m"); 279 | 280 | var obj = Visit(m.Object); 281 | var args = VisitExpressionList(m.Arguments); 282 | return obj == m.Object && args == m.Arguments ? m : Expression.Call(obj, m.Method, args); 283 | } 284 | 285 | /// 286 | /// Dispatches the list of expressions to one of the more specialized visit methods in this class. 287 | /// 288 | /// The expressions to visit. 289 | /// The modified expression list, if any one of the elements were modified; 290 | /// otherwise, returns the original expression list. 291 | protected virtual ReadOnlyCollection VisitExpressionList(ReadOnlyCollection original) 292 | { 293 | if (original == null) 294 | throw new ArgumentNullException("original"); 295 | 296 | List list = null; 297 | for (int i = 0, n = original.Count; i < n; i++) 298 | { 299 | var p = Visit(original[i]); 300 | if (list != null) 301 | { 302 | list.Add(p); 303 | } 304 | else if (p != original[i]) 305 | { 306 | list = new List(n); 307 | for (var j = 0; j < i; j++) 308 | { 309 | list.Add(original[j]); 310 | } 311 | list.Add(p); 312 | } 313 | } 314 | return list == null ? original : new ReadOnlyCollection(list); 315 | } 316 | 317 | /// 318 | /// Visits the children of the MemberAssignment. 319 | /// 320 | /// The expression to visit. 321 | /// The modified expression, if it or any subexpression was modified; 322 | /// otherwise, returns the original expression. 323 | protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) 324 | { 325 | if (assignment == null) 326 | throw new ArgumentNullException("assignment"); 327 | 328 | var e = Visit(assignment.Expression); 329 | return e == assignment.Expression ? assignment : Expression.Bind(assignment.Member, e); 330 | } 331 | 332 | /// 333 | /// Visits the children of the MemberMemberBinding. 334 | /// 335 | /// The expression to visit. 336 | /// The modified expression, if it or any subexpression was modified; 337 | /// otherwise, returns the original expression. 338 | protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) 339 | { 340 | if (binding == null) 341 | throw new ArgumentNullException("binding"); 342 | 343 | var bindings = VisitBindingList(binding.Bindings); 344 | return bindings == binding.Bindings ? binding : Expression.MemberBind(binding.Member, bindings); 345 | } 346 | 347 | /// 348 | /// Visits the children of the MemberListBinding. 349 | /// 350 | /// The expression to visit. 351 | /// The modified expression, if it or any subexpression was modified; 352 | /// otherwise, returns the original expression. 353 | protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) 354 | { 355 | if (binding == null) 356 | throw new ArgumentNullException("binding"); 357 | 358 | var initializers = VisitElementInitializerList(binding.Initializers); 359 | return initializers == binding.Initializers ? binding : Expression.ListBind(binding.Member, initializers); 360 | } 361 | 362 | /// 363 | /// Visits all MemberBinding nodes in the collection using VisitBinding method. 364 | /// 365 | /// The nodes to visit. 366 | /// The modified node list, if any of the elements were modified; 367 | /// otherwise, returns the original node list. 368 | protected virtual IEnumerable VisitBindingList(ReadOnlyCollection original) 369 | { 370 | if (original == null) 371 | throw new ArgumentNullException("original"); 372 | 373 | List list = null; 374 | for (int i = 0, n = original.Count; i < n; i++) 375 | { 376 | var b = VisitBinding(original[i]); 377 | if (list != null) 378 | { 379 | list.Add(b); 380 | } 381 | else if (b != original[i]) 382 | { 383 | list = new List(n); 384 | for (var j = 0; j < i; j++) 385 | { 386 | list.Add(original[j]); 387 | } 388 | list.Add(b); 389 | } 390 | } 391 | if (list == null) 392 | return original; 393 | return list; 394 | } 395 | 396 | /// 397 | /// Visits all ElementInit nodes in the collection using VisitElementInitializer method. 398 | /// 399 | /// The nodes to visit. 400 | /// The modified node list, if any of the elements were modified; 401 | /// otherwise, returns the original node list. 402 | protected virtual IEnumerable VisitElementInitializerList(ReadOnlyCollection original) 403 | { 404 | if (original == null) 405 | throw new ArgumentNullException("original"); 406 | 407 | List list = null; 408 | for (int i = 0, n = original.Count; i < n; i++) 409 | { 410 | var init = VisitElementInitializer(original[i]); 411 | if (list != null) 412 | { 413 | list.Add(init); 414 | } 415 | else if (init != original[i]) 416 | { 417 | list = new List(n); 418 | for (var j = 0; j < i; j++) 419 | { 420 | list.Add(original[j]); 421 | } 422 | list.Add(init); 423 | } 424 | } 425 | if (list != null) 426 | return list; 427 | return original; 428 | } 429 | 430 | /// 431 | /// Visits the children of the LambdaExpression. 432 | /// 433 | /// The expression to visit. 434 | /// The modified expression, if it or any subexpression was modified; 435 | /// otherwise, returns the original expression. 436 | protected virtual Expression VisitLambda(LambdaExpression lambda) 437 | { 438 | if (lambda == null) 439 | throw new ArgumentNullException("lambda"); 440 | 441 | var body = Visit(lambda.Body); 442 | return body == lambda.Body ? lambda : Expression.Lambda(lambda.Type, body, lambda.Parameters); 443 | } 444 | 445 | /// 446 | /// Visits the children of the NewExpression. 447 | /// 448 | /// The expression to visit. 449 | /// The modified expression, if it or any subexpression was modified; 450 | /// otherwise, returns the original expression. 451 | protected virtual NewExpression VisitNew(NewExpression nex) 452 | { 453 | if (nex == null) 454 | throw new ArgumentNullException("nex"); 455 | 456 | IEnumerable args = VisitExpressionList(nex.Arguments); 457 | if (args == nex.Arguments) 458 | return nex; 459 | return nex.Members == null ? 460 | Expression.New(nex.Constructor, args) : 461 | Expression.New(nex.Constructor, args, nex.Members); 462 | } 463 | 464 | /// 465 | /// Visits the children of the MemberInitExpression. 466 | /// 467 | /// The expression to visit. 468 | /// The modified expression, if it or any subexpression was modified; 469 | /// otherwise, returns the original expression. 470 | protected virtual Expression VisitMemberInit(MemberInitExpression init) 471 | { 472 | if (init == null) 473 | throw new ArgumentNullException("init"); 474 | 475 | var n = VisitNew(init.NewExpression); 476 | var bindings = VisitBindingList(init.Bindings); 477 | if (n == init.NewExpression && bindings == init.Bindings) 478 | return init; 479 | return Expression.MemberInit(n, bindings); 480 | } 481 | 482 | /// 483 | /// Visits the children of the ListInitExpression. 484 | /// 485 | /// The expression to visit. 486 | /// The modified expression, if it or any subexpression was modified; 487 | /// otherwise, returns the original expression. 488 | protected virtual Expression VisitListInit(ListInitExpression init) 489 | { 490 | if (init == null) 491 | throw new ArgumentNullException("init"); 492 | 493 | var n = VisitNew(init.NewExpression); 494 | var initializers = VisitElementInitializerList(init.Initializers); 495 | if (n == init.NewExpression && initializers == init.Initializers) 496 | return init; 497 | return Expression.ListInit(n, initializers); 498 | } 499 | 500 | /// 501 | /// Visits the children of the NewArrayExpression. 502 | /// 503 | /// The expression to visit. 504 | /// The modified expression, if it or any subexpression was modified; 505 | /// otherwise, returns the original expression. 506 | protected virtual Expression VisitNewArray(NewArrayExpression na) 507 | { 508 | if (na == null) 509 | throw new ArgumentNullException("na"); 510 | 511 | var exprs = VisitExpressionList(na.Expressions); 512 | if (exprs == na.Expressions) 513 | return na; 514 | if (na.NodeType == ExpressionType.NewArrayInit) 515 | return Expression.NewArrayInit(na.Type.GetElementType(), exprs); 516 | return Expression.NewArrayBounds(na.Type.GetElementType(), exprs); 517 | } 518 | 519 | /// 520 | /// Visits the children of the InvocationExpression. 521 | /// 522 | /// The expression to visit. 523 | /// The modified expression, if it or any subexpression was modified; 524 | /// otherwise, returns the original expression. 525 | protected virtual Expression VisitInvocation(InvocationExpression iv) 526 | { 527 | if (iv == null) 528 | throw new ArgumentNullException("iv"); 529 | 530 | var args = VisitExpressionList(iv.Arguments); 531 | var expr = Visit(iv.Expression); 532 | if (args == iv.Arguments && expr == iv.Expression) 533 | return iv; 534 | return Expression.Invoke(expr, args); 535 | } 536 | } 537 | #endif 538 | } 539 | -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/BooleanExpressionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | #if NETFX_CORE || WINDOWS_PHONE 7 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 8 | #else 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | #endif 11 | 12 | namespace Mindbox.Expressions.Tests 13 | { 14 | [TestClass] 15 | public class BooleanExpressionsTests 16 | { 17 | [TestMethod] 18 | public void AndAlso0ParameterTest() 19 | { 20 | foreach (var value1 in new[] 21 | { 22 | false, 23 | true 24 | }) 25 | { 26 | foreach (var value2 in new[] 27 | { 28 | false, 29 | true 30 | }) 31 | { 32 | Expression> expression1 = () => value1; 33 | var result = expression1.AndAlso(() => value2); 34 | Assert.AreEqual(value1 && value2, result.Evaluate()); 35 | } 36 | } 37 | } 38 | 39 | [TestMethod] 40 | public void AndAlso1ParameterTest() 41 | { 42 | foreach (var value1 in new[] 43 | { 44 | false, 45 | true 46 | }) 47 | { 48 | foreach (var value2 in new[] 49 | { 50 | false, 51 | true 52 | }) 53 | { 54 | Expression> expression1 = x1 => value1; 55 | var result = expression1.AndAlso(x1 => value2); 56 | Assert.AreEqual(value1 && value2, result.Evaluate(1)); 57 | } 58 | } 59 | } 60 | 61 | [TestMethod] 62 | public void AndAlso2ParameterTest() 63 | { 64 | foreach (var value1 in new[] 65 | { 66 | false, 67 | true 68 | }) 69 | { 70 | foreach (var value2 in new[] 71 | { 72 | false, 73 | true 74 | }) 75 | { 76 | Expression> expression1 = (x1, x2) => value1; 77 | var result = expression1.AndAlso((x1, x2) => value2); 78 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2)); 79 | } 80 | } 81 | } 82 | 83 | [TestMethod] 84 | public void AndAlso3ParameterTest() 85 | { 86 | foreach (var value1 in new[] 87 | { 88 | false, 89 | true 90 | }) 91 | { 92 | foreach (var value2 in new[] 93 | { 94 | false, 95 | true 96 | }) 97 | { 98 | Expression> expression1 = (x1, x2, x3) => value1; 99 | var result = expression1.AndAlso((x1, x2, x3) => value2); 100 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3)); 101 | } 102 | } 103 | } 104 | 105 | [TestMethod] 106 | public void AndAlso4ParameterTest() 107 | { 108 | foreach (var value1 in new[] 109 | { 110 | false, 111 | true 112 | }) 113 | { 114 | foreach (var value2 in new[] 115 | { 116 | false, 117 | true 118 | }) 119 | { 120 | Expression> expression1 = (x1, x2, x3, x4) => value1; 121 | var result = expression1.AndAlso((x1, x2, x3, x4) => value2); 122 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4)); 123 | } 124 | } 125 | } 126 | 127 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 128 | [TestMethod] 129 | public void AndAlso5ParameterTest() 130 | { 131 | foreach (var value1 in new[] 132 | { 133 | false, 134 | true 135 | }) 136 | { 137 | foreach (var value2 in new[] 138 | { 139 | false, 140 | true 141 | }) 142 | { 143 | Expression> expression1 = (x1, x2, x3, x4, x5) => value1; 144 | var result = expression1.AndAlso((x1, x2, x3, x4, x5) => value2); 145 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5)); 146 | } 147 | } 148 | } 149 | 150 | [TestMethod] 151 | public void AndAlso6ParameterTest() 152 | { 153 | foreach (var value1 in new[] 154 | { 155 | false, 156 | true 157 | }) 158 | { 159 | foreach (var value2 in new[] 160 | { 161 | false, 162 | true 163 | }) 164 | { 165 | Expression> expression1 = (x1, x2, x3, x4, x5, x6) => value1; 166 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6) => value2); 167 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6)); 168 | } 169 | } 170 | } 171 | 172 | [TestMethod] 173 | public void AndAlso7ParameterTest() 174 | { 175 | foreach (var value1 in new[] 176 | { 177 | false, 178 | true 179 | }) 180 | { 181 | foreach (var value2 in new[] 182 | { 183 | false, 184 | true 185 | }) 186 | { 187 | Expression> expression1 = 188 | (x1, x2, x3, x4, x5, x6, x7) => value1; 189 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7) => value2); 190 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7)); 191 | } 192 | } 193 | } 194 | 195 | [TestMethod] 196 | public void AndAlso8ParameterTest() 197 | { 198 | foreach (var value1 in new[] 199 | { 200 | false, 201 | true 202 | }) 203 | { 204 | foreach (var value2 in new[] 205 | { 206 | false, 207 | true 208 | }) 209 | { 210 | Expression> expression1 = 211 | (x1, x2, x3, x4, x5, x6, x7, x8) => value1; 212 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8) => value2); 213 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8)); 214 | } 215 | } 216 | } 217 | 218 | [TestMethod] 219 | public void AndAlso9ParameterTest() 220 | { 221 | foreach (var value1 in new[] 222 | { 223 | false, 224 | true 225 | }) 226 | { 227 | foreach (var value2 in new[] 228 | { 229 | false, 230 | true 231 | }) 232 | { 233 | Expression> expression1 = 234 | (x1, x2, x3, x4, x5, x6, x7, x8, x9) => value1; 235 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9) => value2); 236 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9)); 237 | } 238 | } 239 | } 240 | 241 | [TestMethod] 242 | public void AndAlso10ParameterTest() 243 | { 244 | foreach (var value1 in new[] 245 | { 246 | false, 247 | true 248 | }) 249 | { 250 | foreach (var value2 in new[] 251 | { 252 | false, 253 | true 254 | }) 255 | { 256 | Expression> expression1 = 257 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => value1; 258 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => value2); 259 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 260 | } 261 | } 262 | } 263 | 264 | [TestMethod] 265 | public void AndAlso11ParameterTest() 266 | { 267 | foreach (var value1 in new[] 268 | { 269 | false, 270 | true 271 | }) 272 | { 273 | foreach (var value2 in new[] 274 | { 275 | false, 276 | true 277 | }) 278 | { 279 | Expression> expression1 = 280 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => value1; 281 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => value2); 282 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); 283 | } 284 | } 285 | } 286 | 287 | [TestMethod] 288 | public void AndAlso12ParameterTest() 289 | { 290 | foreach (var value1 in new[] 291 | { 292 | false, 293 | true 294 | }) 295 | { 296 | foreach (var value2 in new[] 297 | { 298 | false, 299 | true 300 | }) 301 | { 302 | Expression> expression1 = 303 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => value1; 304 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => value2); 305 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 306 | } 307 | } 308 | } 309 | 310 | [TestMethod] 311 | public void AndAlso13ParameterTest() 312 | { 313 | foreach (var value1 in new[] 314 | { 315 | false, 316 | true 317 | }) 318 | { 319 | foreach (var value2 in new[] 320 | { 321 | false, 322 | true 323 | }) 324 | { 325 | Expression> expression1 = 326 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => value1; 327 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => value2); 328 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); 329 | } 330 | } 331 | } 332 | 333 | [TestMethod] 334 | public void AndAlso14ParameterTest() 335 | { 336 | foreach (var value1 in new[] 337 | { 338 | false, 339 | true 340 | }) 341 | { 342 | foreach (var value2 in new[] 343 | { 344 | false, 345 | true 346 | }) 347 | { 348 | Expression> expression1 = 349 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => value1; 350 | var result = expression1.AndAlso((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => value2); 351 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); 352 | } 353 | } 354 | } 355 | 356 | [TestMethod] 357 | public void AndAlso15ParameterTest() 358 | { 359 | foreach (var value1 in new[] 360 | { 361 | false, 362 | true 363 | }) 364 | { 365 | foreach (var value2 in new[] 366 | { 367 | false, 368 | true 369 | }) 370 | { 371 | Expression> 372 | expression1 = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => value1; 373 | var result = expression1.AndAlso( 374 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => value2); 375 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); 376 | } 377 | } 378 | } 379 | 380 | [TestMethod] 381 | public void AndAlso16ParameterTest() 382 | { 383 | foreach (var value1 in new[] 384 | { 385 | false, 386 | true 387 | }) 388 | { 389 | foreach (var value2 in new[] 390 | { 391 | false, 392 | true 393 | }) 394 | { 395 | Expression> 396 | expression1 = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => value1; 397 | var result = expression1.AndAlso( 398 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => value2); 399 | Assert.AreEqual(value1 && value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 400 | } 401 | } 402 | } 403 | #endif 404 | 405 | [TestMethod] 406 | public void AndAlso2ParameterReplacementTest() 407 | { 408 | foreach (var value1 in new[] 409 | { 410 | false, 411 | true 412 | }) 413 | { 414 | foreach (var value2 in new[] 415 | { 416 | false, 417 | true 418 | }) 419 | { 420 | Expression> expression1 = (x1, x2) => x1; 421 | var result = expression1.AndAlso((x1, x2) => x2); 422 | Assert.AreEqual(value1 && value2, result.Evaluate(value1, value2)); 423 | } 424 | } 425 | } 426 | 427 | [TestMethod] 428 | public void OrElse0ParameterTest() 429 | { 430 | foreach (var value1 in new[] 431 | { 432 | false, 433 | true 434 | }) 435 | { 436 | foreach (var value2 in new[] 437 | { 438 | false, 439 | true 440 | }) 441 | { 442 | Expression> expression1 = () => value1; 443 | var result = expression1.OrElse(() => value2); 444 | Assert.AreEqual(value1 || value2, result.Evaluate()); 445 | } 446 | } 447 | } 448 | 449 | [TestMethod] 450 | public void OrElse1ParameterTest() 451 | { 452 | foreach (var value1 in new[] 453 | { 454 | false, 455 | true 456 | }) 457 | { 458 | foreach (var value2 in new[] 459 | { 460 | false, 461 | true 462 | }) 463 | { 464 | Expression> expression1 = x1 => value1; 465 | var result = expression1.OrElse(x1 => value2); 466 | Assert.AreEqual(value1 || value2, result.Evaluate(1)); 467 | } 468 | } 469 | } 470 | 471 | [TestMethod] 472 | public void OrElse2ParameterTest() 473 | { 474 | foreach (var value1 in new[] 475 | { 476 | false, 477 | true 478 | }) 479 | { 480 | foreach (var value2 in new[] 481 | { 482 | false, 483 | true 484 | }) 485 | { 486 | Expression> expression1 = (x1, x2) => value1; 487 | var result = expression1.OrElse((x1, x2) => value2); 488 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2)); 489 | } 490 | } 491 | } 492 | 493 | [TestMethod] 494 | public void OrElse3ParameterTest() 495 | { 496 | foreach (var value1 in new[] 497 | { 498 | false, 499 | true 500 | }) 501 | { 502 | foreach (var value2 in new[] 503 | { 504 | false, 505 | true 506 | }) 507 | { 508 | Expression> expression1 = (x1, x2, x3) => value1; 509 | var result = expression1.OrElse((x1, x2, x3) => value2); 510 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3)); 511 | } 512 | } 513 | } 514 | 515 | [TestMethod] 516 | public void OrElse4ParameterTest() 517 | { 518 | foreach (var value1 in new[] 519 | { 520 | false, 521 | true 522 | }) 523 | { 524 | foreach (var value2 in new[] 525 | { 526 | false, 527 | true 528 | }) 529 | { 530 | Expression> expression1 = (x1, x2, x3, x4) => value1; 531 | var result = expression1.OrElse((x1, x2, x3, x4) => value2); 532 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4)); 533 | } 534 | } 535 | } 536 | 537 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 538 | [TestMethod] 539 | public void OrElse5ParameterTest() 540 | { 541 | foreach (var value1 in new[] 542 | { 543 | false, 544 | true 545 | }) 546 | { 547 | foreach (var value2 in new[] 548 | { 549 | false, 550 | true 551 | }) 552 | { 553 | Expression> expression1 = (x1, x2, x3, x4, x5) => value1; 554 | var result = expression1.OrElse((x1, x2, x3, x4, x5) => value2); 555 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5)); 556 | } 557 | } 558 | } 559 | 560 | [TestMethod] 561 | public void OrElse6ParameterTest() 562 | { 563 | foreach (var value1 in new[] 564 | { 565 | false, 566 | true 567 | }) 568 | { 569 | foreach (var value2 in new[] 570 | { 571 | false, 572 | true 573 | }) 574 | { 575 | Expression> expression1 = (x1, x2, x3, x4, x5, x6) => value1; 576 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6) => value2); 577 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6)); 578 | } 579 | } 580 | } 581 | 582 | [TestMethod] 583 | public void OrElse7ParameterTest() 584 | { 585 | foreach (var value1 in new[] 586 | { 587 | false, 588 | true 589 | }) 590 | { 591 | foreach (var value2 in new[] 592 | { 593 | false, 594 | true 595 | }) 596 | { 597 | Expression> expression1 = 598 | (x1, x2, x3, x4, x5, x6, x7) => value1; 599 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7) => value2); 600 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7)); 601 | } 602 | } 603 | } 604 | 605 | [TestMethod] 606 | public void OrElse8ParameterTest() 607 | { 608 | foreach (var value1 in new[] 609 | { 610 | false, 611 | true 612 | }) 613 | { 614 | foreach (var value2 in new[] 615 | { 616 | false, 617 | true 618 | }) 619 | { 620 | Expression> expression1 = 621 | (x1, x2, x3, x4, x5, x6, x7, x8) => value1; 622 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8) => value2); 623 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8)); 624 | } 625 | } 626 | } 627 | 628 | [TestMethod] 629 | public void OrElse9ParameterTest() 630 | { 631 | foreach (var value1 in new[] 632 | { 633 | false, 634 | true 635 | }) 636 | { 637 | foreach (var value2 in new[] 638 | { 639 | false, 640 | true 641 | }) 642 | { 643 | Expression> expression1 = 644 | (x1, x2, x3, x4, x5, x6, x7, x8, x9) => value1; 645 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9) => value2); 646 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9)); 647 | } 648 | } 649 | } 650 | 651 | [TestMethod] 652 | public void OrElse10ParameterTest() 653 | { 654 | foreach (var value1 in new[] 655 | { 656 | false, 657 | true 658 | }) 659 | { 660 | foreach (var value2 in new[] 661 | { 662 | false, 663 | true 664 | }) 665 | { 666 | Expression> expression1 = 667 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => value1; 668 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) => value2); 669 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 670 | } 671 | } 672 | } 673 | 674 | [TestMethod] 675 | public void OrElse11ParameterTest() 676 | { 677 | foreach (var value1 in new[] 678 | { 679 | false, 680 | true 681 | }) 682 | { 683 | foreach (var value2 in new[] 684 | { 685 | false, 686 | true 687 | }) 688 | { 689 | Expression> expression1 = 690 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => value1; 691 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) => value2); 692 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); 693 | } 694 | } 695 | } 696 | 697 | [TestMethod] 698 | public void OrElse12ParameterTest() 699 | { 700 | foreach (var value1 in new[] 701 | { 702 | false, 703 | true 704 | }) 705 | { 706 | foreach (var value2 in new[] 707 | { 708 | false, 709 | true 710 | }) 711 | { 712 | Expression> expression1 = 713 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => value1; 714 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => value2); 715 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); 716 | } 717 | } 718 | } 719 | 720 | [TestMethod] 721 | public void OrElse13ParameterTest() 722 | { 723 | foreach (var value1 in new[] 724 | { 725 | false, 726 | true 727 | }) 728 | { 729 | foreach (var value2 in new[] 730 | { 731 | false, 732 | true 733 | }) 734 | { 735 | Expression> expression1 = 736 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => value1; 737 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) => value2); 738 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); 739 | } 740 | } 741 | } 742 | 743 | [TestMethod] 744 | public void OrElse14ParameterTest() 745 | { 746 | foreach (var value1 in new[] 747 | { 748 | false, 749 | true 750 | }) 751 | { 752 | foreach (var value2 in new[] 753 | { 754 | false, 755 | true 756 | }) 757 | { 758 | Expression> expression1 = 759 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => value1; 760 | var result = expression1.OrElse((x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) => value2); 761 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); 762 | } 763 | } 764 | } 765 | 766 | [TestMethod] 767 | public void OrElse15ParameterTest() 768 | { 769 | foreach (var value1 in new[] 770 | { 771 | false, 772 | true 773 | }) 774 | { 775 | foreach (var value2 in new[] 776 | { 777 | false, 778 | true 779 | }) 780 | { 781 | Expression> 782 | expression1 = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => value1; 783 | var result = expression1.OrElse( 784 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) => value2); 785 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); 786 | } 787 | } 788 | } 789 | 790 | [TestMethod] 791 | public void OrElse16ParameterTest() 792 | { 793 | foreach (var value1 in new[] 794 | { 795 | false, 796 | true 797 | }) 798 | { 799 | foreach (var value2 in new[] 800 | { 801 | false, 802 | true 803 | }) 804 | { 805 | Expression> 806 | expression1 = (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => value1; 807 | var result = expression1.OrElse( 808 | (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) => value2); 809 | Assert.AreEqual(value1 || value2, result.Evaluate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); 810 | } 811 | } 812 | } 813 | #endif 814 | 815 | [TestMethod] 816 | public void OrElse2ParameterReplacementTest() 817 | { 818 | foreach (var value1 in new[] 819 | { 820 | false, 821 | true 822 | }) 823 | { 824 | foreach (var value2 in new[] 825 | { 826 | false, 827 | true 828 | }) 829 | { 830 | Expression> expression1 = (x1, x2) => x1; 831 | var result = expression1.OrElse((x1, x2) => x2); 832 | Assert.AreEqual(value1 || value2, result.Evaluate(value1, value2)); 833 | } 834 | } 835 | } 836 | } 837 | } 838 | -------------------------------------------------------------------------------- /Mindbox.Expressions/Evaluators/Caching/MindboxExpressionStringBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Dynamic; 5 | using System.Globalization; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using System.Text; 10 | 11 | namespace Mindbox.Expressions 12 | { 13 | public sealed class MindboxExpressionStringBuilder : System.Linq.Expressions.ExpressionVisitor 14 | { 15 | private StringBuilder result; 16 | 17 | // Associate every unique label or anonymous parameter in the tree with an integer. 18 | // The label is displayed as Label_#. 19 | private Dictionary ids; 20 | 21 | private MindboxExpressionStringBuilder() 22 | { 23 | result = new StringBuilder(); 24 | } 25 | 26 | public override string ToString() 27 | { 28 | return result.ToString(); 29 | } 30 | 31 | private void AddLabel(LabelTarget label) 32 | { 33 | if (ids == null) 34 | { 35 | ids = new Dictionary(); 36 | ids.Add(label, 0); 37 | } 38 | else 39 | { 40 | if (!ids.ContainsKey(label)) 41 | { 42 | ids.Add(label, ids.Count); 43 | } 44 | } 45 | } 46 | 47 | private int GetLabelId(LabelTarget label) 48 | { 49 | if (ids == null) 50 | { 51 | ids = new Dictionary(); 52 | AddLabel(label); 53 | return 0; 54 | } 55 | else 56 | { 57 | int id; 58 | if (!ids.TryGetValue(label, out id)) 59 | { 60 | // label is met the first time 61 | id = ids.Count; 62 | AddLabel(label); 63 | } 64 | return id; 65 | } 66 | } 67 | 68 | private void AddParam(ParameterExpression p) 69 | { 70 | if (ids == null) 71 | { 72 | ids = new Dictionary(); 73 | ids.Add(ids, 0); 74 | } 75 | else 76 | { 77 | if (!ids.ContainsKey(p)) 78 | { 79 | ids.Add(p, ids.Count); 80 | } 81 | } 82 | } 83 | 84 | private int GetParamId(ParameterExpression p) 85 | { 86 | if (ids == null) 87 | { 88 | ids = new Dictionary(); 89 | AddParam(p); 90 | return 0; 91 | } 92 | else 93 | { 94 | int id; 95 | if (!ids.TryGetValue(p, out id)) 96 | { 97 | // p is met the first time 98 | id = ids.Count; 99 | AddParam(p); 100 | } 101 | return id; 102 | } 103 | } 104 | 105 | #region The printing code 106 | 107 | private void Out(string s) 108 | { 109 | result.Append(s); 110 | } 111 | 112 | private void Out(char c) 113 | { 114 | result.Append(c); 115 | } 116 | 117 | #endregion 118 | 119 | #region Output an expresstion tree to a string 120 | 121 | /// 122 | /// Output a given expression tree to a string. 123 | /// 124 | internal static string ExpressionToString(Expression node) 125 | { 126 | Debug.Assert(node != null); 127 | MindboxExpressionStringBuilder esb = new MindboxExpressionStringBuilder(); 128 | esb.Visit(node); 129 | return esb.ToString(); 130 | } 131 | 132 | // More proper would be to make this a virtual method on Action 133 | private static string FormatBinder(CallSiteBinder binder) 134 | { 135 | ConvertBinder convert; 136 | GetMemberBinder getMember; 137 | SetMemberBinder setMember; 138 | DeleteMemberBinder deleteMember; 139 | InvokeMemberBinder call; 140 | UnaryOperationBinder unary; 141 | BinaryOperationBinder binary; 142 | 143 | if ((convert = binder as ConvertBinder) != null) 144 | { 145 | return "Convert " + convert.Type; 146 | } 147 | else if ((getMember = binder as GetMemberBinder) != null) 148 | { 149 | return "GetMember " + getMember.Name; 150 | } 151 | else if ((setMember = binder as SetMemberBinder) != null) 152 | { 153 | return "SetMember " + setMember.Name; 154 | } 155 | else if ((deleteMember = binder as DeleteMemberBinder) != null) 156 | { 157 | return "DeleteMember " + deleteMember.Name; 158 | } 159 | else if (binder is GetIndexBinder) 160 | { 161 | return "GetIndex"; 162 | } 163 | else if (binder is SetIndexBinder) 164 | { 165 | return "SetIndex"; 166 | } 167 | else if (binder is DeleteIndexBinder) 168 | { 169 | return "DeleteIndex"; 170 | } 171 | else if ((call = binder as InvokeMemberBinder) != null) 172 | { 173 | return "Call " + call.Name; 174 | } 175 | else if (binder is InvokeBinder) 176 | { 177 | return "Invoke"; 178 | } 179 | else if (binder is CreateInstanceBinder) 180 | { 181 | return "Create"; 182 | } 183 | else if ((unary = binder as UnaryOperationBinder) != null) 184 | { 185 | return unary.Operation.ToString(); 186 | } 187 | else if ((binary = binder as BinaryOperationBinder) != null) 188 | { 189 | return binary.Operation.ToString(); 190 | } 191 | else 192 | { 193 | return "CallSiteBinder"; 194 | } 195 | } 196 | 197 | private void VisitExpressions(char open, IList expressions, char close) where T : Expression 198 | { 199 | VisitExpressions(open, expressions, close, ", "); 200 | } 201 | 202 | private void VisitExpressions(char open, IList expressions, char close, string seperator) where T : Expression 203 | { 204 | Out(open); 205 | if (expressions != null) 206 | { 207 | bool isFirst = true; 208 | foreach (T e in expressions) 209 | { 210 | if (isFirst) 211 | { 212 | isFirst = false; 213 | } 214 | else 215 | { 216 | Out(seperator); 217 | } 218 | Visit(e); 219 | } 220 | } 221 | Out(close); 222 | } 223 | 224 | protected override Expression VisitDynamic(DynamicExpression node) 225 | { 226 | Out(FormatBinder(node.Binder)); 227 | VisitExpressions('(', node.Arguments, ')'); 228 | return node; 229 | } 230 | 231 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 232 | protected override Expression VisitBinary(BinaryExpression node) 233 | { 234 | if (node.NodeType == ExpressionType.ArrayIndex) 235 | { 236 | Visit(node.Left); 237 | Out("["); 238 | Visit(node.Right); 239 | Out("]"); 240 | } 241 | else 242 | { 243 | string op; 244 | switch (node.NodeType) 245 | { 246 | // AndAlso and OrElse were unintentionally changed in 247 | // CLR 4. We changed them to "AndAlso" and "OrElse" to 248 | // be 3.5 compatible, but it turns out 3.5 shipped with 249 | // "&&" and "||". Oops. 250 | case ExpressionType.AndAlso: 251 | op = "AndAlso"; 252 | 253 | break; 254 | case ExpressionType.OrElse: 255 | op = "OrElse"; 256 | 257 | break; 258 | case ExpressionType.Assign: op = "="; break; 259 | case ExpressionType.Equal: 260 | op = "=="; 261 | 262 | break; 263 | case ExpressionType.NotEqual: op = "!="; break; 264 | case ExpressionType.GreaterThan: op = ">"; break; 265 | case ExpressionType.LessThan: op = "<"; break; 266 | case ExpressionType.GreaterThanOrEqual: op = ">="; break; 267 | case ExpressionType.LessThanOrEqual: op = "<="; break; 268 | case ExpressionType.Add: op = "+"; break; 269 | case ExpressionType.AddAssign: op = "+="; break; 270 | case ExpressionType.AddAssignChecked: op = "+="; break; 271 | case ExpressionType.AddChecked: op = "+"; break; 272 | case ExpressionType.Subtract: op = "-"; break; 273 | case ExpressionType.SubtractAssign: op = "-="; break; 274 | case ExpressionType.SubtractAssignChecked: op = "-="; break; 275 | case ExpressionType.SubtractChecked: op = "-"; break; 276 | case ExpressionType.Divide: op = "/"; break; 277 | case ExpressionType.DivideAssign: op = "/="; break; 278 | case ExpressionType.Modulo: op = "%"; break; 279 | case ExpressionType.ModuloAssign: op = "%="; break; 280 | case ExpressionType.Multiply: op = "*"; break; 281 | case ExpressionType.MultiplyAssign: op = "*="; break; 282 | case ExpressionType.MultiplyAssignChecked: op = "*="; break; 283 | case ExpressionType.MultiplyChecked: op = "*"; break; 284 | case ExpressionType.LeftShift: op = "<<"; break; 285 | case ExpressionType.LeftShiftAssign: op = "<<="; break; 286 | case ExpressionType.RightShift: op = ">>"; break; 287 | case ExpressionType.RightShiftAssign: op = ">>="; break; 288 | case ExpressionType.And: 289 | if (node.Type == typeof(bool) || node.Type == typeof(bool?)) 290 | { 291 | op = "And"; 292 | } 293 | else 294 | { 295 | op = "&"; 296 | } 297 | break; 298 | case ExpressionType.AndAssign: 299 | if (node.Type == typeof(bool) || node.Type == typeof(bool?)) 300 | { 301 | op = "&&="; 302 | } 303 | else 304 | { 305 | op = "&="; 306 | } 307 | break; 308 | case ExpressionType.Or: 309 | if (node.Type == typeof(bool) || node.Type == typeof(bool?)) 310 | { 311 | op = "Or"; 312 | } 313 | else 314 | { 315 | op = "|"; 316 | } 317 | break; 318 | case ExpressionType.OrAssign: 319 | if (node.Type == typeof(bool) || node.Type == typeof(bool?)) 320 | { 321 | op = "||="; 322 | } 323 | else { op = "|="; } 324 | break; 325 | case ExpressionType.ExclusiveOr: op = "^"; break; 326 | case ExpressionType.ExclusiveOrAssign: op = "^="; break; 327 | case ExpressionType.Power: op = "^"; break; 328 | case ExpressionType.PowerAssign: op = "**="; break; 329 | case ExpressionType.Coalesce: op = "??"; break; 330 | 331 | default: 332 | throw new InvalidOperationException(); 333 | } 334 | Out("("); 335 | Visit(node.Left); 336 | Out(' '); 337 | Out(op); 338 | Out(' '); 339 | Visit(node.Right); 340 | Out(")"); 341 | } 342 | return node; 343 | } 344 | 345 | protected override Expression VisitParameter(ParameterExpression node) 346 | { 347 | if (node.IsByRef) 348 | { 349 | Out("ref "); 350 | } 351 | string name = node.Name; 352 | if (String.IsNullOrEmpty(name)) 353 | { 354 | Out("Param_" + GetParamId(node)); 355 | } 356 | else 357 | { 358 | Out(name); 359 | } 360 | return node; 361 | } 362 | 363 | protected override Expression VisitLambda(Expression node) 364 | { 365 | if (node.Parameters.Count == 1) 366 | { 367 | // p => body 368 | Visit(node.Parameters[0]); 369 | } 370 | else 371 | { 372 | // (p1, p2, ..., pn) => body 373 | VisitExpressions('(', node.Parameters, ')'); 374 | } 375 | Out(" => "); 376 | Visit(node.Body); 377 | return node; 378 | } 379 | 380 | protected override Expression VisitListInit(ListInitExpression node) 381 | { 382 | Visit(node.NewExpression); 383 | Out(" {"); 384 | for (int i = 0, n = node.Initializers.Count; i < n; i++) 385 | { 386 | if (i > 0) 387 | { 388 | Out(", "); 389 | } 390 | Out(node.Initializers[i].ToString()); 391 | } 392 | Out("}"); 393 | return node; 394 | } 395 | 396 | protected override Expression VisitConditional(ConditionalExpression node) 397 | { 398 | Out("IIF("); 399 | Visit(node.Test); 400 | Out(", "); 401 | Visit(node.IfTrue); 402 | Out(", "); 403 | Visit(node.IfFalse); 404 | Out(")"); 405 | return node; 406 | } 407 | 408 | protected override Expression VisitConstant(ConstantExpression node) 409 | { 410 | var sValue = node.Type.ToString(); 411 | Out(sValue); 412 | 413 | if (node.Value == null) 414 | Out(""); 415 | 416 | return node; 417 | } 418 | 419 | protected override Expression VisitDebugInfo(DebugInfoExpression node) 420 | { 421 | string s = String.Format( 422 | CultureInfo.CurrentCulture, 423 | "", 424 | node.Document.FileName, 425 | node.StartLine, 426 | node.StartColumn, 427 | node.EndLine, 428 | node.EndColumn 429 | ); 430 | Out(s); 431 | return node; 432 | } 433 | 434 | protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) 435 | { 436 | VisitExpressions('(', node.Variables, ')'); 437 | return node; 438 | } 439 | 440 | // Prints ".instanceField" or "declaringType.staticField" 441 | private void OutMember(Expression instance, MemberInfo member) 442 | { 443 | if (instance != null) 444 | { 445 | Visit(instance); 446 | Out("." + member.Name); 447 | } 448 | else 449 | { 450 | // For static members, include the type name 451 | Out(member.DeclaringType.Name + "." + member.Name); 452 | } 453 | } 454 | 455 | protected override Expression VisitMember(MemberExpression node) 456 | { 457 | OutMember(node.Expression, node.Member); 458 | return node; 459 | } 460 | 461 | protected override Expression VisitMemberInit(MemberInitExpression node) 462 | { 463 | if (node.NewExpression.Arguments.Count == 0 && 464 | node.NewExpression.Type.Name.Contains("<")) 465 | { 466 | // anonymous type constructor 467 | Out("new"); 468 | } 469 | else 470 | { 471 | Visit(node.NewExpression); 472 | } 473 | Out(" {"); 474 | for (int i = 0, n = node.Bindings.Count; i < n; i++) 475 | { 476 | MemberBinding b = node.Bindings[i]; 477 | if (i > 0) 478 | { 479 | Out(", "); 480 | } 481 | VisitMemberBinding(b); 482 | } 483 | Out("}"); 484 | return node; 485 | } 486 | 487 | protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment) 488 | { 489 | Out(assignment.Member.Name); 490 | Out(" = "); 491 | Visit(assignment.Expression); 492 | return assignment; 493 | } 494 | 495 | protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding) 496 | { 497 | Out(binding.Member.Name); 498 | Out(" = {"); 499 | for (int i = 0, n = binding.Initializers.Count; i < n; i++) 500 | { 501 | if (i > 0) 502 | { 503 | Out(", "); 504 | } 505 | VisitElementInit(binding.Initializers[i]); 506 | } 507 | Out("}"); 508 | return binding; 509 | } 510 | 511 | protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) 512 | { 513 | Out(binding.Member.Name); 514 | Out(" = {"); 515 | for (int i = 0, n = binding.Bindings.Count; i < n; i++) 516 | { 517 | if (i > 0) 518 | { 519 | Out(", "); 520 | } 521 | VisitMemberBinding(binding.Bindings[i]); 522 | } 523 | Out("}"); 524 | return binding; 525 | } 526 | 527 | protected override ElementInit VisitElementInit(ElementInit initializer) 528 | { 529 | Out(initializer.AddMethod.ToString()); 530 | string sep = ", "; 531 | 532 | VisitExpressions('(', initializer.Arguments, ')', sep); 533 | return initializer; 534 | } 535 | 536 | protected override Expression VisitInvocation(InvocationExpression node) 537 | { 538 | Out("Invoke("); 539 | Visit(node.Expression); 540 | string sep = ", "; 541 | 542 | for (int i = 0, n = node.Arguments.Count; i < n; i++) 543 | { 544 | Out(sep); 545 | Visit(node.Arguments[i]); 546 | } 547 | Out(")"); 548 | return node; 549 | } 550 | 551 | protected override Expression VisitMethodCall(MethodCallExpression node) 552 | { 553 | int start = 0; 554 | Expression ob = node.Object; 555 | 556 | if (Attribute.GetCustomAttribute(node.Method, typeof(ExtensionAttribute)) != null) 557 | { 558 | start = 1; 559 | ob = node.Arguments[0]; 560 | } 561 | 562 | if (ob != null) 563 | { 564 | Visit(ob); 565 | Out("."); 566 | } 567 | Out(node.Method.Name); 568 | Out("("); 569 | for (int i = start, n = node.Arguments.Count; i < n; i++) 570 | { 571 | if (i > start) 572 | Out(", "); 573 | Visit(node.Arguments[i]); 574 | } 575 | Out(")"); 576 | return node; 577 | } 578 | 579 | protected override Expression VisitNewArray(NewArrayExpression node) 580 | { 581 | switch (node.NodeType) 582 | { 583 | case ExpressionType.NewArrayBounds: 584 | // new MyType[](expr1, expr2) 585 | Out("new " + node.Type.ToString()); 586 | VisitExpressions('(', node.Expressions, ')'); 587 | break; 588 | case ExpressionType.NewArrayInit: 589 | // new [] {expr1, expr2} 590 | Out("new [] "); 591 | VisitExpressions('{', node.Expressions, '}'); 592 | break; 593 | } 594 | return node; 595 | } 596 | 597 | protected override Expression VisitNew(NewExpression node) 598 | { 599 | Out("new " + node.Type.Name); 600 | Out("("); 601 | var members = node.Members; 602 | for (int i = 0; i < node.Arguments.Count; i++) 603 | { 604 | if (i > 0) 605 | { 606 | Out(", "); 607 | } 608 | if (members != null) 609 | { 610 | string name = members[i].Name; 611 | 612 | Out(name); 613 | Out(" = "); 614 | } 615 | Visit(node.Arguments[i]); 616 | } 617 | Out(")"); 618 | return node; 619 | } 620 | 621 | protected override Expression VisitTypeBinary(TypeBinaryExpression node) 622 | { 623 | Out("("); 624 | Visit(node.Expression); 625 | switch (node.NodeType) 626 | { 627 | case ExpressionType.TypeIs: 628 | Out(" Is "); 629 | break; 630 | case ExpressionType.TypeEqual: 631 | Out(" TypeEqual "); 632 | break; 633 | } 634 | Out(node.TypeOperand.Name); 635 | Out(")"); 636 | return node; 637 | } 638 | 639 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] 640 | protected override Expression VisitUnary(UnaryExpression node) 641 | { 642 | switch (node.NodeType) 643 | { 644 | case ExpressionType.TypeAs: 645 | Out("("); 646 | break; 647 | case ExpressionType.Not: 648 | Out("Not("); 649 | break; 650 | case ExpressionType.Negate: 651 | case ExpressionType.NegateChecked: 652 | Out("-"); 653 | break; 654 | case ExpressionType.UnaryPlus: 655 | Out("+"); 656 | break; 657 | case ExpressionType.Quote: 658 | break; 659 | case ExpressionType.Throw: 660 | Out("throw("); 661 | break; 662 | case ExpressionType.Increment: 663 | Out("Increment("); 664 | break; 665 | case ExpressionType.Decrement: 666 | Out("Decrement("); 667 | break; 668 | case ExpressionType.PreIncrementAssign: 669 | Out("++"); 670 | break; 671 | case ExpressionType.PreDecrementAssign: 672 | Out("--"); 673 | break; 674 | case ExpressionType.OnesComplement: 675 | Out("~("); 676 | break; 677 | default: 678 | Out(node.NodeType.ToString()); 679 | Out("("); 680 | break; 681 | } 682 | 683 | Visit(node.Operand); 684 | 685 | switch (node.NodeType) 686 | { 687 | case ExpressionType.Negate: 688 | case ExpressionType.NegateChecked: 689 | case ExpressionType.UnaryPlus: 690 | case ExpressionType.PreDecrementAssign: 691 | case ExpressionType.PreIncrementAssign: 692 | case ExpressionType.Quote: 693 | break; 694 | case ExpressionType.TypeAs: 695 | Out(" As "); 696 | Out(node.Type.Name); 697 | Out(")"); 698 | break; 699 | case ExpressionType.PostIncrementAssign: 700 | Out("++"); 701 | break; 702 | case ExpressionType.PostDecrementAssign: 703 | Out("--"); 704 | break; 705 | case ExpressionType.Convert: 706 | Out(node.Type.FullName); 707 | Out(")"); 708 | break; 709 | default: 710 | Out(")"); 711 | break; 712 | } 713 | return node; 714 | } 715 | 716 | protected override Expression VisitBlock(BlockExpression node) 717 | { 718 | Out("{"); 719 | foreach (var v in node.Variables) 720 | { 721 | Out("var "); 722 | Visit(v); 723 | Out(";"); 724 | } 725 | Out(" ... }"); 726 | return node; 727 | } 728 | 729 | protected override Expression VisitDefault(DefaultExpression node) 730 | { 731 | Out("default("); 732 | Out(node.Type.Name); 733 | Out(")"); 734 | return node; 735 | } 736 | 737 | protected override Expression VisitLabel(LabelExpression node) 738 | { 739 | Out("{ ... } "); 740 | DumpLabel(node.Target); 741 | Out(":"); 742 | return node; 743 | } 744 | 745 | protected override Expression VisitGoto(GotoExpression node) 746 | { 747 | Out(node.Kind.ToString().ToLower(CultureInfo.CurrentCulture)); 748 | DumpLabel(node.Target); 749 | if (node.Value != null) 750 | { 751 | Out(" ("); 752 | Visit(node.Value); 753 | Out(") "); 754 | } 755 | return node; 756 | } 757 | 758 | protected override Expression VisitLoop(LoopExpression node) 759 | { 760 | Out("loop { ... }"); 761 | return node; 762 | } 763 | 764 | protected override SwitchCase VisitSwitchCase(SwitchCase node) 765 | { 766 | Out("case "); 767 | VisitExpressions('(', node.TestValues, ')'); 768 | Out(": ..."); 769 | return node; 770 | } 771 | 772 | protected override Expression VisitSwitch(SwitchExpression node) 773 | { 774 | Out("switch "); 775 | Out("("); 776 | Visit(node.SwitchValue); 777 | Out(") { ... }"); 778 | return node; 779 | } 780 | 781 | protected override CatchBlock VisitCatchBlock(CatchBlock node) 782 | { 783 | Out("catch (" + node.Test.Name); 784 | if (node.Variable != null) 785 | { 786 | Out(node.Variable.Name ?? ""); 787 | } 788 | Out(") { ... }"); 789 | return node; 790 | } 791 | 792 | protected override Expression VisitTry(TryExpression node) 793 | { 794 | Out("try { ... }"); 795 | return node; 796 | } 797 | 798 | protected override Expression VisitIndex(IndexExpression node) 799 | { 800 | if (node.Object != null) 801 | { 802 | Visit(node.Object); 803 | } 804 | else 805 | { 806 | Debug.Assert(node.Indexer != null); 807 | Out(node.Indexer.DeclaringType.Name); 808 | } 809 | if (node.Indexer != null) 810 | { 811 | Out("."); 812 | Out(node.Indexer.Name); 813 | } 814 | 815 | VisitExpressions('[', node.Arguments, ']'); 816 | return node; 817 | } 818 | 819 | protected override Expression VisitExtension(Expression node) 820 | { 821 | // Prefer an overriden ToString, if available. 822 | var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.ExactBinding; 823 | var toString = node.GetType().GetMethod("ToString", flags, null, Type.EmptyTypes, null); 824 | if (toString.DeclaringType != typeof(Expression)) 825 | { 826 | Out(node.ToString()); 827 | return node; 828 | } 829 | 830 | Out("["); 831 | // For 3.5 subclasses, print the NodeType. 832 | // For Extension nodes, print the class name. 833 | if (node.NodeType == ExpressionType.Extension) 834 | { 835 | Out(node.GetType().FullName); 836 | } 837 | else 838 | { 839 | Out(node.NodeType.ToString()); 840 | } 841 | Out("]"); 842 | return node; 843 | } 844 | 845 | private void DumpLabel(LabelTarget target) 846 | { 847 | if (!String.IsNullOrEmpty(target.Name)) 848 | { 849 | Out(target.Name); 850 | } 851 | else 852 | { 853 | int labelId = GetLabelId(target); 854 | Out("UnamedLabel_" + labelId); 855 | } 856 | } 857 | #endregion 858 | } 859 | } -------------------------------------------------------------------------------- /Mindbox.Expressions.Tests/ExpandExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | #if NETFX_CORE || WINDOWS_PHONE 8 | using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; 9 | #else 10 | using Microsoft.VisualStudio.TestTools.UnitTesting; 11 | #endif 12 | 13 | namespace Mindbox.Expressions.Tests 14 | { 15 | [TestClass] 16 | public class ExpandExpressionTests 17 | { 18 | [TestInitialize] 19 | public void Initialize() 20 | { 21 | ExpressionsConfiguration.ExpressionEvaluatorFactory = 22 | () => InterpretingExpressionEvaluator.Instance; 23 | } 24 | 25 | [TestMethod] 26 | public void ExpandExpressionsSimpleTest() 27 | { 28 | Expression> f1 = x => x + 2; 29 | var result = f1.ExpandExpressions(); 30 | 31 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 32 | Assert.AreEqual(3, result.Compile()(1)); 33 | NoEvaluationsAssertion.AssertNoEvaluations(result); 34 | } 35 | 36 | [TestMethod] 37 | public void ExpandExpressionsExpand1Test() 38 | { 39 | Expression> f1 = x => x + 2; 40 | Expression> f2 = x => f1.Compile()(x) * 3; 41 | var result = f2.ExpandExpressions(); 42 | 43 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 44 | Assert.AreEqual(9, result.Compile()(1)); 45 | NoEvaluationsAssertion.AssertNoEvaluations(result); 46 | } 47 | 48 | [TestMethod] 49 | public void ExpandExpressionsExpand1QuoteTest() 50 | { 51 | Expression> f1 = y => y + 2; 52 | 53 | var x = Expression.Parameter(typeof(int), "x"); 54 | Expression> f2 = Expression.Lambda>( 55 | Expression.Multiply( 56 | Expression.Call( 57 | ReflectionExpressions.GetMethodInfo>>(expression => 58 | expression.Evaluate(default(int))), 59 | Expression.Quote(f1), 60 | x), 61 | Expression.Constant(3)), 62 | new[] 63 | { 64 | x 65 | }); 66 | var result = f2.ExpandExpressions(); 67 | 68 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 69 | Assert.AreEqual(9, result.Compile()(1)); 70 | NoEvaluationsAssertion.AssertNoEvaluations(result); 71 | } 72 | 73 | [TestMethod] 74 | public void ExpandExpressionsExpand1MethodCallTest() 75 | { 76 | Expression> f2 = x => Getter().Evaluate(x) * 3; 77 | var result = f2.ExpandExpressions(); 78 | 79 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 80 | Assert.AreEqual(6, result.Evaluate(1)); 81 | NoEvaluationsAssertion.AssertNoEvaluations(result); 82 | } 83 | 84 | [TestMethod] 85 | public void ExpandExpressionsExpand1QuoteParameterizedTest() 86 | { 87 | Expression>>> f1 = x => y => x * 2 + y; 88 | Expression> f2 = z => f1.Evaluate(z).Evaluate(z * 3) + 1; 89 | var result = f2.ExpandExpressions(); 90 | 91 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 92 | Assert.AreEqual(6, result.Compile()(1)); 93 | NoEvaluationsAssertion.AssertNoEvaluations(result); 94 | } 95 | 96 | [TestMethod] 97 | public void ExpandExpressionsExpand1InvokeTest() 98 | { 99 | Expression> f1 = x => x + 2; 100 | Expression> f2 = x => f1.Compile().Invoke(x) * 3; 101 | var result = f2.ExpandExpressions(); 102 | 103 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 104 | Assert.AreEqual(9, result.Compile()(1)); 105 | NoEvaluationsAssertion.AssertNoEvaluations(result); 106 | } 107 | 108 | [TestMethod] 109 | public void ExpandExpressionsExpand1EvaluateTest() 110 | { 111 | Expression> f1 = x => x + 2; 112 | Expression> f2 = x => f1.Evaluate(x) * 3; 113 | var result = f2.ExpandExpressions(); 114 | 115 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 116 | Assert.AreEqual(9, result.Evaluate(1)); 117 | NoEvaluationsAssertion.AssertNoEvaluations(result); 118 | } 119 | 120 | [TestMethod] 121 | public void ExpandExpressionsExpand1EvaluateMethodGroupTest() 122 | { 123 | Expression> f1 = x => x == 2; 124 | Expression, IEnumerable>> f2 = x => x.Where(f1.Evaluate); 125 | var result = f2.ExpandExpressions(); 126 | 127 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 128 | Assert.AreEqual(3, result.Evaluate(new[] { 1, 2, 2, 2, 3 }).Count()); 129 | NoEvaluationsAssertion.AssertNoEvaluations(result); 130 | NoConvertAssertion.AssertNoConverts(result); 131 | } 132 | 133 | [TestMethod] 134 | public void ExpandExpressionsExpand2EvaluateMethodGroupTest() 135 | { 136 | Expression> f1 = x => x == 2; 137 | Expression, IEnumerable>> f2 = x => x.Where(f1.Evaluate).Concat(x.Where(f1.Evaluate)); 138 | var result = f2.ExpandExpressions(); 139 | 140 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 141 | Assert.AreEqual(6, result.Evaluate(new[] { 1, 2, 2, 2, 3 }).Count()); 142 | NoEvaluationsAssertion.AssertNoEvaluations(result); 143 | NoConvertAssertion.AssertNoConverts(result); 144 | } 145 | 146 | [TestMethod] 147 | public void ExpandExpressionsExpandComplexTest() 148 | { 149 | Expression> f1 = x => x + 2; 150 | Expression> f2 = y => f1.Compile()(y + 4) * 3; 151 | var result = f2.ExpandExpressions(); 152 | 153 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 154 | Assert.AreEqual(21, result.Compile()(1)); 155 | NoEvaluationsAssertion.AssertNoEvaluations(result); 156 | } 157 | 158 | [TestMethod] 159 | public void ExpandExpressionsExpandComplexEvaluateTest() 160 | { 161 | Expression> f1 = x => x + 2; 162 | Expression> f2 = y => f1.Evaluate(y + 4) * 3; 163 | var result = f2.ExpandExpressions(); 164 | 165 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 166 | Assert.AreEqual(21, result.Evaluate(1)); 167 | NoEvaluationsAssertion.AssertNoEvaluations(result); 168 | } 169 | 170 | [TestMethod] 171 | public void ExpandExpressionsExpandNestedTest() 172 | { 173 | Expression> f1 = x => x + 2; 174 | Expression> f2 = y => f1.Compile()(f1.Compile()(y) + f1.Compile()(4)) * 3; 175 | var result = f2.ExpandExpressions(); 176 | 177 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 178 | Assert.AreEqual(33, result.Compile()(1)); 179 | NoEvaluationsAssertion.AssertNoEvaluations(result); 180 | } 181 | 182 | [TestMethod] 183 | public void ExpandExpressionsExpandNestedEvaluateTest() 184 | { 185 | Expression> f1 = x => x + 2; 186 | Expression> f2 = y => f1.Evaluate(f1.Evaluate(y) + f1.Evaluate(4)) * 3; 187 | var result = f2.ExpandExpressions(); 188 | 189 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 190 | Assert.AreEqual(33, result.Evaluate(1)); 191 | NoEvaluationsAssertion.AssertNoEvaluations(result); 192 | } 193 | 194 | [TestMethod] 195 | public void ExpandExpressions_EvaluateOnNull_Exception() 196 | { 197 | Expression> f1 = null; 198 | Expression> f2 = z => f1.Evaluate(z); 199 | 200 | AssertException.Throws( 201 | () => f2.ExpandExpressions(), 202 | ex => 203 | { 204 | Assert.AreEqual( 205 | $"Usage of {nameof(Extensions.Evaluate)} on null expression is invalid", 206 | ex.Message); 207 | }); 208 | } 209 | 210 | [TestMethod] 211 | public void ExpandExpressionsVeryComplexTest() 212 | { 213 | Expression>>> f1 = x => (y => y + x); 214 | Expression> f2 = 215 | z => f1.Compile()(f1.Compile()(1).Compile()(z)).Compile()(f1.Compile()(5).Compile()(z)); 216 | 217 | Assert.AreEqual(8, f2.Compile()(1)); 218 | 219 | var result = f2.ExpandExpressions(); 220 | 221 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 222 | Assert.AreEqual(8, result.Compile()(1)); 223 | NoEvaluationsAssertion.AssertNoEvaluations(result); 224 | } 225 | 226 | [TestMethod] 227 | public void ExpandExpressionsVeryComplexEvaluateTest() 228 | { 229 | Expression>>> f1 = x => (y => y + x); 230 | Expression> f2 = 231 | z => f1.Evaluate(f1.Evaluate(1).Evaluate(z)).Evaluate(f1.Evaluate(5).Evaluate(z)); 232 | 233 | Assert.AreEqual(8, f2.Evaluate(1)); 234 | 235 | var result = f2.ExpandExpressions(); 236 | 237 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 238 | Assert.AreEqual(8, result.Evaluate(1)); 239 | NoEvaluationsAssertion.AssertNoEvaluations(result); 240 | } 241 | 242 | [TestMethod] 243 | public void ExpandExpressionsVeryComplexTest2() 244 | { 245 | Expression>>> f1 = x => (y => y + x); 246 | Expression> f2 = 247 | z => f1.Compile()(f1.Compile()(z).Compile()(1)).Compile()(f1.Compile()(z).Compile()(5)); 248 | 249 | Assert.AreEqual(8, f2.Compile()(1)); 250 | 251 | var result = f2.ExpandExpressions(); 252 | 253 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 254 | Assert.AreEqual(8, result.Compile()(1)); 255 | NoEvaluationsAssertion.AssertNoEvaluations(result); 256 | } 257 | 258 | [TestMethod] 259 | public void ExpandExpressionsVeryComplexEvaluateTest2() 260 | { 261 | Expression>>> f1 = x => (y => y + x); 262 | Expression> f2 = 263 | z => f1.Evaluate(f1.Evaluate(z).Evaluate(1)).Evaluate(f1.Evaluate(z).Evaluate(5)); 264 | 265 | Assert.AreEqual(8, f2.Evaluate(1)); 266 | 267 | var result = f2.ExpandExpressions(); 268 | 269 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 270 | Assert.AreEqual(8, result.Evaluate(1)); 271 | NoEvaluationsAssertion.AssertNoEvaluations(result); 272 | } 273 | 274 | [TestMethod] 275 | public void ExpandExpressionsVeryComplexTest3() 276 | { 277 | Expression>>> f1 = x => (y => y + x); 278 | Expression> f2 = z => f1.Compile()(z).Compile()(5); 279 | 280 | Assert.AreEqual(6, f2.Compile()(1)); 281 | 282 | var result = f2.ExpandExpressions(); 283 | 284 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 285 | Assert.AreEqual(6, result.Compile()(1)); 286 | NoEvaluationsAssertion.AssertNoEvaluations(result); 287 | } 288 | 289 | [TestMethod] 290 | public void ExpandExpressionsVeryComplexEvaluateTest3() 291 | { 292 | Expression>>> f1 = x => (y => y + x); 293 | Expression> f2 = z => f1.Evaluate(z).Evaluate(5); 294 | 295 | Assert.AreEqual(6, f2.Evaluate(1)); 296 | 297 | var result = f2.ExpandExpressions(); 298 | 299 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 300 | Assert.AreEqual(6, result.Evaluate(1)); 301 | NoEvaluationsAssertion.AssertNoEvaluations(result); 302 | } 303 | 304 | [TestMethod] 305 | public void ExpandExpressionsVeryComplexTest4() 306 | { 307 | Expression>>> f1 = x => (y => y + x); 308 | Expression> f2 = 309 | z => f1.Compile()(f1.Compile()(z).Compile()(1)).Compile()(6); 310 | 311 | Assert.AreEqual(8, f2.Compile()(1)); 312 | 313 | var result = f2.ExpandExpressions(); 314 | 315 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 316 | Assert.AreEqual(8, result.Compile()(1)); 317 | NoEvaluationsAssertion.AssertNoEvaluations(result); 318 | } 319 | 320 | [TestMethod] 321 | public void ExpandExpressionsVeryComplexEvaluateTest4() 322 | { 323 | Expression>>> f1 = x => (y => y + x); 324 | Expression> f2 = 325 | z => f1.Evaluate(f1.Evaluate(z).Evaluate(1)).Evaluate(6); 326 | 327 | Assert.AreEqual(8, f2.Evaluate(1)); 328 | 329 | var result = f2.ExpandExpressions(); 330 | 331 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 332 | Assert.AreEqual(8, result.Evaluate(1)); 333 | NoEvaluationsAssertion.AssertNoEvaluations(result); 334 | } 335 | 336 | [TestMethod] 337 | public void ExpandExpressionsVeryComplexSameNamesTest1() 338 | { 339 | Expression>>> f1 = x => (y => y + x); 340 | Expression> f2 = 341 | x => f1.Compile()(f1.Compile()(x).Compile()(1)).Compile()(f1.Compile()(x).Compile()(5)); 342 | 343 | Assert.AreEqual(8, f2.Compile()(1)); 344 | 345 | var result = f2.ExpandExpressions(); 346 | 347 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 348 | Assert.AreEqual(8, result.Compile()(1)); 349 | NoEvaluationsAssertion.AssertNoEvaluations(result); 350 | } 351 | 352 | [TestMethod] 353 | public void ExpandExpressionsVeryComplexSameNamesEvaluateTest1() 354 | { 355 | Expression>>> f1 = x => (y => y + x); 356 | Expression> f2 = 357 | x => f1.Evaluate(f1.Evaluate(x).Evaluate(1)).Evaluate(f1.Evaluate(x).Evaluate(5)); 358 | 359 | Assert.AreEqual(8, f2.Evaluate(1)); 360 | 361 | var result = f2.ExpandExpressions(); 362 | 363 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 364 | Assert.AreEqual(8, result.Evaluate(1)); 365 | NoEvaluationsAssertion.AssertNoEvaluations(result); 366 | } 367 | 368 | [TestMethod] 369 | public void ExpandExpressionsVeryComplexSameNamesTest2() 370 | { 371 | Expression>>> f1 = x => (y => y + x); 372 | Expression> f2 = 373 | y => f1.Compile()(f1.Compile()(y).Compile()(1)).Compile()(f1.Compile()(y).Compile()(5)); 374 | 375 | Assert.AreEqual(8, f2.Compile()(1)); 376 | 377 | var result = f2.ExpandExpressions(); 378 | 379 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 380 | Assert.AreEqual(8, result.Compile()(1)); 381 | NoEvaluationsAssertion.AssertNoEvaluations(result); 382 | } 383 | 384 | [TestMethod] 385 | public void ExpandExpressionsVeryComplexSameNamesEvaluateTest2() 386 | { 387 | Expression>>> f1 = x => (y => y + x); 388 | Expression> f2 = 389 | y => f1.Evaluate(f1.Evaluate(y).Evaluate(1)).Evaluate(f1.Evaluate(y).Evaluate(5)); 390 | 391 | Assert.AreEqual(8, f2.Evaluate(1)); 392 | 393 | var result = f2.ExpandExpressions(); 394 | 395 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 396 | Assert.AreEqual(8, result.Evaluate(1)); 397 | NoEvaluationsAssertion.AssertNoEvaluations(result); 398 | } 399 | 400 | [TestMethod] 401 | public void ExpandExpressionsDirtyTest() 402 | { 403 | Expression>>> f1 = x => (y => y + x); 404 | Expression> f2 = 405 | z => f1.Compile()(DirtyGetter(z).Compile()(1)).Compile()(f1.Compile()(5).Compile()(z)); 406 | 407 | Assert.AreEqual(8, f2.Compile()(1)); 408 | 409 | AssertException.Throws( 410 | () => f2.ExpandExpressions(), 411 | ex => 412 | { 413 | Assert.AreEqual( 414 | "Expression isn't expandable due to usage of " + 415 | $"{nameof(Extensions.Evaluate)} or {nameof(LambdaExpression.Compile)} on expression, " + 416 | "that can't be obtained because it depends on outer lambda expression parameter.", 417 | ex.Message); 418 | }); 419 | } 420 | 421 | [TestMethod] 422 | public void ExpandExpressionsDirtyEvaluateTest() 423 | { 424 | Expression>>> f1 = x => (y => y + x); 425 | Expression> f2 = 426 | z => f1.Evaluate(DirtyGetter(z).Evaluate(1)).Evaluate(f1.Evaluate(5).Evaluate(z)); 427 | 428 | Assert.AreEqual(8, f2.Evaluate(1)); 429 | 430 | AssertException.Throws( 431 | () => f2.ExpandExpressions(), 432 | ex => 433 | { 434 | Assert.AreEqual( 435 | "Expression isn't expandable due to usage of " + 436 | $"{nameof(Extensions.Evaluate)} or {nameof(LambdaExpression.Compile)} on expression, " + 437 | "that can't be obtained because it depends on outer lambda expression parameter.", 438 | ex.Message); 439 | }); 440 | } 441 | 442 | [TestMethod] 443 | public void ExpandExpressionsNonCompileInvocationTest() 444 | { 445 | Expression> f1 = x => GetGetter()() + x; 446 | 447 | Assert.AreEqual(2, f1.Compile()(1)); 448 | 449 | var result = f1.ExpandExpressions(); 450 | 451 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 452 | Assert.AreEqual(2, result.Compile()(1)); 453 | NoEvaluationsAssertion.AssertNoEvaluations(result); 454 | } 455 | 456 | [TestMethod] 457 | public void ExpandExpressionsFalseCompileTest() 458 | { 459 | Expression> f1 = x => x + 2; 460 | Expression>> f2 = x => f1.Compile(); 461 | var result = f2.ExpandExpressions(); 462 | 463 | Assert.AreEqual(3, result.Evaluate(4)(1)); 464 | } 465 | 466 | [TestMethod] 467 | public void ExpandExpressionsDuplicateParameterComlexTest() 468 | { 469 | Expression> f1 = x => new[] { x + 1 }.Single(y => y != x); 470 | Expression> f2 = x => x * x; 471 | Expression> f3 = x => f2.Evaluate(f1.Evaluate(x)); 472 | 473 | var result = f3.ExpandExpressions(); 474 | 475 | NoDuplicateParameterAssertion.AssertNoDuplicateParameters(result); 476 | } 477 | 478 | 479 | private static Expression> DirtyGetter(int argument) 480 | { 481 | return x => x + argument; 482 | } 483 | 484 | private static Func GetGetter() 485 | { 486 | return () => 1; 487 | } 488 | 489 | private static Expression> Getter() 490 | { 491 | return x => x * 2; 492 | } 493 | 494 | 495 | private sealed class NoEvaluationsAssertion : ExpressionVisitor 496 | { 497 | private static readonly string CompileMethodName = 498 | ReflectionExpressions.GetMethodName>>(expression => expression.Compile()); 499 | 500 | 501 | public static void AssertNoEvaluations(Expression expression) 502 | { 503 | new NoEvaluationsAssertion().Visit(expression); 504 | } 505 | 506 | 507 | private NoEvaluationsAssertion() { } 508 | 509 | 510 | protected override Expression VisitMethodCall(MethodCallExpression node) 511 | { 512 | if (node == null) 513 | throw new ArgumentNullException("node"); 514 | 515 | ValidateMethod(node, node.Method); 516 | 517 | return base.VisitMethodCall(node); 518 | } 519 | 520 | protected override Expression VisitInvocation(InvocationExpression node) 521 | { 522 | if (node == null) 523 | throw new ArgumentNullException("node"); 524 | 525 | if (node.Expression.NodeType == ExpressionType.Call) 526 | { 527 | var methodCallExpression = (MethodCallExpression)node.Expression; 528 | if ((methodCallExpression.Method.DeclaringType != null) && 529 | #if NET45 || CORE45 || WINDOWS_PHONE_APP 530 | methodCallExpression.Method.DeclaringType.IsConstructedGenericType && 531 | #else 532 | methodCallExpression.Method.DeclaringType.IsGenericType && 533 | !methodCallExpression.Method.DeclaringType.IsGenericTypeDefinition && 534 | #endif 535 | (methodCallExpression.Method.DeclaringType.GetGenericTypeDefinition() == typeof(Expression<>)) && 536 | (methodCallExpression.Method.Name == CompileMethodName)) 537 | Assert.Fail("The expression body has evaluation: \"{0}\".", node); 538 | } 539 | 540 | return base.VisitInvocation(node); 541 | } 542 | 543 | protected override Expression VisitConstant(ConstantExpression node) 544 | { 545 | if (node == null) 546 | throw new ArgumentNullException("node"); 547 | 548 | if (node.Type == typeof(MethodInfo)) 549 | ValidateMethod(node, (MethodInfo)node.Value); 550 | 551 | return base.VisitConstant(node); 552 | } 553 | 554 | 555 | private static void ValidateMethod(Expression node, MethodInfo method) 556 | { 557 | if (node == null) 558 | throw new ArgumentNullException("node"); 559 | if (method == null) 560 | throw new ArgumentNullException("method"); 561 | 562 | if ((method.DeclaringType == typeof(Extensions)) && 563 | (method.Name == ReflectionExpressions.GetMethodName>>( 564 | expression => expression.Evaluate()))) 565 | Assert.Fail("The expression body has evaluation: \"{0}\".", node); 566 | if ((method.DeclaringType != null) && 567 | #if NET35 || SL3 || WINDOWS_PHONE || PORTABLE36 || PORTABLE88 || PORTABLE328 568 | (method.DeclaringType.BaseType == 569 | #else 570 | (method.DeclaringType.GetTypeInfo().BaseType == 571 | #endif 572 | typeof(MulticastDelegate)) && 573 | (method.Name == ReflectionExpressions.GetMethodName(action => action.Invoke()))) 574 | Assert.Fail("The expression body has invokation: \"{0}\".", node); 575 | } 576 | } 577 | 578 | 579 | private sealed class NoConvertAssertion : ExpressionVisitor 580 | { 581 | public static void AssertNoConverts(Expression expression) 582 | { 583 | new NoConvertAssertion().Visit(expression); 584 | } 585 | 586 | 587 | private NoConvertAssertion() { } 588 | 589 | 590 | protected override Expression VisitUnary(UnaryExpression node) 591 | { 592 | if (node == null) 593 | throw new ArgumentNullException("node"); 594 | 595 | if (node.NodeType == ExpressionType.Convert) 596 | Assert.Fail("The expression body has convert: \"{0}\".", node); 597 | 598 | return base.VisitUnary(node); 599 | } 600 | } 601 | 602 | 603 | private sealed class NoDuplicateParameterAssertion : ExpressionVisitor 604 | { 605 | 606 | public static void AssertNoDuplicateParameters(Expression expression) 607 | { 608 | new NoDuplicateParameterAssertion().Visit(expression); 609 | } 610 | 611 | 612 | private NoDuplicateParameterAssertion() { } 613 | 614 | 615 | private readonly List parameters = new List(); 616 | 617 | 618 | #if NET40 || SL4 || CORE45 || WP8 || WINDOWS_PHONE_APP || PORTABLE36 || PORTABLE328 619 | protected override Expression VisitLambda(Expression node) 620 | { 621 | if (node == null) 622 | throw new ArgumentNullException("node"); 623 | 624 | foreach (var parameter in node.Parameters) 625 | { 626 | if (parameters.Contains(parameter)) 627 | Assert.Fail("Duplicate parameter detected: \"{0}\".", parameter); 628 | parameters.Add(parameter); 629 | } 630 | 631 | return base.VisitLambda(node); 632 | } 633 | } 634 | #else 635 | protected override Expression VisitLambda(LambdaExpression node) 636 | { 637 | if (node == null) 638 | throw new ArgumentNullException("node"); 639 | 640 | foreach (var parameter in node.Parameters) 641 | { 642 | if (parameters.Contains(parameter)) 643 | Assert.Fail("Duplicate parameter detected: \"{0}\".", parameter); 644 | parameters.Add(parameter); 645 | } 646 | 647 | return base.VisitLambda(node); 648 | } 649 | } 650 | #endif 651 | } 652 | } 653 | -------------------------------------------------------------------------------- /Mindbox.Expressions/ReflectionExpressions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Mindbox.Expressions 9 | { 10 | /// 11 | /// Allows acquiring MemberInfo objects and member names in a refactoring friendly way via expressions. 12 | /// 13 | public static class ReflectionExpressions 14 | { 15 | private const string GetterSpecialNamePrefix = "get_"; 16 | 17 | 18 | /// 19 | /// Tries to find MethodInfo via the method call expression. 20 | /// This overload is for instance methods (and extension methods) with return value. 21 | /// 22 | /// Declaring type for instance methods. First parameter type for extension methods. 23 | /// Method call expression. Can contain any valid argument values. 24 | /// MethodInfo or null (if the expression is not a method call expression). 25 | public static MethodInfo TryGetMethodInfo(Expression> callExpression) 26 | { 27 | return TryGetMethodInfo((LambdaExpression)callExpression); 28 | } 29 | 30 | /// 31 | /// Tries to find MethodInfo via the method call expression. 32 | /// This overload is for instance methods (and extension methods) without return value. 33 | /// 34 | /// Declaring type for instance methods. First parameter type for extension methods. 35 | /// Method call expression. Can contain any valid argument values. 36 | /// MethodInfo or null (if the expression is not a method call expression). 37 | public static MethodInfo TryGetMethodInfo(Expression> callExpression) 38 | { 39 | return TryGetMethodInfo((LambdaExpression)callExpression); 40 | } 41 | 42 | /// 43 | /// Tries to find MethodInfo via the method call expression. 44 | /// This overload is for static methods with return value. 45 | /// 46 | /// Method call expression. Can contain any valid argument values. 47 | /// MethodInfo or null (if the expression is not a method call expression). 48 | public static MethodInfo TryGetMethodInfo(Expression> callExpression) 49 | { 50 | return TryGetMethodInfo((LambdaExpression)callExpression); 51 | } 52 | 53 | /// 54 | /// Tries to find MethodInfo via the method call expression. 55 | /// This overload is for static methods without return value. 56 | /// 57 | /// Method call expression. Can contain any valid argument values. 58 | /// MethodInfo or null (if the expression is not a method call expression). 59 | public static MethodInfo TryGetMethodInfo(Expression callExpression) 60 | { 61 | return TryGetMethodInfo((LambdaExpression)callExpression); 62 | } 63 | 64 | /// 65 | /// Tries to find MethodInfo via the method call expression. 66 | /// This overload is for cases when you already have the expression object. 67 | /// 68 | /// Method call expression. Can contain any valid argument values. 69 | /// MethodInfo or null (if the expression is not a method call expression). 70 | public static MethodInfo TryGetMethodInfo(LambdaExpression callExpression) 71 | { 72 | if (callExpression == null) 73 | return null; 74 | 75 | var effectiveExpression = RemoveConverts(callExpression.Body); 76 | switch (effectiveExpression.NodeType) 77 | { 78 | case ExpressionType.Call: 79 | return ((MethodCallExpression)effectiveExpression).Method; 80 | 81 | default: 82 | return null; 83 | } 84 | } 85 | 86 | 87 | /// 88 | /// Finds MethodInfo via the method call expression. 89 | /// This overload is for instance methods (and extension methods) with return value. 90 | /// 91 | /// Declaring type for instance methods. First parameter type for extension methods. 92 | /// Method call expression. Can contain any valid argument values. 93 | /// The expression is null. 94 | /// The expression is not a method call expression. 95 | public static MethodInfo GetMethodInfo(Expression> callExpression) 96 | { 97 | if (callExpression == null) 98 | throw new ArgumentNullException("callExpression"); 99 | 100 | return GetMethodInfo((LambdaExpression)callExpression); 101 | } 102 | 103 | /// 104 | /// Finds MethodInfo via the method call expression. 105 | /// This overload is for instance methods (and extension methods) without return value. 106 | /// 107 | /// Declaring type for instance methods. First parameter type for extension methods. 108 | /// Method call expression. Can contain any valid argument values. 109 | /// The expression is null. 110 | /// The expression is not a method call expression. 111 | public static MethodInfo GetMethodInfo(Expression> callExpression) 112 | { 113 | if (callExpression == null) 114 | throw new ArgumentNullException("callExpression"); 115 | 116 | return GetMethodInfo((LambdaExpression)callExpression); 117 | } 118 | 119 | /// 120 | /// Finds MethodInfo via the method call expression. 121 | /// This overload is for static methods with return value. 122 | /// 123 | /// Method call expression. Can contain any valid argument values. 124 | /// The expression is null. 125 | /// The expression is not a method call expression. 126 | public static MethodInfo GetMethodInfo(Expression> callExpression) 127 | { 128 | if (callExpression == null) 129 | throw new ArgumentNullException("callExpression"); 130 | 131 | return GetMethodInfo((LambdaExpression)callExpression); 132 | } 133 | 134 | /// 135 | /// Finds MethodInfo via the method call expression. 136 | /// This overload is for static methods without return value. 137 | /// 138 | /// Method call expression. Can contain any valid argument values. 139 | /// The expression is null. 140 | /// The expression is not a method call expression. 141 | public static MethodInfo GetMethodInfo(Expression callExpression) 142 | { 143 | if (callExpression == null) 144 | throw new ArgumentNullException("callExpression"); 145 | 146 | return GetMethodInfo((LambdaExpression)callExpression); 147 | } 148 | 149 | /// 150 | /// Finds MethodInfo via the method call expression. 151 | /// This overload is for cases when you already have the expression object. 152 | /// 153 | /// Method call expression. Can contain any valid argument values. 154 | /// The expression is null. 155 | /// The expression is not a method call expression. 156 | public static MethodInfo GetMethodInfo(LambdaExpression callExpression) 157 | { 158 | if (callExpression == null) 159 | throw new ArgumentNullException("callExpression"); 160 | 161 | var result = TryGetMethodInfo(callExpression); 162 | if (result == null) 163 | throw new ArgumentException("Expression is not a method call.", "callExpression"); 164 | return result; 165 | } 166 | 167 | 168 | /// 169 | /// Tries to find method name via the method call expression. 170 | /// This overload is for instance methods (and extension methods) with return value. 171 | /// 172 | /// Declaring type for instance methods. First parameter type for extension methods. 173 | /// Method call expression. Can contain any valid argument values. 174 | /// Method name or null (if the expression is not a method call expression). 175 | public static string TryGetMethodName(Expression> callExpression) 176 | { 177 | return TryGetMethodName((LambdaExpression)callExpression); 178 | } 179 | 180 | /// 181 | /// Tries to find method name via the method call expression. 182 | /// This overload is for instance methods (and extension methods) without return value. 183 | /// 184 | /// Declaring type for instance methods. First parameter type for extension methods. 185 | /// Method call expression. Can contain any valid argument values. 186 | /// Method name or null (if the expression is not a method call expression). 187 | public static string TryGetMethodName(Expression> callExpression) 188 | { 189 | return TryGetMethodName((LambdaExpression)callExpression); 190 | } 191 | 192 | /// 193 | /// Tries to find method name via the method call expression. 194 | /// This overload is for static methods with return value. 195 | /// 196 | /// Method call expression. Can contain any valid argument values. 197 | /// Method name or null (if the expression is not a method call expression). 198 | public static string TryGetMethodName(Expression> callExpression) 199 | { 200 | return TryGetMethodName((LambdaExpression)callExpression); 201 | } 202 | 203 | /// 204 | /// Tries to find method name via the method call expression. 205 | /// This overload is for static methods without return value. 206 | /// 207 | /// Method call expression. Can contain any valid argument values. 208 | /// Method name or null (if the expression is not a method call expression). 209 | public static string TryGetMethodName(Expression callExpression) 210 | { 211 | return TryGetMethodName((LambdaExpression)callExpression); 212 | } 213 | 214 | /// 215 | /// Tries to find method name via the method call expression. 216 | /// This overload is for cases when you already have the expression object. 217 | /// 218 | /// Method call expression. Can contain any valid argument values. 219 | /// Method name or null (if the expression is not a method call expression). 220 | public static string TryGetMethodName(LambdaExpression callExpression) 221 | { 222 | var methodInfo = TryGetMethodInfo(callExpression); 223 | return methodInfo == null ? null : methodInfo.Name; 224 | } 225 | 226 | 227 | /// 228 | /// Finds method name via the method call expression. 229 | /// This overload is for instance methods (and extension methods) with return value. 230 | /// 231 | /// Declaring type for instance methods. First parameter type for extension methods. 232 | /// Method call expression. Can contain any valid argument values. 233 | /// The expression is null. 234 | /// The expression is not a method call expression. 235 | public static string GetMethodName(Expression> callExpression) 236 | { 237 | if (callExpression == null) 238 | throw new ArgumentNullException("callExpression"); 239 | 240 | return GetMethodName((LambdaExpression)callExpression); 241 | } 242 | 243 | /// 244 | /// Finds method name via the method call expression. 245 | /// This overload is for instance methods (and extension methods) without return value. 246 | /// 247 | /// Declaring type for instance methods. First parameter type for extension methods. 248 | /// Method call expression. Can contain any valid argument values. 249 | /// The expression is null. 250 | /// The expression is not a method call expression. 251 | public static string GetMethodName(Expression> callExpression) 252 | { 253 | if (callExpression == null) 254 | throw new ArgumentNullException("callExpression"); 255 | 256 | return GetMethodName((LambdaExpression)callExpression); 257 | } 258 | 259 | /// 260 | /// Finds method name via the method call expression. 261 | /// This overload is for static methods with return value. 262 | /// 263 | /// Method call expression. Can contain any valid argument values. 264 | /// The expression is null. 265 | /// The expression is not a method call expression. 266 | public static string GetMethodName(Expression> callExpression) 267 | { 268 | if (callExpression == null) 269 | throw new ArgumentNullException("callExpression"); 270 | 271 | return GetMethodName((LambdaExpression)callExpression); 272 | } 273 | 274 | /// 275 | /// Finds method name via the method call expression. 276 | /// This overload is for static methods without return value. 277 | /// 278 | /// Method call expression. Can contain any valid argument values. 279 | /// The expression is null. 280 | /// The expression is not a method call expression. 281 | public static string GetMethodName(Expression callExpression) 282 | { 283 | if (callExpression == null) 284 | throw new ArgumentNullException("callExpression"); 285 | 286 | return GetMethodName((LambdaExpression)callExpression); 287 | } 288 | 289 | /// 290 | /// Finds method name via the method call expression. 291 | /// This overload is for cases when you already have the expression object. 292 | /// 293 | /// Method call expression. Can contain any valid argument values. 294 | /// The expression is null. 295 | /// The expression is not a method call expression. 296 | public static string GetMethodName(LambdaExpression callExpression) 297 | { 298 | if (callExpression == null) 299 | throw new ArgumentNullException("callExpression"); 300 | 301 | return GetMethodInfo(callExpression).Name; 302 | } 303 | 304 | 305 | /// 306 | /// Tries to find PropertyInfo via the property access expression. 307 | /// This overload is for instance properties. 308 | /// 309 | /// Declaring type. 310 | /// Property access expression. 311 | /// Can contain any valid argument values for indexed properties. 312 | /// PropertyInfo or null (if the expression is not a property access expression). 313 | public static PropertyInfo TryGetPropertyInfo(Expression> propertyExpression) 314 | { 315 | return TryGetPropertyInfo((LambdaExpression)propertyExpression); 316 | } 317 | 318 | /// 319 | /// Tries to find PropertyInfo via the property access expression. 320 | /// This overload is for static properties. 321 | /// 322 | /// Property access expression. 323 | /// Can contain any valid argument values for indexed properties. 324 | /// PropertyInfo or null (if the expression is not a property access expression). 325 | public static PropertyInfo TryGetPropertyInfo(Expression> propertyExpression) 326 | { 327 | return TryGetPropertyInfo((LambdaExpression)propertyExpression); 328 | } 329 | 330 | /// 331 | /// Tries to find PropertyInfo via the property access expression. 332 | /// This overload is for cases when you already have the expression object. 333 | /// 334 | /// Property access expression. 335 | /// Can contain any valid argument values for indexed properties. 336 | /// PropertyInfo or null (if the expression is not a property access expression). 337 | public static PropertyInfo TryGetPropertyInfo(LambdaExpression propertyExpression) 338 | { 339 | if (propertyExpression == null) 340 | return null; 341 | 342 | var effectiveExpression = RemoveConverts(propertyExpression.Body); 343 | switch (effectiveExpression.NodeType) 344 | { 345 | case ExpressionType.MemberAccess: 346 | return ((MemberExpression)effectiveExpression).Member as PropertyInfo; 347 | 348 | case ExpressionType.Call: 349 | var methodCallExpression = (MethodCallExpression)effectiveExpression; 350 | if (methodCallExpression.Method.IsSpecialName && 351 | methodCallExpression.Method.Name.StartsWith(GetterSpecialNamePrefix, StringComparison.Ordinal) && 352 | (methodCallExpression.Method.DeclaringType != null)) 353 | { 354 | var properties = methodCallExpression 355 | .Method 356 | .DeclaringType 357 | #if NET45 || CORE45 || WINDOWS_PHONE_APP 358 | .GetTypeInfo() 359 | .DeclaredProperties; 360 | #else 361 | .GetProperties(BindingFlags.DeclaredOnly | 362 | BindingFlags.Public | 363 | BindingFlags.NonPublic | 364 | BindingFlags.Instance | 365 | BindingFlags.Static); 366 | #endif 367 | return properties.SingleOrDefault(property => 368 | #if NET45 || CORE45 || WINDOWS_PHONE_APP 369 | property.GetMethod == 370 | #else 371 | property.GetGetMethod(true) == 372 | #endif 373 | methodCallExpression.Method); 374 | } 375 | return null; 376 | 377 | default: 378 | return null; 379 | } 380 | } 381 | 382 | 383 | /// 384 | /// Finds PropertyInfo via the property access expression. 385 | /// This overload is for instance properties. 386 | /// 387 | /// Declaring type. 388 | /// Property access expression. 389 | /// Can contain any valid argument values for indexed properties. 390 | /// The expression is null. 391 | /// The expression is not a property access expression. 392 | public static PropertyInfo GetPropertyInfo(Expression> propertyExpression) 393 | { 394 | if (propertyExpression == null) 395 | throw new ArgumentNullException("propertyExpression"); 396 | 397 | return GetPropertyInfo((LambdaExpression)propertyExpression); 398 | } 399 | 400 | /// 401 | /// Finds PropertyInfo via the property access expression. 402 | /// This overload is for static properties. 403 | /// 404 | /// Property access expression. 405 | /// Can contain any valid argument values for indexed properties. 406 | /// The expression is null. 407 | /// The expression is not a property access expression. 408 | public static PropertyInfo GetPropertyInfo(Expression> propertyExpression) 409 | { 410 | if (propertyExpression == null) 411 | throw new ArgumentNullException("propertyExpression"); 412 | 413 | return GetPropertyInfo((LambdaExpression)propertyExpression); 414 | } 415 | 416 | /// 417 | /// Finds PropertyInfo via the property access expression. 418 | /// This overload is for cases when you already have the expression object. 419 | /// 420 | /// Property access expression. 421 | /// Can contain any valid argument values for indexed properties. 422 | /// The expression is null. 423 | /// The expression is not a property access expression. 424 | public static PropertyInfo GetPropertyInfo(LambdaExpression propertyExpression) 425 | { 426 | if (propertyExpression == null) 427 | throw new ArgumentNullException("propertyExpression"); 428 | 429 | var result = TryGetPropertyInfo(propertyExpression); 430 | if (result == null) 431 | throw new ArgumentException("Expression is not a property getter access.", "propertyExpression"); 432 | return result; 433 | } 434 | 435 | 436 | /// 437 | /// Tries to find property name via the property access expression. 438 | /// This overload is for instance properties. 439 | /// 440 | /// Declaring type. 441 | /// Property access expression. 442 | /// Can contain any valid argument values for indexed properties. 443 | /// Property name or null (if the expression is not a property access expression). 444 | public static string TryGetPropertyName(Expression> propertyExpression) 445 | { 446 | return TryGetPropertyName((LambdaExpression)propertyExpression); 447 | } 448 | 449 | /// 450 | /// Tries to find property name via the property access expression. 451 | /// This overload is for static properties. 452 | /// 453 | /// Property access expression. 454 | /// Can contain any valid argument values for indexed properties. 455 | /// Property name or null (if the expression is not a property access expression). 456 | public static string TryGetPropertyName(Expression> propertyExpression) 457 | { 458 | return TryGetPropertyName((LambdaExpression)propertyExpression); 459 | } 460 | 461 | /// 462 | /// Tries to find property name via the property access expression. 463 | /// This overload is for cases when you already have the expression object. 464 | /// 465 | /// Property access expression. 466 | /// Can contain any valid argument values for indexed properties. 467 | /// Property name or null (if the expression is not a property access expression). 468 | public static string TryGetPropertyName(LambdaExpression propertyExpression) 469 | { 470 | var propertyInfo = TryGetPropertyInfo(propertyExpression); 471 | return propertyInfo == null ? null : propertyInfo.Name; 472 | } 473 | 474 | 475 | /// 476 | /// Finds property name via the property access expression. 477 | /// This overload is for instance properties. 478 | /// 479 | /// Declaring type. 480 | /// Property access expression. 481 | /// Can contain any valid argument values for indexed properties. 482 | /// The expression is null. 483 | /// The expression is not a property access expression. 484 | public static string GetPropertyName(Expression> propertyExpression) 485 | { 486 | if (propertyExpression == null) 487 | throw new ArgumentNullException("propertyExpression"); 488 | 489 | return GetPropertyName((LambdaExpression)propertyExpression); 490 | } 491 | 492 | /// 493 | /// Finds property name via the property access expression. 494 | /// This overload is for static properties. 495 | /// 496 | /// Property access expression. 497 | /// Can contain any valid argument values for indexed properties. 498 | /// The expression is null. 499 | /// The expression is not a property access expression. 500 | public static string GetPropertyName(Expression> propertyExpression) 501 | { 502 | if (propertyExpression == null) 503 | throw new ArgumentNullException("propertyExpression"); 504 | 505 | return GetPropertyName((LambdaExpression)propertyExpression); 506 | } 507 | 508 | /// 509 | /// Finds property name via the property access expression. 510 | /// This overload is for cases when you already have the expression object. 511 | /// 512 | /// Property access expression. 513 | /// Can contain any valid argument values for indexed properties. 514 | /// The expression is null. 515 | /// The expression is not a property access expression. 516 | public static string GetPropertyName(LambdaExpression propertyExpression) 517 | { 518 | if (propertyExpression == null) 519 | throw new ArgumentNullException("propertyExpression"); 520 | 521 | return GetPropertyInfo(propertyExpression).Name; 522 | } 523 | 524 | 525 | /// 526 | /// Tries to find FieldInfo via the field access expression. 527 | /// This overload is for instance fields. 528 | /// 529 | /// Declaring type. 530 | /// Field access expression. 531 | /// FieldInfo or null (if the expression is not a field access expression). 532 | public static FieldInfo TryGetFieldInfo(Expression> fieldExpression) 533 | { 534 | return TryGetFieldInfo((LambdaExpression)fieldExpression); 535 | } 536 | 537 | /// 538 | /// Tries to find FieldInfo via the field access expression. 539 | /// This overload is for static fields. 540 | /// 541 | /// Field access expression. 542 | /// FieldInfo or null (if the expression is not a field access expression). 543 | public static FieldInfo TryGetFieldInfo(Expression> fieldExpression) 544 | { 545 | return TryGetFieldInfo((LambdaExpression)fieldExpression); 546 | } 547 | 548 | /// 549 | /// Tries to find FieldInfo via the field access expression. 550 | /// This overload is for cases when you already have the expression object. 551 | /// 552 | /// Field access expression. 553 | /// FieldInfo or null (if the expression is not a field access expression). 554 | public static FieldInfo TryGetFieldInfo(LambdaExpression fieldExpression) 555 | { 556 | if (fieldExpression == null) 557 | return null; 558 | 559 | var effectiveExpression = RemoveConverts(fieldExpression.Body); 560 | switch (effectiveExpression.NodeType) 561 | { 562 | case ExpressionType.MemberAccess: 563 | return ((MemberExpression)effectiveExpression).Member as FieldInfo; 564 | 565 | default: 566 | return null; 567 | } 568 | } 569 | 570 | 571 | /// 572 | /// Finds FieldInfo via the field access expression. 573 | /// This overload is for instance fields. 574 | /// 575 | /// Declaring type. 576 | /// Field access expression. 577 | /// The expression is null. 578 | /// The expression is not a field access expression. 579 | public static FieldInfo GetFieldInfo(Expression> fieldExpression) 580 | { 581 | if (fieldExpression == null) 582 | throw new ArgumentNullException("fieldExpression"); 583 | 584 | return GetFieldInfo((LambdaExpression)fieldExpression); 585 | } 586 | 587 | /// 588 | /// Finds FieldInfo via the field access expression. 589 | /// This overload is for static fields. 590 | /// 591 | /// Field access expression. 592 | /// The expression is null. 593 | /// The expression is not a field access expression. 594 | public static FieldInfo GetFieldInfo(Expression> fieldExpression) 595 | { 596 | if (fieldExpression == null) 597 | throw new ArgumentNullException("fieldExpression"); 598 | 599 | return GetFieldInfo((LambdaExpression)fieldExpression); 600 | } 601 | 602 | /// 603 | /// Finds FieldInfo via the field access expression. 604 | /// This overload is for cases when you already have the expression object. 605 | /// 606 | /// Field access expression. 607 | /// The expression is null. 608 | /// The expression is not a field access expression. 609 | public static FieldInfo GetFieldInfo(LambdaExpression fieldExpression) 610 | { 611 | if (fieldExpression == null) 612 | throw new ArgumentNullException("fieldExpression"); 613 | 614 | var result = TryGetFieldInfo(fieldExpression); 615 | if (result == null) 616 | throw new ArgumentException("Expression is not a field read access.", "fieldExpression"); 617 | return result; 618 | } 619 | 620 | 621 | /// 622 | /// Tries to find field name via the field access expression. 623 | /// This overload is for instance fields. 624 | /// 625 | /// Declaring type. 626 | /// Field access expression. 627 | /// Field name or null (if the expression is not a field access expression). 628 | public static string TryGetFieldName(Expression> fieldExpression) 629 | { 630 | return TryGetFieldName((LambdaExpression)fieldExpression); 631 | } 632 | 633 | /// 634 | /// Tries to find field name via the field access expression. 635 | /// This overload is for static fields. 636 | /// 637 | /// Field access expression. 638 | /// Field name or null (if the expression is not a field access expression). 639 | public static string TryGetFieldName(Expression> fieldExpression) 640 | { 641 | return TryGetFieldName((LambdaExpression)fieldExpression); 642 | } 643 | 644 | /// 645 | /// Tries to find field name via the field access expression. 646 | /// This overload is for cases when you already have the expression object. 647 | /// 648 | /// Field access expression. 649 | /// Field name or null (if the expression is not a field access expression). 650 | public static string TryGetFieldName(LambdaExpression fieldExpression) 651 | { 652 | var fieldInfo = TryGetFieldInfo(fieldExpression); 653 | return fieldInfo == null ? null : fieldInfo.Name; 654 | } 655 | 656 | 657 | /// 658 | /// Finds field name via the field access expression. 659 | /// This overload is for instance fields. 660 | /// 661 | /// Declaring type. 662 | /// Field access expression. 663 | /// The expression is null. 664 | /// The expression is not a field access expression. 665 | public static string GetFieldName(Expression> fieldExpression) 666 | { 667 | if (fieldExpression == null) 668 | throw new ArgumentNullException("fieldExpression"); 669 | 670 | return GetFieldName((LambdaExpression)fieldExpression); 671 | } 672 | 673 | /// 674 | /// Finds field name via the field access expression. 675 | /// This overload is for static fields. 676 | /// 677 | /// Field access expression. 678 | /// The expression is null. 679 | /// The expression is not a field access expression. 680 | public static string GetFieldName(Expression> fieldExpression) 681 | { 682 | if (fieldExpression == null) 683 | throw new ArgumentNullException("fieldExpression"); 684 | 685 | return GetFieldName((LambdaExpression)fieldExpression); 686 | } 687 | 688 | /// 689 | /// Finds field name via the field access expression. 690 | /// This overload is for cases when you already have the expression object. 691 | /// 692 | /// Field access expression. 693 | /// The expression is null. 694 | /// The expression is not a field access expression. 695 | public static string GetFieldName(LambdaExpression fieldExpression) 696 | { 697 | if (fieldExpression == null) 698 | throw new ArgumentNullException("fieldExpression"); 699 | 700 | return GetFieldInfo(fieldExpression).Name; 701 | } 702 | 703 | 704 | private static Expression RemoveConverts(Expression expression) 705 | { 706 | if (expression == null) 707 | throw new ArgumentNullException("expression"); 708 | 709 | var effectiveExpression = expression; 710 | while (true) 711 | { 712 | switch (effectiveExpression.NodeType) 713 | { 714 | case ExpressionType.Convert: 715 | case ExpressionType.ConvertChecked: 716 | effectiveExpression = ((UnaryExpression)effectiveExpression).Operand; 717 | break; 718 | 719 | default: 720 | return effectiveExpression; 721 | } 722 | } 723 | } 724 | } 725 | } 726 | --------------------------------------------------------------------------------