1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 
7 namespace System
8 {
9     [AttributeUsageAttribute(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
10     [Serializable]
11     [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
12     public abstract partial class Attribute
13     {
Attribute()14         protected Attribute() { }
15 
16         //
17         // Compat note: .NET Core changed the behavior of Equals() relative to the full framework:
18         //
19         //    (https://github.com/dotnet/coreclr/pull/6240)
20         //
21         // This implementation implements the .NET Core behavior.
22         //
Equals(Object obj)23         public override bool Equals(Object obj)
24         {
25             if (obj == null)
26                 return false;
27 
28             if (this.GetType() != obj.GetType())
29                 return false;
30 
31             Object[] thisFieldValues = this.ReadFields();
32             Object[] thatfieldValues = ((Attribute)obj).ReadFields();
33 
34             for (int i = 0; i < thisFieldValues.Length; i++)
35             {
36                 // Visibility check and consistency check are not necessary.
37                 Object thisResult = thisFieldValues[i];
38                 Object thatResult = thatfieldValues[i];
39 
40                 if (!AreFieldValuesEqual(thisResult, thatResult))
41                 {
42                     return false;
43                 }
44             }
45 
46             return true;
47         }
48 
49         // Compares values of custom-attribute fields.
AreFieldValuesEqual(Object thisValue, Object thatValue)50         private static bool AreFieldValuesEqual(Object thisValue, Object thatValue)
51         {
52             if (thisValue == null && thatValue == null)
53                 return true;
54             if (thisValue == null || thatValue == null)
55                 return false;
56 
57             Type thisValueType = thisValue.GetType();
58 
59             if (thisValueType.IsArray)
60             {
61                 // Ensure both are arrays of the same type.
62                 if (!thisValueType.Equals(thatValue.GetType()))
63                 {
64                     return false;
65                 }
66 
67                 Array thisValueArray = thisValue as Array;
68                 Array thatValueArray = thatValue as Array;
69                 if (thisValueArray.Length != thatValueArray.Length)
70                 {
71                     return false;
72                 }
73 
74                 // Attributes can only contain single-dimension arrays, so we don't need to worry about
75                 // multidimensional arrays.
76                 Debug.Assert(thisValueArray.Rank == 1 && thatValueArray.Rank == 1);
77                 for (int j = 0; j < thisValueArray.Length; j++)
78                 {
79                     if (!AreFieldValuesEqual(thisValueArray.GetValue(j), thatValueArray.GetValue(j)))
80                     {
81                         return false;
82                     }
83                 }
84             }
85             else
86             {
87                 // An object of type Attribute will cause a stack overflow.
88                 // However, this should never happen because custom attributes cannot contain values other than
89                 // constants, single-dimensional arrays and typeof expressions.
90                 Debug.Assert(!(thisValue is Attribute));
91                 if (!thisValue.Equals(thatValue))
92                     return false;
93             }
94 
95             return true;
96         }
97 
GetHashCode()98         public override int GetHashCode()
99         {
100             Object vThis = null;
101 
102             Object[] fieldValues = this.ReadFields();
103             for (int i = 0; i < fieldValues.Length; i++)
104             {
105                 Object fieldValue = fieldValues[i];
106 
107                 // The hashcode of an array ignores the contents of the array, so it can produce
108                 // different hashcodes for arrays with the same contents.
109                 // Since we do deep comparisons of arrays in Equals(), this means Equals and GetHashCode will
110                 // be inconsistent for arrays. Therefore, we ignore hashes of arrays.
111                 if (fieldValue != null && !fieldValue.GetType().IsArray)
112                     vThis = fieldValue;
113 
114                 if (vThis != null)
115                     break;
116             }
117 
118             if (vThis != null)
119                 return vThis.GetHashCode();
120 
121             Type type = GetType();
122 
123             return type.GetHashCode();
124         }
125 
126         public virtual object TypeId => GetType();
127 
Match(object obj)128         public virtual bool Match(object obj) => Equals(obj);
129 
IsDefaultAttribute()130         public virtual bool IsDefaultAttribute() => false;
131 
132         //
133         // This non-contract method is known to the IL transformer. See comments around _ILT_ReadFields() for more detail.
134         //
135         [CLSCompliant(false)]
_ILT_GetNumFields()136         protected virtual int _ILT_GetNumFields()
137         {
138             return 0;
139         }
140 
141         //
142         // This non-contract method is known to the IL transformer. The IL transformer generates an override of this for each specific Attribute class.
143         // Together with _ILT_GetNumFields(), it fetches the same field values that the desktop would have for comparison.
144         //
145         // .NET Core uses "GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)" to
146         // determine the list of fields used for comparison. Unfortunately, this list can include fields that the "this" class has no right to access (e.g. "internal"
147         // fields in base classes defined in another assembly.) Thus, the IL Transformer cannot simply generate a method to walk the fields and
148         // be done with it. Instead, _ILT_ReadFields() directly fetches only the directly declared fields and reinvokes itself non-virtually on its
149         // base class to get any inherited fields. To simplify the IL generation, the generated method only writes the results into a specified
150         // offset inside a caller-supplied array. Attribute.ReadFields() calls _ILT_GetNumFields() to figure out how large an array is needed.
151         //
152         [CLSCompliant(false)]
_ILT_ReadFields(Object[] destination, int offset)153         protected virtual void _ILT_ReadFields(Object[] destination, int offset)
154         {
155         }
156 
157         //
158         // ProjectN: Unlike the desktop, low level code such as Attributes cannot go running off to Reflection to fetch the FieldInfo's.
159         // Instead, we use the ILTransform to generate a method that returns the relevant field values, which we then compare as the desktop does.
160         //
ReadFields()161         private Object[] ReadFields()
162         {
163             int numFields = _ILT_GetNumFields();
164             Object[] fieldValues = new Object[numFields];
165             _ILT_ReadFields(fieldValues, 0);
166             return fieldValues;
167         }
168     }
169 }
170