Copy over two expression-support classes from legacy MVC
This commit is contained in:
parent
e8a76cfd7f
commit
f108315038
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace System.Web.Mvc.ExpressionUtil
|
||||
{
|
||||
internal 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<TModel, TValue> Process<TModel, TValue>(Expression<Func<TModel, TValue>> lambdaExpression)
|
||||
{
|
||||
return Compiler<TModel, TValue>.Compile(lambdaExpression);
|
||||
}
|
||||
|
||||
private static class Compiler<TIn, TOut>
|
||||
{
|
||||
private static Func<TIn, TOut> _identityFunc;
|
||||
|
||||
private static readonly ConcurrentDictionary<MemberInfo, Func<TIn, TOut>> _simpleMemberAccessDict =
|
||||
new ConcurrentDictionary<MemberInfo, Func<TIn, TOut>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<MemberInfo, Func<object, TOut>> _constMemberAccessDict =
|
||||
new ConcurrentDictionary<MemberInfo, Func<object, TOut>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>> _fingerprintedCache =
|
||||
new ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>>();
|
||||
|
||||
public static Func<TIn, TOut> Compile(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
return CompileFromIdentityFunc(expr)
|
||||
?? CompileFromConstLookup(expr)
|
||||
?? CompileFromMemberAccess(expr)
|
||||
?? CompileFromFingerprint(expr)
|
||||
?? CompileSlow(expr);
|
||||
}
|
||||
|
||||
private static Func<TIn, TOut> CompileFromConstLookup(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
ConstantExpression constExpr = expr.Body as ConstantExpression;
|
||||
if (constExpr != null)
|
||||
{
|
||||
// model => {const}
|
||||
|
||||
TOut constantValue = (TOut)constExpr.Value;
|
||||
return _ => constantValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Func<TIn, TOut> CompileFromIdentityFunc(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
if (expr.Body == expr.Parameters[0])
|
||||
{
|
||||
// model => model
|
||||
|
||||
// don't need to lock, as all identity funcs are identical
|
||||
if (_identityFunc == null)
|
||||
{
|
||||
_identityFunc = expr.Compile();
|
||||
}
|
||||
|
||||
return _identityFunc;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Func<TIn, TOut> CompileFromFingerprint(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
List<object> capturedConstants;
|
||||
ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
|
||||
|
||||
if (fingerprint != null)
|
||||
{
|
||||
var del = _fingerprintedCache.GetOrAdd(fingerprint, _ =>
|
||||
{
|
||||
// Fingerprinting succeeded, but there was a cache miss. Rewrite the expression
|
||||
// and add the rewritten expression to the cache.
|
||||
|
||||
var hoistedExpr = HoistingExpressionVisitor<TIn, TOut>.Hoist(expr);
|
||||
return hoistedExpr.Compile();
|
||||
});
|
||||
return model => del(model, capturedConstants);
|
||||
}
|
||||
|
||||
// couldn't be fingerprinted
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Func<TIn, TOut> CompileFromMemberAccess(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
// 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.
|
||||
|
||||
MemberExpression memberExpr = expr.Body as MemberExpression;
|
||||
if (memberExpr != null)
|
||||
{
|
||||
if (memberExpr.Expression == expr.Parameters[0] || memberExpr.Expression == null)
|
||||
{
|
||||
// model => model.Member or model => StaticMember
|
||||
return _simpleMemberAccessDict.GetOrAdd(memberExpr.Member, _ => expr.Compile());
|
||||
}
|
||||
|
||||
ConstantExpression constExpr = memberExpr.Expression as ConstantExpression;
|
||||
if (constExpr != null)
|
||||
{
|
||||
// model => {const}.Member (captured local variable)
|
||||
var del = _constMemberAccessDict.GetOrAdd(memberExpr.Member, _ =>
|
||||
{
|
||||
// rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member
|
||||
var constParamExpr = Expression.Parameter(typeof(object), "capturedLocal");
|
||||
var constCastExpr = Expression.Convert(constParamExpr, memberExpr.Member.DeclaringType);
|
||||
var newMemberAccessExpr = memberExpr.Update(constCastExpr);
|
||||
var newLambdaExpr = Expression.Lambda<Func<object, TOut>>(newMemberAccessExpr, constParamExpr);
|
||||
return newLambdaExpr.Compile();
|
||||
});
|
||||
|
||||
object capturedLocal = constExpr.Value;
|
||||
return _ => del(capturedLocal);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Func<TIn, TOut> CompileSlow(Expression<Func<TIn, TOut>> expr)
|
||||
{
|
||||
// fallback compilation system - just compile the expression directly
|
||||
return expr.Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Web.Mvc.ExpressionUtil;
|
||||
using System.Web.Mvc.Properties;
|
||||
|
||||
namespace System.Web.Mvc
|
||||
{
|
||||
public static class ExpressionHelper
|
||||
{
|
||||
public static string GetExpressionText(string expression)
|
||||
{
|
||||
return
|
||||
String.Equals(expression, "model", StringComparison.OrdinalIgnoreCase)
|
||||
? String.Empty // If it's exactly "model", then give them an empty string, to replicate the lambda behavior
|
||||
: expression;
|
||||
}
|
||||
|
||||
public static string GetExpressionText(LambdaExpression expression)
|
||||
{
|
||||
// Split apart the expression string for property/field accessors to create its name
|
||||
Stack<string> nameParts = new Stack<string>();
|
||||
Expression part = expression.Body;
|
||||
|
||||
while (part != null)
|
||||
{
|
||||
if (part.NodeType == ExpressionType.Call)
|
||||
{
|
||||
MethodCallExpression methodExpression = (MethodCallExpression)part;
|
||||
|
||||
if (!IsSingleArgumentIndexer(methodExpression))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
nameParts.Push(
|
||||
GetIndexerInvocation(
|
||||
methodExpression.Arguments.Single(),
|
||||
expression.Parameters.ToArray()));
|
||||
|
||||
part = methodExpression.Object;
|
||||
}
|
||||
else if (part.NodeType == ExpressionType.ArrayIndex)
|
||||
{
|
||||
BinaryExpression binaryExpression = (BinaryExpression)part;
|
||||
|
||||
nameParts.Push(
|
||||
GetIndexerInvocation(
|
||||
binaryExpression.Right,
|
||||
expression.Parameters.ToArray()));
|
||||
|
||||
part = binaryExpression.Left;
|
||||
}
|
||||
else if (part.NodeType == ExpressionType.MemberAccess)
|
||||
{
|
||||
MemberExpression memberExpressionPart = (MemberExpression)part;
|
||||
nameParts.Push("." + memberExpressionPart.Member.Name);
|
||||
part = memberExpressionPart.Expression;
|
||||
}
|
||||
else if (part.NodeType == ExpressionType.Parameter)
|
||||
{
|
||||
// Dev10 Bug #907611
|
||||
// When the expression is parameter based (m => m.Something...), we'll push an empty
|
||||
// string onto the stack and stop evaluating. The extra empty string makes sure that
|
||||
// we don't accidentally cut off too much of m => m.Model.
|
||||
nameParts.Push(String.Empty);
|
||||
part = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it starts with "model", then strip that away
|
||||
if (nameParts.Count > 0 && String.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
nameParts.Pop();
|
||||
}
|
||||
|
||||
if (nameParts.Count > 0)
|
||||
{
|
||||
return nameParts.Aggregate((left, right) => left + right).TrimStart('.');
|
||||
}
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
private static string GetIndexerInvocation(Expression expression, ParameterExpression[] parameters)
|
||||
{
|
||||
Expression converted = Expression.Convert(expression, typeof(object));
|
||||
ParameterExpression fakeParameter = Expression.Parameter(typeof(object), null);
|
||||
Expression<Func<object, object>> lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
|
||||
Func<object, object> func;
|
||||
|
||||
try
|
||||
{
|
||||
func = CachedExpressionCompiler.Process(lambda);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
String.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
MvcResources.ExpressionHelper_InvalidIndexerExpression,
|
||||
expression,
|
||||
parameters[0].Name),
|
||||
ex);
|
||||
}
|
||||
|
||||
return "[" + Convert.ToString(func(null), CultureInfo.InvariantCulture) + "]";
|
||||
}
|
||||
|
||||
internal static bool IsSingleArgumentIndexer(Expression expression)
|
||||
{
|
||||
MethodCallExpression methodExpression = expression as MethodCallExpression;
|
||||
if (methodExpression == null || methodExpression.Arguments.Count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return methodExpression.Method
|
||||
.DeclaringType
|
||||
.GetDefaultMembers()
|
||||
.OfType<PropertyInfo>()
|
||||
.Any(p => p.GetGetMethod() == methodExpression.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue