File: src\libraries\System.Private.CoreLib\src\System\Attribute.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
 
namespace System
{
    [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
    [Serializable]
    [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    public abstract partial class Attribute
    {
        protected Attribute() { }
 
#if !NATIVEAOT
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "Unused fields don't make a difference for equality")]
        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            if (obj == null)
                return false;
 
            if (this.GetType() != obj.GetType())
                return false;
 
            Type thisType = this.GetType();
            object thisObj = this;
            object? thisResult, thatResult;
 
            while (thisType != typeof(Attribute))
            {
                FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
 
                for (int i = 0; i < thisFields.Length; i++)
                {
                    thisResult = thisFields[i].GetValue(thisObj);
                    thatResult = thisFields[i].GetValue(obj);
 
                    if (!AreFieldValuesEqual(thisResult, thatResult))
                    {
                        return false;
                    }
                }
                thisType = thisType.BaseType!;
            }
 
            return true;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "Unused fields don't make a difference for hashcode quality")]
        public override int GetHashCode()
        {
            Type type = GetType();
 
            while (type != typeof(Attribute))
            {
                FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
                object? vThis = null;
 
                for (int i = 0; i < fields.Length; i++)
                {
                    object? fieldValue = fields[i].GetValue(this);
 
                    // The hashcode of an array ignores the contents of the array, so it can produce
                    // different hashcodes for arrays with the same contents.
                    // Since we do deep comparisons of arrays in Equals(), this means Equals and GetHashCode will
                    // be inconsistent for arrays. Therefore, we ignore hashes of arrays.
                    if (fieldValue != null && !fieldValue.GetType().IsArray)
                        vThis = fieldValue;
 
                    if (vThis != null)
                        break;
                }
 
                if (vThis != null)
                    return vThis.GetHashCode();
 
                type = type.BaseType!;
            }
 
            return type.GetHashCode();
        }
#endif
 
        // Compares values of custom-attribute fields.
        private static bool AreFieldValuesEqual(object? thisValue, object? thatValue)
        {
            if (thisValue == null && thatValue == null)
                return true;
            if (thisValue == null || thatValue == null)
                return false;
 
            Type thisValueType = thisValue.GetType();
 
            if (thisValueType.IsArray)
            {
                // Ensure both are arrays of the same type.
                if (!thisValueType.Equals(thatValue.GetType()))
                {
                    return false;
                }
 
                Array thisValueArray = (Array)thisValue;
                Array thatValueArray = (Array)thatValue;
                if (thisValueArray.Length != thatValueArray.Length)
                {
                    return false;
                }
 
                // Attributes can only contain single-dimension arrays, so we don't need to worry about
                // multidimensional arrays.
                Debug.Assert(thisValueArray.Rank == 1 && thatValueArray.Rank == 1);
                for (int j = 0; j < thisValueArray.Length; j++)
                {
                    if (!AreFieldValuesEqual(thisValueArray.GetValue(j), thatValueArray.GetValue(j)))
                    {
                        return false;
                    }
                }
            }
            else
            {
                // An object of type Attribute will cause a stack overflow, but is unpractical to fight every recursion here.
                // There are many ways the default implementation of Equals for ValueTypes or Attributes can lead to an infinite recursion. It is not practical to prevent it.
                // If users will hit this, they should declare custom Equals.
                if (!thisValue.Equals(thatValue))
                    return false;
            }
 
            return true;
        }
 
        public virtual object TypeId => GetType();
 
        public virtual bool Match(object? obj) => Equals(obj);
 
        public virtual bool IsDefaultAttribute() => false;
    }
}