File: src\System\ValueType.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.
 
/*============================================================
**
**
**
** Purpose: Base class for all value classes.
**
**
===========================================================*/
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System
{
    [Serializable]
    [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    public abstract partial class ValueType
    {
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "Trimmed fields don't make a difference for equality")]
        public override unsafe bool Equals([NotNullWhen(true)] object? obj)
        {
            if (null == obj)
            {
                return false;
            }
 
            if (GetType() != obj.GetType())
            {
                return false;
            }
 
            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp
            if (CanCompareBitsOrUseFastGetHashCode(RuntimeHelpers.GetMethodTable(obj))) // MethodTable kept alive by access to object below
            {
                return SpanHelpers.SequenceEqual(
                    ref RuntimeHelpers.GetRawData(this),
                    ref RuntimeHelpers.GetRawData(obj),
                    RuntimeHelpers.GetMethodTable(this)->GetNumInstanceFieldBytes());
            }
 
            FieldInfo[] thisFields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
            for (int i = 0; i < thisFields.Length; i++)
            {
                object? thisResult = thisFields[i].GetValue(this);
                object? thatResult = thisFields[i].GetValue(obj);
 
                if (thisResult == null)
                {
                    if (thatResult != null)
                        return false;
                }
                else
                if (!thisResult.Equals(thatResult))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        // Return true if the valuetype does not contain pointer, is tightly packed,
        // does not have floating point number field and does not override Equals method.
        private static unsafe bool CanCompareBitsOrUseFastGetHashCode(MethodTable* pMT)
        {
            MethodTableAuxiliaryData* pAuxData = pMT->AuxiliaryData;
 
            if (pAuxData->HasCheckedCanCompareBitsOrUseFastGetHashCode)
            {
                return pAuxData->CanCompareBitsOrUseFastGetHashCode;
            }
 
            return CanCompareBitsOrUseFastGetHashCodeHelper(pMT);
        }
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MethodTable_CanCompareBitsOrUseFastGetHashCode")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static unsafe partial bool CanCompareBitsOrUseFastGetHashCodeHelper(MethodTable* pMT);
 
        /*=================================GetHashCode==================================
        **Action: Our algorithm for returning the hashcode is a little bit complex.  We look
        **        for the first non-static field and get its hashcode.  If the type has no
        **        non-static fields, we return the hashcode of the type.  We can't take the
        **        hashcode of a static member because if that member is of the same type as
        **        the original type, we'll end up in an infinite loop.
        **Returns: The hashcode for the type.
        **Arguments: None.
        **Exceptions: None.
        ==============================================================================*/
        public override unsafe int GetHashCode()
        {
            // The default implementation of GetHashCode() for all value types.
            // Note that this implementation reveals the value of the fields.
            // So if the value type contains any sensitive information it should
            // implement its own GetHashCode().
 
            MethodTable* pMT = RuntimeHelpers.GetMethodTable(this);
            ref byte rawData = ref RuntimeHelpers.GetRawData(this);
            HashCode hashCode = default;
 
            // To get less colliding and more evenly distributed hash codes,
            // we munge the class index into the hashcode
            hashCode.Add((IntPtr)pMT);
 
            if (CanCompareBitsOrUseFastGetHashCode(pMT))
            {
                // this is a struct with no refs and no "strange" offsets
                uint size = pMT->GetNumInstanceFieldBytes();
                hashCode.AddBytes(MemoryMarshal.CreateReadOnlySpan(ref rawData, (int)size));
            }
            else
            {
                object thisRef = this;
                switch (GetHashCodeStrategy(pMT, ObjectHandleOnStack.Create(ref thisRef), out uint fieldOffset, out uint fieldSize, out MethodTable* fieldMT))
                {
                    case ValueTypeHashCodeStrategy.ReferenceField:
                        hashCode.Add(Unsafe.As<byte, object>(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
                        break;
 
                    case ValueTypeHashCodeStrategy.DoubleField:
                        hashCode.Add(Unsafe.As<byte, double>(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
                        break;
 
                    case ValueTypeHashCodeStrategy.SingleField:
                        hashCode.Add(Unsafe.As<byte, float>(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
                        break;
 
                    case ValueTypeHashCodeStrategy.FastGetHashCode:
                        Debug.Assert(fieldSize != 0);
                        hashCode.AddBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AddByteOffset(ref rawData, fieldOffset), (int)fieldSize));
                        break;
 
                    case ValueTypeHashCodeStrategy.ValueTypeOverride:
                        Debug.Assert(fieldMT != null);
                        // Box the field to handle complicated cases like mutable method and shared generic
                        hashCode.Add(RuntimeHelpers.Box(fieldMT, ref Unsafe.AddByteOffset(ref rawData, fieldOffset))?.GetHashCode() ?? 0);
                        break;
                }
            }
 
            return hashCode.ToHashCode();
        }
 
        // Must match the definition in src\vm\comutilnative.cpp
        private enum ValueTypeHashCodeStrategy
        {
            None,
            ReferenceField,
            DoubleField,
            SingleField,
            FastGetHashCode,
            ValueTypeOverride,
        }
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ValueType_GetHashCodeStrategy")]
        private static unsafe partial ValueTypeHashCodeStrategy GetHashCodeStrategy(
            MethodTable* pMT, ObjectHandleOnStack objHandle, out uint fieldOffset, out uint fieldSize, out MethodTable* fieldMT);
 
        public override string? ToString()
        {
            return this.GetType().ToString();
        }
    }
}