1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\r
5 * This software is subject to the Microsoft Public License (Ms-PL).
\r
6 * A copy of the license can be found in the license.htm file included
\r
7 * in this distribution.
\r
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc.ExpressionUtil {
\r
15 using System.Collections.Generic;
\r
16 using System.Collections.ObjectModel;
\r
18 using System.Linq.Expressions;
\r
20 // Expression fingerprint class
\r
21 // Contains information used for generalizing, comparing, and recreating Expression instances
\r
23 // Since Expression objects are immutable and are recreated for every invocation of an expression
\r
24 // helper method, they can't be compared directly. Fingerprinting Expression objects allows
\r
25 // information about them to be abstracted away, and the fingerprints can be directly compared.
\r
26 // Consider the process of fingerprinting that all values (parameters, constants, etc.) are hoisted
\r
27 // and replaced with dummies. What remains can be decomposed into a sequence of operations on specific
\r
28 // types and specific inputs.
\r
30 // Some sample fingerprints:
\r
32 // 2 + 4 -> OP_ADD(CONST:int, CONST:int):int
\r
33 // 2 + 8 -> OP_ADD(CONST:int, CONST:int):int
\r
34 // 2.0 + 4.0 -> OP_ADD(CONST:double, CONST:double):double
\r
36 // 2 + 4 and 2 + 8 have the same fingerprint, but 2.0 + 4.0 has a different fingerprint since its
\r
37 // underlying types differ.
\r
39 // "Hello " + "world" -> OP_ADD(CONST:string, CONST:string):string
\r
40 // "Hello " + {model} -> OP_ADD(CONST:string, PARAM:string):string
\r
42 // These string concatenations have different fingerprints since the inputs are provided differently:
\r
43 // one is a hoisted local, the other is a parameter.
\r
45 // ({model} ?? "sample").Length -> MEMBER_ACCESS(String.Length, OP_COALESCE(PARAM:string, CONST:string):string):int
\r
46 // ({model} ?? "other sample").Length -> MEMBER_ACCESS(String.Length, OP_COALESCE(PARAM:string, CONST:string):string):int
\r
48 // These expressions have the same fingerprint.
\r
49 internal abstract class ExpressionFingerprint {
\r
51 protected ExpressionFingerprint(Expression expression) {
\r
52 // since the fingerprints are cached potentially forever, don't keep a reference
\r
53 // to the original expression
\r
55 NodeType = expression.NodeType;
\r
56 Type = expression.Type;
\r
59 // the type of expression node, e.g. OP_ADD, MEMBER_ACCESS, etc.
\r
60 public ExpressionType NodeType {
\r
65 // the CLR type resulting from this expression, e.g. int, string, etc.
\r
71 internal virtual void AddToHashCodeCombiner(HashCodeCombiner combiner) {
\r
72 combiner.AddObject(NodeType);
\r
73 combiner.AddObject(Type);
\r
76 public static ExpressionFingerprint Create(Expression expression, ParserContext parserContext) {
\r
78 BinaryExpression binaryExpression = expression as BinaryExpression;
\r
79 if (binaryExpression != null) {
\r
80 return BinaryExpressionFingerprint.Create(binaryExpression, parserContext);
\r
85 ConditionalExpression conditionalExpression = expression as ConditionalExpression;
\r
86 if (conditionalExpression != null) {
\r
87 return ConditionalExpressionFingerprint.Create(conditionalExpression, parserContext);
\r
92 ConstantExpression constantExpression = expression as ConstantExpression;
\r
93 if (constantExpression != null) {
\r
94 return ConstantExpressionFingerprint.Create(constantExpression, parserContext);
\r
99 MemberExpression memberExpression = expression as MemberExpression;
\r
100 if (memberExpression != null) {
\r
101 return MemberExpressionFingerprint.Create(memberExpression, parserContext);
\r
106 MethodCallExpression methodCallExpression = expression as MethodCallExpression;
\r
107 if (methodCallExpression != null) {
\r
108 return MethodCallExpressionFingerprint.Create(methodCallExpression, parserContext);
\r
113 ParameterExpression parameterExpression = expression as ParameterExpression;
\r
114 if (parameterExpression != null) {
\r
115 return ParameterExpressionFingerprint.Create(parameterExpression, parserContext);
\r
120 UnaryExpression unaryExpression = expression as UnaryExpression;
\r
121 if (unaryExpression != null) {
\r
122 return UnaryExpressionFingerprint.Create(unaryExpression, parserContext);
\r
126 // unknown expression
\r
130 public static ReadOnlyCollection<ExpressionFingerprint> Create(IEnumerable<Expression> expressions, ParserContext parserContext) {
\r
131 List<ExpressionFingerprint> fingerprints = new List<ExpressionFingerprint>();
\r
132 foreach (Expression expression in expressions) {
\r
133 ExpressionFingerprint fingerprint = Create(expression, parserContext);
\r
134 if (fingerprint == null && expression != null) {
\r
135 // something couldn't be parsed properly
\r
139 fingerprints.Add(fingerprint);
\r
142 return new ReadOnlyCollection<ExpressionFingerprint>(fingerprints);
\r
145 public override int GetHashCode() {
\r
146 HashCodeCombiner combiner = new HashCodeCombiner();
\r
147 combiner.AddObject(GetType());
\r
148 AddToHashCodeCombiner(combiner);
\r
149 return combiner.CombinedHash;
\r
152 public override bool Equals(object obj) {
\r
153 ExpressionFingerprint other = obj as ExpressionFingerprint;
\r
154 if (other == null) {
\r
158 return (this.NodeType == other.NodeType
\r
159 && this.Type == other.Type
\r
160 && this.GetType() == other.GetType());
\r
163 protected static Expression ToExpression(ExpressionFingerprint fingerprint, ParserContext parserContext) {
\r
164 return (fingerprint != null) ? fingerprint.ToExpression(parserContext) : null;
\r
167 protected static IEnumerable<Expression> ToExpression(IEnumerable<ExpressionFingerprint> fingerprints, ParserContext parserContext) {
\r
168 return from fingerprint in fingerprints select ToExpression(fingerprint, parserContext);
\r
171 public abstract Expression ToExpression(ParserContext parserContext);
\r