// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public static class CachedExpressionCompiler { // This is the entry point to the cached expression compilation system. The system // will try to turn the expression into an actual delegate as quickly as possible, // relying on cache lookups and other techniques to save time if appropriate. // If the provided expression is particularly obscure and the system doesn't know // how to handle it, we'll just compile the expression as normal. public static Func Process( Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } return Compiler.Compile(expression); } private static class Compiler { private static Func _identityFunc; private static readonly ConcurrentDictionary> _simpleMemberAccessCache = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> _constMemberAccessCache = new ConcurrentDictionary>(); public static Func Compile(Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } return CompileFromIdentityFunc(expression) ?? CompileFromConstLookup(expression) ?? CompileFromMemberAccess(expression) ?? CompileSlow(expression); } private static Func CompileFromConstLookup( Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } var constantExpression = expression.Body as ConstantExpression; if (constantExpression != null) { // model => {const} var constantValue = (TResult)constantExpression.Value; return _ => constantValue; } return null; } private static Func CompileFromIdentityFunc( Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } if (expression.Body == expression.Parameters[0]) { // model => model // Don't need to lock, as all identity funcs are identical. if (_identityFunc == null) { _identityFunc = expression.Compile(); } return _identityFunc; } return null; } private static Func CompileFromMemberAccess( Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } // Performance tests show that on the x64 platform, special-casing static member and // captured local variable accesses is faster than letting the fingerprinting system // handle them. On the x86 platform, the fingerprinting system is faster, but only // by around one microsecond, so it's not worth it to complicate the logic here with // an architecture check. var memberExpression = expression.Body as MemberExpression; if (memberExpression != null) { if (memberExpression.Expression == expression.Parameters[0] || memberExpression.Expression == null) { // model => model.Member or model => StaticMember return _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, _ => expression.Compile()); } var constantExpression = memberExpression.Expression as ConstantExpression; if (constantExpression != null) { // model => {const}.Member (captured local variable) var compiledExpression = _constMemberAccessCache.GetOrAdd(memberExpression.Member, _ => { // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member var parameterExpression = Expression.Parameter(typeof(object), "capturedLocal"); var castExpression = Expression.Convert(parameterExpression, memberExpression.Member.DeclaringType); var replacementMemberExpression = memberExpression.Update(castExpression); var replacementExpression = Expression.Lambda>( replacementMemberExpression, parameterExpression); return replacementExpression.Compile(); }); var capturedLocal = constantExpression.Value; return _ => compiledExpression(capturedLocal); } } return null; } private static Func CompileSlow(Expression> expression) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } // fallback compilation system - just compile the expression directly return expression.Compile(); } } } }