How to best implement Equals for custom types?

前端 未结 10 639
春和景丽
春和景丽 2020-11-28 08:41

Say for a Point2 class, and the following Equals:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

This is the

相关标签:
10条回答
  • 2020-11-28 08:59

    Slight variants of forms already posted by several others...

    using System;
    ...
    public override bool Equals ( object obj ) {
       return Equals(obj as SomeClass);
    }
    
    public bool Equals ( SomeClass someInstance ) {
        return Object.ReferenceEquals( this, someInstance ) 
            || ( !Object.ReferenceEquals( someInstance, null ) 
                && this.Value == someInstance.Value );
    }
    
    public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
        if( Object.ReferenceEquals( lhs, null ) ) {
            return Object.ReferenceEquals( rhs, null );
        }
        return lhs.Equals( rhs );
        //OR
        return Object.ReferenceEquals( lhs, rhs )
                || ( !Object.ReferenceEquals( lhs, null ) 
                    && !Object.ReferenceEquals( rhs, null )
                    && lhs.Value == rhs.Value );
    }
    
    public static bool operator !=( SomeClass lhs, SomeClass rhs ) {
        return !( lhs == rhs );
        // OR
        return ( Object.ReferenceEquals( lhs, null ) || !lhs.Equals( rhs ) )
                && !Object.ReferenceEquals( lhs, rhs );
    }
    

    Trying to find a way to implement operator == using Equals to avoid duplicating the value comparison logic... without any redundant tests (ReferenceEquals calls w/ the same parameters) or unnecessary tests (this can't be null in the instance.Equals method) and without any explicit conditionals ("ifs"). More of a mind teaser than anything useful.

    Closest I can think of is this, but it feels like it should be possible without an extra method :)

    public bool Equals ( SomeClass someInstance ) {
        return Object.ReferenceEquals( this, someInstance ) 
            || (!Object.ReferenceEquals( someInstance, null ) && EqualsNonNullInstance( someInstance );
    }
    
    public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
        return Object.ReferenceEquals( lhs, rhs ) 
        || ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.EqualsNonNullInstance( rhs ) );
    }
    
    //super fragile method which returns logical non-sense
    protected virtual bool EqualsNonNullInstance ( SomeClass someInstance ) {
        //In practice this would be a more complex method...
        return this.Value == someInstance.Value;
    }
    

    Remembering how tedious and error prone this all is (I'm almost sure there's an error in the above code... which still sucks because who wants to subclass a Type just to make equality checks slightly simpler?), going forward I think I'll just create some static methods that handle all the null checks and accept a delegate or require and interface to perform the comparison of values (the only part that really changes Type to Type).

    It'd be great if we could just add attributes onto the fields/properties/methods that need to be compared and let the compiler/runtime handle all the tedium.

    Also make sure GetHashCode() values are equal for any instances in which which .Equals(object) returns true or crazy shit can happen.

    0 讨论(0)
  • 2020-11-28 09:03

    There is a whole set of guidelines on MSDN as well. You should read them well, it is both tricky and important.

    A few points I found most helpful:

    • Value Types don't have Identity, so in a struct Point you will usually do a member by member compare.

    • Reference Types usually do have identity, and therefore the Equals test usually stops at ReferenceEquals (the default, no need to override). But there are exceptions, like string and your class Point2, where an object has no useful identity and then you override the Equality members to provide your own semantics. In that situation, follow the guidelines to get through the null and other-type cases first.

    • And there are good reasons to keep GethashCode() and operator== in sync as well.

    0 讨论(0)
  • 2020-11-28 09:06
    public override bool Equals ( object obj )
    {
       // struct
       return obj  is Point2 && Equals (  ( Point2 ) value );
       // class
       //return Equals ( obj as Point2 );
    }
    
    public bool Equals ( Point2 obj )
    
    0 讨论(0)
  • 2020-11-28 09:07

    Using C# 7 and the is type varname pattern (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is#type), provides for a clean Equals(object) that deals with null and type checking using either of the below approaches:

    // using Equals(point)
    public override bool Equals(object obj) =>
        (obj is Point other) && this.Equals(other);
    
    // using the == operator
    public override bool Equals(object obj) =>
        (obj is Point other) && this == other;
    
    

    Obviously, you need to implement at least one of the following as well:

    public bool Equals(Point2 other);
    public static bool operator == (Point2 lhs, Point2 rhs);
    
    0 讨论(0)
  • 2020-11-28 09:08

    In the one that takes an obj, if the type of obj is Point2, call the type specific Equals. Inside the type specific Equals, make sure that all the members have the same value.

    public override bool Equals ( object obj )
    {
       return Equals(obj as Point2);
    }
    
    public bool Equals ( Point2 obj )
    {
       return obj != null && obj.X == this.X && obj.Y == this.Y ... 
       // Or whatever you think qualifies as the objects being equal.
    }
    

    You probably ought to override GetHashCode as well to make sure that objects that are "equal" have the same hash code.

    0 讨论(0)
  • 2020-11-28 09:10

    The simple and best way to override Equals looks like:

    public class Person
        {
            public int Age { get; set; }
            public string Name { get; set; }
    
            public override bool Equals(object other)
            {
                Person otherItem = other as Person;
    
                if (otherItem == null)
                    return false;
    
                return Age == otherItem.Age && Name == otherItem.Name;
            }
            public override int GetHashCode()
            {
                int hash = 13;
                hash = (hash * 7) + Age.GetHashCode();
                hash = (hash * 7) + Name.GetHashCode();
                return hash;
            }
        }
    

    Override the GetHashCode method to allow a type to work correctly in a hash table.

    0 讨论(0)
提交回复
热议问题