When Should a .NET Class Override Equals()? When Should it Not?

后端 未结 4 1045
小鲜肉
小鲜肉 2020-11-30 10:20

The VS2005 documentation Guidelines for Overloading Equals() and Operator == (C# Programming Guide) states in part

Overriding operator == in non-immut

相关标签:
4条回答
  • 2020-11-30 10:41

    I don't understand your concerns about GetHashCode in regards to HashSet. GetHashCode just returns a number that helps HashSet internally store and lookup values. If the hash code for an object changes the object doesn't get removed from HashSet, it just won't be stored in the most optimal position.

    EDIT

    Thanks to @Erik J I see the point.

    The HashSet<T> is a performance collection and to achieve that performance it relies completely on GetHashCode being constant for the life of the collection. If you want this performance then you need to follow these rules. If you can't then you'll have to switch to something else like List<T>

    0 讨论(0)
  • 2020-11-30 11:01

    The MSDN documentation about not overloading == for mutable types is wrong. There is absolutely nothing wrong for mutable types to implement equality semantics. Two items can be equal now even if they will change in the future.

    The dangers around mutable types and equality generally show up when they are used as a key in a hash table or allow mutable members to participate in the GetHashCode function.

    0 讨论(0)
  • 2020-11-30 11:03

    I came to realize that I wanted Equals to mean two different things, depending on the context. After weighing the input here as well as here, I have settled on the following for my particular situation:

    I'm not overriding Equals() and GetHashCode(), but rather preserving the common but by no means ubiquitous convention that Equals() means identity equality for classes, and that Equals() means value equality for structs. The largest driver of this decision is the behavior of objects in hashed collections (Dictionary<T,U>, HashSet<T>, ...) if I stray from this convention.

    That decision left me still missing the concept of value equality (as discussed on MSDN)

    When you define a class or struct, you decide whether it makes sense to create a custom definition of value equality (or equivalence) for the type. Typically, you implement value equality when objects of the type are expected to be added to a collection of some sort, or when their primary purpose is to store a set of fields or properties.

    A typical case for desiring the concept of value equality (or as I'm terming it "equivalence") is in unit tests.

    Given

    public class A
    {
        int P1 { get; set; }
        int P2 { get; set; }
    }
    
    [TestMethod()]
    public void ATest()
    {
        A expected = new A() {42, 99};
        A actual = SomeMethodThatReturnsAnA();
        Assert.AreEqual(expected, actual);
    }
    

    the test will fail because Equals() is testing reference equality.

    The unit test certainly could be modified to test each property individually, but that moves the concept of equivalence out of the class into the test code for the class.

    To keep that knowledge encapsulated in the class, and to provide a consistent framework for testing equivalence, I defined an interface that my objects implement

    public interface IEquivalence<T>
    {
        bool IsEquivalentTo(T other);
    }
    

    the implementation typically follows this pattern:

    public bool IsEquivalentTo(A other)
    {
        if (object.ReferenceEquals(this, other)) return true;
    
        if (other == null) return false;
    
        bool baseEquivalent = base.IsEquivalentTo((SBase)other);
    
        return (baseEquivalent && this.P1 == other.P1 && this.P2 == other.P2);
    }
    

    Certainly, if I had enough classes with enough properties, I could write a helper that builds an expression tree via reflection to implement IsEquivalentTo().

    Finally, I implemented an extension method that tests the equivalence of two IEnumerable<T>:

    static public bool IsEquivalentTo<T>
        (this IEnumerable<T> first, IEnumerable<T> second)
    

    If T implements IEquivalence<T> that interface is used, otherwise Equals() is used, to compare elements of the sequence. Allowing the fallback to Equals() lets it work e.g. with ObservableCollection<string> in addition to my business objects.

    Now, the assertion in my unit test is

    Assert.IsTrue(expected.IsEquivalentTo(actual));
    
    0 讨论(0)
  • 2020-11-30 11:07

    Check out the Guidelines and rules for GetHashCode by Eric Lippert.

    Rule: the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable

    It is permissible, though dangerous, to make an object whose hash code value can mutate as the fields of the object mutate.

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