简体   繁体   中英

C# equality checking

What's your approach on writing equality checks for the structs and classes you create?

1) Does the "full" equality checking require that much of boilerplate code (like override Equals , override GetHashCode , generic Equals , operator== , operator!= )?

2) Do you specify explicitly that your classes model the IEquatable<T> interface?

3) Do I understand correctly, that there is no actual way to automatically apply Equals overrides, when I invoke something like a == b and I always have to implement both the Equals and operator== members?

You're right, this is a lot of boiler-plate code, and you need to implement everything separately.

I would recommend:

  • If you're going to implement value equality at all, override GetHashCode and Equals(object) - creating overloads for == and implementing IEquatable<T> without doing that could result in very unexpected behaviour
  • I would always implement IEquatable<T> if you're overriding Equals(object) and GetHashCode
  • I only overload the == operator more rarely
  • Implementing equality correctly for unsealed classes is tricky, and can still produce surprising/undesirable results. If you need equality for types in a hierarchy, implement IEqualityComparer<T> expressing the comparison you're interested in.
  • Equality for mutable types is usually a bad idea, as two objects can be equal and then unequal later... if an object is mutated (in an equality-affecting way) after it's been used as a key in a hash table, you won't be able to find it again.
  • Some of the boiler-plate is slightly different for structs... but like Marc, I very rarely write my own structs.

Here's a sample implementation:

using System;

public sealed class Foo : IEquatable<Foo>
{
    private readonly string name;
    public string Name { get { return name; } }

    private readonly int value;
    public int Value { get { return value; } }

    public Foo(string name, int value)
    {
        this.name = name;
        this.value = value;
    }

    public override bool Equals(object other)
    {
        return Equals(other as Foo);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        hash = hash * 31 + (name == null ? 0 : name.GetHashCode());
        hash = hash * 31 + value;
        return hash;
    }

    public bool Equals(Foo other)
    {
        if ((object) other == null)
        {
            return false;
        }
        return name == other.name && value == other.value;
    }

    public static bool operator ==(Foo left, Foo right)
    {
        return object.Equals(left, right);
    }

    public static bool operator !=(Foo left, Foo right)
    {
        return !(left == right);
    }
}

And yes, that's a heck of a lot of boilerplate, very little of which changes between implementations :(

The implementation of == is slightly less efficient than it might be, as it will call through to Equals(object) which needs to do the dynamic type check... but the alternative is even more boiler-plate, like this:

public static bool operator ==(Foo left, Foo right)
{
    if ((object) left == (object) right)
    {
        return true;
    }

    // "right" being null is covered in left.Equals(right)
    if ((object) left == null)
    {
        return false;
    }
    return left.Equals(right);
}

I rarely do anything special for classes; for most regular objects referential equality works fine.

I even more rarely write a struct ; but since structs represent values it is usually appropriate to provide equality etc. This would usually involve everything; Equals, ==, != and IEquatable<T> (since this avoids boxing in scenarios using EqualityComparer<T>.Default .

The boilerplate isn't usually too problematic, but IIRC tools like resharper can help here.

Yes, it is advisable to keep Equals and == in sync, and this needs to be done explicitely.

You just need to implement operator== for a==b .
As I like my data in dictionaries sometimes I override GetHashCode.
Next I implement Equals (as an unmentioned standard ... this is because there is no constraint for equality when using generics) and specify implementing IEquatable. Since I am going to do this I might as well point my == and != implementations to Equals. :)

See What is "Best Practice" For Comparing Two Instances of a Reference Type?

You can avoid boiler plate code (hope C#/VS team brings something easy for developers in their next iteration) with the help of a snippet, here is one such..

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