开发者

Writing A Good C# Equals Method

Does anyone have a template for writing a decent equals method - I rememb开发者_如何学Cer in Effective Java there was problems around handling equals when dealing with subclasses.

I dont have the book with me and I cannot remember if it was practical advice - So how do you write a solid robust equals method implementation?


Possibly an off-the-wall suggestion but: consider not overriding Equals in the first place. Basically the nature of equality doesn't work well with subclassing, as you mentioned. However, almost everywhere in the .NET API which uses equality (e.g. dictionaries, hash sets) allows an IEqualityComparer<T> to be passed in. Making a different object responsible for equality makes life much more flexible: you can use different objects to determine which criteria to use.

Implementing IEqualityComparer<T> is much simpler - you still need to check for nullity, but you don't need to worry about whether the types are appropriate, or whether Equals will be further overridden.

Another approach to making the normal Equals work more smoothly is to avoid inheritance entirely for the most part - I can't remember the last time it really made sense in my code to override Equals and allow derived classes. sealed FTW :)


You may have already done this, but did you check out the MSDN Article on implementing Equals()?

Implementing the Equals Method


Properties of a good equals method:

  • Symmetry: For two references, a and b, a.equals(b) if and only if b.equals(a)
  • Reflexivity: For all non-null references, a.equals(a)
  • Transitivity: If a.equals(b) and b.equals(c), then a.equals(c)


If you get tired of writing a lot of boilerplate for this, you can try using a base class that implements it for you.

public abstract class ValueObject<T> : IEquatable<T>
    where T : ValueObject<T>
{
    protected abstract IEnumerable<object> Reflect();

    public override bool Equals(Object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != GetType()) return false;
        return Equals(obj as T);
    }

    public bool Equals(T other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Reflect().SequenceEqual(other.Reflect());
    }

    public override int GetHashCode()
    {
        return Reflect().Aggregate(36, (hashCode, value) => value == null ?
                                hashCode : hashCode ^ value.GetHashCode());
    }

    public override string ToString()
    {
        return "{ " + Reflect().Aggregate((l, r) => l + ", " + r) + " }";
    }
}

Now to make a value-like class, you just say:

public class Person : ValueObject<Person>
{
    public int Age { get; set; }
    public string Name { get; set; }

    protected override IEnumerable<object> Reflect()
    {
        return new object[] { Age, Name };
    }
}

In the Reflect override you return a sequence of values that need to contribute to equality.

Unfortunately this approach can't help with declaring operator == as that has to be specifically declared on the derived type.


Look at Guidelines for Overloading Equals() and Operator == on MSDN.


I generally do something like this:

public struct EmailAddress : IEquatable<EmailAddress>
{
    public override bool Equals(object obj)
    {
        return obj != null && obj.GetType() == typeof(EmailAddress) && Equals((EmailAddress)obj);
    }

    public bool Equals(EmailAddress other)
    {
        return this.LocalPart == other.LocalPart && this.Domain == other.Domain;
    }
}


A "good" equals method is a method that compares a unique part of a class while leaving out parts that do not contribute to the uniqueness. So, if you have a class with a id which is unique you can just use it to establish equality, leaving out any other properties. Often you will also need some timestamp value too, though.


public class Person { public string Name { get; set; }

    public int Age { get; set; }

    public override bool Equals(object obj)
    {
        var objAsPerson = obj as Person;

        if (obj == null)
        {
            return false;
        }

        if (this.Name != objAsPerson.Name)
        {
            return false;
        }

        if (this.Age != objAsPerson.Age)
        {
            return false;
        }

        return true;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜