Get things working in our world

- usual stuff, especially use of `var` and `[NotNull]`
- remove references to `ExpressionFingerprintChain` and so on to minimize
  classes we bring over now (and remove one cache)
- copy over missing resource
- rework checks in `IsSingleArgumentIndexer()`
This commit is contained in:
dougbu 2014-03-27 22:21:26 -07:00
parent f108315038
commit aaa30591a8
5 changed files with 74 additions and 78 deletions

View File

@ -1,20 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace System.Web.Mvc.ExpressionUtil namespace Microsoft.AspNet.Mvc.Rendering.Expressions
{ {
internal static class CachedExpressionCompiler public static class CachedExpressionCompiler
{ {
// This is the entry point to the cached expression compilation system. The system // 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, // 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. // 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 // 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. // 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) public static Func<TModel, TValue> Process<TModel, TValue>(
[NotNull] Expression<Func<TModel, TValue>> lambdaExpression)
{ {
return Compiler<TModel, TValue>.Compile(lambdaExpression); return Compiler<TModel, TValue>.Compile(lambdaExpression);
} }
@ -29,39 +28,35 @@ namespace System.Web.Mvc.ExpressionUtil
private static readonly ConcurrentDictionary<MemberInfo, Func<object, TOut>> _constMemberAccessDict = private static readonly ConcurrentDictionary<MemberInfo, Func<object, TOut>> _constMemberAccessDict =
new ConcurrentDictionary<MemberInfo, Func<object, TOut>>(); new ConcurrentDictionary<MemberInfo, Func<object, TOut>>();
private static readonly ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>> _fingerprintedCache = public static Func<TIn, TOut> Compile([NotNull] Expression<Func<TIn, TOut>> expr)
new ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>>();
public static Func<TIn, TOut> Compile(Expression<Func<TIn, TOut>> expr)
{ {
return CompileFromIdentityFunc(expr) return CompileFromIdentityFunc(expr)
?? CompileFromConstLookup(expr) ?? CompileFromConstLookup(expr)
?? CompileFromMemberAccess(expr) ?? CompileFromMemberAccess(expr)
?? CompileFromFingerprint(expr)
?? CompileSlow(expr); ?? CompileSlow(expr);
} }
private static Func<TIn, TOut> CompileFromConstLookup(Expression<Func<TIn, TOut>> expr) private static Func<TIn, TOut> CompileFromConstLookup([NotNull] Expression<Func<TIn, TOut>> expr)
{ {
ConstantExpression constExpr = expr.Body as ConstantExpression; var constExpr = expr.Body as ConstantExpression;
if (constExpr != null) if (constExpr != null)
{ {
// model => {const} // model => {const}
TOut constantValue = (TOut)constExpr.Value; var constantValue = (TOut)constExpr.Value;
return _ => constantValue; return _ => constantValue;
} }
return null; return null;
} }
private static Func<TIn, TOut> CompileFromIdentityFunc(Expression<Func<TIn, TOut>> expr) private static Func<TIn, TOut> CompileFromIdentityFunc([NotNull] Expression<Func<TIn, TOut>> expr)
{ {
if (expr.Body == expr.Parameters[0]) if (expr.Body == expr.Parameters[0])
{ {
// model => model // model => model
// don't need to lock, as all identity funcs are identical // Don't need to lock, as all identity funcs are identical.
if (_identityFunc == null) if (_identityFunc == null)
{ {
_identityFunc = expr.Compile(); _identityFunc = expr.Compile();
@ -73,29 +68,7 @@ namespace System.Web.Mvc.ExpressionUtil
return null; return null;
} }
private static Func<TIn, TOut> CompileFromFingerprint(Expression<Func<TIn, TOut>> expr) private static Func<TIn, TOut> CompileFromMemberAccess([NotNull] 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 // Performance tests show that on the x64 platform, special-casing static member and
// captured local variable accesses is faster than letting the fingerprinting system // captured local variable accesses is faster than letting the fingerprinting system
@ -103,7 +76,7 @@ namespace System.Web.Mvc.ExpressionUtil
// by around one microsecond, so it's not worth it to complicate the logic here with // by around one microsecond, so it's not worth it to complicate the logic here with
// an architecture check. // an architecture check.
MemberExpression memberExpr = expr.Body as MemberExpression; var memberExpr = expr.Body as MemberExpression;
if (memberExpr != null) if (memberExpr != null)
{ {
if (memberExpr.Expression == expr.Parameters[0] || memberExpr.Expression == null) if (memberExpr.Expression == expr.Parameters[0] || memberExpr.Expression == null)
@ -112,7 +85,7 @@ namespace System.Web.Mvc.ExpressionUtil
return _simpleMemberAccessDict.GetOrAdd(memberExpr.Member, _ => expr.Compile()); return _simpleMemberAccessDict.GetOrAdd(memberExpr.Member, _ => expr.Compile());
} }
ConstantExpression constExpr = memberExpr.Expression as ConstantExpression; var constExpr = memberExpr.Expression as ConstantExpression;
if (constExpr != null) if (constExpr != null)
{ {
// model => {const}.Member (captured local variable) // model => {const}.Member (captured local variable)
@ -122,11 +95,12 @@ namespace System.Web.Mvc.ExpressionUtil
var constParamExpr = Expression.Parameter(typeof(object), "capturedLocal"); var constParamExpr = Expression.Parameter(typeof(object), "capturedLocal");
var constCastExpr = Expression.Convert(constParamExpr, memberExpr.Member.DeclaringType); var constCastExpr = Expression.Convert(constParamExpr, memberExpr.Member.DeclaringType);
var newMemberAccessExpr = memberExpr.Update(constCastExpr); var newMemberAccessExpr = memberExpr.Update(constCastExpr);
var newLambdaExpr = Expression.Lambda<Func<object, TOut>>(newMemberAccessExpr, constParamExpr); var newLambdaExpr =
Expression.Lambda<Func<object, TOut>>(newMemberAccessExpr, constParamExpr);
return newLambdaExpr.Compile(); return newLambdaExpr.Compile();
}); });
object capturedLocal = constExpr.Value; var capturedLocal = constExpr.Value;
return _ => del(capturedLocal); return _ => del(capturedLocal);
} }
} }
@ -134,7 +108,7 @@ namespace System.Web.Mvc.ExpressionUtil
return null; return null;
} }
private static Func<TIn, TOut> CompileSlow(Expression<Func<TIn, TOut>> expr) private static Func<TIn, TOut> CompileSlow([NotNull] Expression<Func<TIn, TOut>> expr)
{ {
// fallback compilation system - just compile the expression directly // fallback compilation system - just compile the expression directly
return expr.Compile(); return expr.Compile();

View File

@ -1,36 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Web.Mvc.ExpressionUtil;
using System.Web.Mvc.Properties;
namespace System.Web.Mvc namespace Microsoft.AspNet.Mvc.Rendering.Expressions
{ {
public static class ExpressionHelper public static class ExpressionHelper
{ {
public static string GetExpressionText(string expression) public static string GetExpressionText(string expression)
{ {
// If it's exactly "model", then give them an empty string, to replicate the lambda behavior.
return return
String.Equals(expression, "model", StringComparison.OrdinalIgnoreCase) string.Equals(expression, "model", StringComparison.OrdinalIgnoreCase)
? String.Empty // If it's exactly "model", then give them an empty string, to replicate the lambda behavior ? string.Empty
: expression; : expression;
} }
public static string GetExpressionText(LambdaExpression expression) public static string GetExpressionText([NotNull] LambdaExpression expression)
{ {
// Split apart the expression string for property/field accessors to create its name // Split apart the expression string for property/field accessors to create its name
Stack<string> nameParts = new Stack<string>(); var nameParts = new Stack<string>();
Expression part = expression.Body; var part = expression.Body;
while (part != null) while (part != null)
{ {
if (part.NodeType == ExpressionType.Call) if (part.NodeType == ExpressionType.Call)
{ {
MethodCallExpression methodExpression = (MethodCallExpression)part; var methodExpression = (MethodCallExpression)part;
if (!IsSingleArgumentIndexer(methodExpression)) if (!IsSingleArgumentIndexer(methodExpression))
{ {
@ -46,7 +44,7 @@ namespace System.Web.Mvc
} }
else if (part.NodeType == ExpressionType.ArrayIndex) else if (part.NodeType == ExpressionType.ArrayIndex)
{ {
BinaryExpression binaryExpression = (BinaryExpression)part; var binaryExpression = (BinaryExpression)part;
nameParts.Push( nameParts.Push(
GetIndexerInvocation( GetIndexerInvocation(
@ -57,17 +55,16 @@ namespace System.Web.Mvc
} }
else if (part.NodeType == ExpressionType.MemberAccess) else if (part.NodeType == ExpressionType.MemberAccess)
{ {
MemberExpression memberExpressionPart = (MemberExpression)part; var memberExpressionPart = (MemberExpression)part;
nameParts.Push("." + memberExpressionPart.Member.Name); nameParts.Push("." + memberExpressionPart.Member.Name);
part = memberExpressionPart.Expression; part = memberExpressionPart.Expression;
} }
else if (part.NodeType == ExpressionType.Parameter) else if (part.NodeType == ExpressionType.Parameter)
{ {
// Dev10 Bug #907611
// When the expression is parameter based (m => m.Something...), we'll push an empty // 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 // 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. // we don't accidentally cut off too much of m => m.Model.
nameParts.Push(String.Empty); nameParts.Push(string.Empty);
part = null; part = null;
} }
else else
@ -77,7 +74,7 @@ namespace System.Web.Mvc
} }
// If it starts with "model", then strip that away // If it starts with "model", then strip that away
if (nameParts.Count > 0 && String.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase)) if (nameParts.Count > 0 && string.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase))
{ {
nameParts.Pop(); nameParts.Pop();
} }
@ -87,14 +84,15 @@ namespace System.Web.Mvc
return nameParts.Aggregate((left, right) => left + right).TrimStart('.'); return nameParts.Aggregate((left, right) => left + right).TrimStart('.');
} }
return String.Empty; return string.Empty;
} }
private static string GetIndexerInvocation(Expression expression, ParameterExpression[] parameters) private static string GetIndexerInvocation([NotNull] Expression expression,
[NotNull] ParameterExpression[] parameters)
{ {
Expression converted = Expression.Convert(expression, typeof(object)); var converted = Expression.Convert(expression, typeof(object));
ParameterExpression fakeParameter = Expression.Parameter(typeof(object), null); var fakeParameter = Expression.Parameter(typeof(object), null);
Expression<Func<object, object>> lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter); var lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
Func<object, object> func; Func<object, object> func;
try try
@ -104,30 +102,34 @@ namespace System.Web.Mvc
catch (InvalidOperationException ex) catch (InvalidOperationException ex)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
String.Format( Resources.FormatExpressionHelper_InvalidIndexerExpression(expression, parameters[0].Name),
CultureInfo.CurrentCulture, ex);
MvcResources.ExpressionHelper_InvalidIndexerExpression,
expression,
parameters[0].Name),
ex);
} }
return "[" + Convert.ToString(func(null), CultureInfo.InvariantCulture) + "]"; return "[" + Convert.ToString(func(null), CultureInfo.InvariantCulture) + "]";
} }
internal static bool IsSingleArgumentIndexer(Expression expression) public static bool IsSingleArgumentIndexer(Expression expression)
{ {
MethodCallExpression methodExpression = expression as MethodCallExpression; var methodExpression = expression as MethodCallExpression;
if (methodExpression == null || methodExpression.Arguments.Count != 1) if (methodExpression == null || methodExpression.Arguments.Count != 1)
{ {
return false; return false;
} }
return methodExpression.Method // Check whether GetDefaultMembers() (if present in CoreCLR) would return a member of this type. Compiler
.DeclaringType // names the indexer property, if any, in a generated [DefaultMember] attribute for the containing type.
.GetDefaultMembers() var declaringType = methodExpression.Method.DeclaringType;
.OfType<PropertyInfo>() var defaultMember = declaringType.GetTypeInfo().GetCustomAttribute<DefaultMemberAttribute>(inherit: true);
.Any(p => p.GetGetMethod() == methodExpression.Method); if (defaultMember == null)
{
return false;
}
// Find default property (the indexer) and confirm its getter is the method in this expression.
return declaringType.GetRuntimeProperties().Any(
property => (string.Equals(defaultMember.MemberName, property.Name, StringComparison.Ordinal) &&
property.GetMethod == methodExpression.Method));
} }
} }
} }

View File

@ -90,6 +90,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
return GetString("DynamicViewData_ViewDataNull"); return GetString("DynamicViewData_ViewDataNull");
} }
/// <summary>
/// The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.
/// </summary>
internal static string ExpressionHelper_InvalidIndexerExpression
{
get { return GetString("ExpressionHelper_InvalidIndexerExpression"); }
}
/// <summary>
/// The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.
/// </summary>
internal static string FormatExpressionHelper_InvalidIndexerExpression(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ExpressionHelper_InvalidIndexerExpression"), p0, p1);
}
/// <summary> /// <summary>
/// Must call 'Contextualize' method before using this HtmlHelper instance. /// Must call 'Contextualize' method before using this HtmlHelper instance.
/// </summary> /// </summary>

View File

@ -132,6 +132,9 @@
<data name="DynamicViewData_ViewDataNull" xml:space="preserve"> <data name="DynamicViewData_ViewDataNull" xml:space="preserve">
<value>ViewData value must not be null.</value> <value>ViewData value must not be null.</value>
</data> </data>
<data name="ExpressionHelper_InvalidIndexerExpression" xml:space="preserve">
<value>The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.</value>
</data>
<data name="HtmlHelper_NotContextualized" xml:space="preserve"> <data name="HtmlHelper_NotContextualized" xml:space="preserve">
<value>Must call 'Contextualize' method before using this HtmlHelper instance.</value> <value>Must call 'Contextualize' method before using this HtmlHelper instance.</value>
</data> </data>

View File

@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"Microsoft.CSharp": "4.0.0.0", "Microsoft.CSharp": "4.0.0.0",
"System.Collections": "4.0.0.0", "System.Collections": "4.0.0.0",
"System.Collections.Concurrent": "4.0.0.0",
"System.ComponentModel": "4.0.0.0", "System.ComponentModel": "4.0.0.0",
"System.Diagnostics.Contracts": "4.0.0.0", "System.Diagnostics.Contracts": "4.0.0.0",
"System.Diagnostics.Debug": "4.0.10.0", "System.Diagnostics.Debug": "4.0.10.0",