简体   繁体   中英

Default Equality Comparison of Struct Wrapper types C#

The C# Reference states that for value types:

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

As such since int is a value type I would expect that a simple wrapper for an int, would be equal to the int it wrapped, since it is the same on a byte by byte comparison - they both only contain a single int:

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

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

But it actually prints false. Why is this?

These aren't the same type. Though the text doesn't explicitly say that, the Equals method checks that they are the same type.

So this would work:

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

If you want to handle comparison of two different types on your struct, you need to override Equals and handle that yourself.

The relevant source is:

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;
    }

As you can see, it validates that the type of the arguments match before it checks the bytes. Since your type is not an int , it'll always return false.

In general, the Equals method of an object will not consider it equal to any object of any other type, even if both objects would encapsulate the same value.

Because of overloading, the act of passing values of certain types to the Equals methods of other types may cause them to get converted to the same type as the original. For example, (16777216.0f).Equals(16777217) will select the overload Equals(float) , convert the int value 16777217 into the nearest float value (ie 16777216.0f ) which will in turn compare equal to 16777216.0f . This behavior could be prevented by converting either operand to object , as in ((object)16777.216.0f).Equals(16777216) or (16777.216.0f).Equals((object)16777216) , either of which would report a float object as unequal to an int object (even though they hold the same numerical value).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM