1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 5 namespace System.Web.Mvc.ExpressionUtil 6 { 7 // Expression fingerprint chain class 8 // Contains information used for generalizing, comparing, and recreating Expression instances 9 // 10 // Since Expression objects are immutable and are recreated for every invocation of an expression 11 // helper method, they can't be compared directly. Fingerprinting Expression objects allows 12 // information about them to be abstracted away, and the fingerprints can be directly compared. 13 // Consider the process of fingerprinting that all values (parameters, constants, etc.) are hoisted 14 // and replaced with dummies. What remains can be decomposed into a sequence of operations on specific 15 // types and specific inputs. 16 // 17 // Some sample fingerprints chains: 18 // 19 // 2 + 4 -> OP_ADD, CONST:int, NULL, CONST:int 20 // 2 + 8 -> OP_ADD, CONST:int, NULL, CONST:int 21 // 2.0 + 4.0 -> OP_ADD, CONST:double, NULL, CONST:double 22 // 23 // 2 + 4 and 2 + 8 have the same fingerprint, but 2.0 + 4.0 has a different fingerprint since its 24 // underlying types differ. Note that this looks a bit like prefix notation and is a side effect 25 // of how the ExpressionVisitor class recurses into expressions. (Occasionally there will be a NULL 26 // in the fingerprint chain, which depending on context can denote a static member, a null Conversion 27 // in a BinaryExpression, and so forth.) 28 // 29 // "Hello " + "world" -> OP_ADD, CONST:string, NULL, CONST:string 30 // "Hello " + {model} -> OP_ADD, CONST:string, NULL, PARAM_0:string 31 // 32 // These string concatenations have different fingerprints since the inputs are provided differently: 33 // one is a constant, the other is a parameter. 34 // 35 // ({model} ?? "sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string 36 // ({model} ?? "other sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string 37 // 38 // These expressions have the same fingerprint since all constants of the same underlying type are 39 // treated equally. 40 // 41 // It's also important that the fingerprints don't reference the actual Expression objects that were 42 // used to generate them, as the fingerprints will be cached, and caching a fingerprint that references 43 // an Expression will root the Expression (and any objects it references). 44 45 internal sealed class ExpressionFingerprintChain : IEquatable<ExpressionFingerprintChain> 46 { 47 public readonly List<ExpressionFingerprint> Elements = new List<ExpressionFingerprint>(); 48 Equals(ExpressionFingerprintChain other)49 public bool Equals(ExpressionFingerprintChain other) 50 { 51 // Two chains are considered equal if two elements appearing in the same index in 52 // each chain are equal (value equality, not referential equality). 53 54 if (other == null) 55 { 56 return false; 57 } 58 59 if (this.Elements.Count != other.Elements.Count) 60 { 61 return false; 62 } 63 64 for (int i = 0; i < this.Elements.Count; i++) 65 { 66 if (!Equals(this.Elements[i], other.Elements[i])) 67 { 68 return false; 69 } 70 } 71 72 return true; 73 } 74 Equals(object obj)75 public override bool Equals(object obj) 76 { 77 return Equals(obj as ExpressionFingerprintChain); 78 } 79 GetHashCode()80 public override int GetHashCode() 81 { 82 HashCodeCombiner combiner = new HashCodeCombiner(); 83 Elements.ForEach(combiner.AddFingerprint); 84 return combiner.CombinedHash; 85 } 86 } 87 } 88