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