结构包装器类型的默认相等性比较 C#

Default Equality Comparison of Struct Wrapper types C#

C# 参考指出对于值类型:

The ValueType.Equals(Object) method overrides Object.Equals(Object) and provides the default implementation of value equality for all value types in the .NET Framework.

If none of the fields of the current instance and obj are reference types, the Equals method performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields of obj and this instance.

https://msdn.microsoft.com/en-us/library/2dts52z7(v=vs.110).aspx

因此,由于 int 是一种值类型,我希望 int 的简单包装器等于它包装的 int,因为它在逐字节比较时是相同的——它们都只包含一个单个整数:

    public struct Id
    {
        public Id(int id)
        {
            Id = id;
        }
        public int Id { get; }
    }

    Console.WriteLine(new Id(17).Equals(17);

但它实际上打印错误。这是为什么?

这些不是同一类型。尽管文本没有明确说明,Equals 方法 checks 它们是同一类型。

所以这会起作用:

new Id(17).Equals(new Id(17));

如果你想在你的结构上处理两种不同类型的比较,你需要覆盖 Equals 并自己处理。

relevant source是:

public abstract class ValueType {
    [System.Security.SecuritySafeCritical]
    public override bool Equals (Object obj) {
        BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)");
        if (null==obj) {
            return false;
        }
        RuntimeType thisType = (RuntimeType)this.GetType();
        RuntimeType thatType = (RuntimeType)obj.GetType();

        if (thatType!=thisType) {
            return false;
        }

        Object thisObj = (Object)this;
        Object thisResult, thatResult;

        // if there are no GC references in this object we can avoid reflection 
        // and do a fast memcmp
        if (CanCompareBits(this))
            return FastEqualsCheck(thisObj, obj);

        FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        for (int i=0; i<thisFields.Length; i++) {
            thisResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(thisObj);
            thatResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(obj);

            if (thisResult == null) {
                if (thatResult != null)
                    return false;
            }
            else
            if (!thisResult.Equals(thatResult)) {
                return false;
            }
        }

        return true;
    }

如您所见,它会在检查字节之前验证参数的类型是否匹配。由于您的类型不是 int,它总是 return false。

一般来说,对象的 Equals 方法不会认为它等于任何其他类型的任何对象,即使两个对象都封装了相同的值。

由于重载,将某些类型的值传递给其他类型的 Equals 方法的行为可能会导致它们被转换为与原始类型相同的类型。例如,(16777216.0f).Equals(16777217) 将 select 重载 Equals(float),将 int16777217 转换为最接近的 float 值(即 16777216.0f) 将依次比较等于 16777216.0f。可以通过将任一操作数转换为 object 来防止这种行为,如 ((object)16777.216.0f).Equals(16777216)(16777.216.0f).Equals((object)16777216),其中任何一个都会报告 float 对象不等于 int 对象(即使它们具有相同的数值)。