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