Question

What would be the best (most elegant or performing) way of overloading the equality operator on a class containing only string attributes?

Example:

class MagicClass
{
    public string FirstAttribute { get; set; }
    public string SecondAttribute { get; set; }
    public string ThirdAttribute { get; set; }
    public string FourthAttribute { get; set; }
    public string FifthAttribute { get; set; }
}

I know how to overload the operator itself, however, I am wondering about the following points:

  1. Is there a way to elegantly compare such two objects (e.g. without having to write an if statement containing mutual comparisons of all the attributes
  2. What would be a good implementation of the GetHashCode() method in such case
Was it helpful?

Solution

How about something like this, Just create array of all properties and a loop.

internal class MagicClass
{
    public string FirstAttribute { get; set; }
    public string SecondAttribute { get; set; }
    public string ThirdAttribute { get; set; }
    public string FourthAttribute { get; set; }
    public string FifthAttribute { get; set; }

    private string[] AllProperties//Array of all properties
    {
        get
        {
            return new[]
            {
                FirstAttribute,
                SecondAttribute,
                ThirdAttribute,
                FourthAttribute,
                FifthAttribute
            };
        }
    }

    protected bool Equals(MagicClass other)
    {
        var thisProps = this.AllProperties;
        var otherProps = other.AllProperties;

        return thisProps.SequenceEqual(otherProps);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MagicClass) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var thisProps = this.AllProperties;
            int hashCode = 0;
            foreach (var prop in thisProps)
            {
                hashCode = (hashCode * 397) ^ (prop != null ? prop.GetHashCode() : 0);
            }
            return hashCode;
        }
    }
}

Then you can call Equals method inside your operator overload. If you're lazy to create AllProperties array you can use Reflection but IMO reflection is overkill here.

OTHER TIPS

Not saying this is the 'best' or the most elegant solution, but I'd have the tendency to use an array and an index initializer, using an enumeration, so I could reuse get and set logic and in this case reset a hash code for a quick first comparison. The advantage of the enumeration is, that you don't have to recheck your compare logic when an attribute is added, and you can prevent the overhead of resorting to reflection.

class MagicClass
{
    string[] Values = new string[Enum.GetValues(typeof(MagicClassValues)).Length];

    public string this[MagicClassValues Value] //and/or a GetValue/SetValue construction
    {
        get
        {
            return Values[(int)Value];
        }
        set
        {
            Values[(int)Value] = value;
            hash = null;
        }
    }

    int? hash; //buffered for optimal dictionary performance and == comparisson
    public override int GetHashCode()
    {
        if (hash == null)
            unchecked
            {
                hash = Values.Sum(s => s.GetHashCode());
            }
        return hash.Value;
    }

    public static bool operator ==(MagicClass v1, MagicClass v2) //used == operator, in compliance to the question, but this would be better for 'Equals'
    {
        if(ReferenceEquals(v1,v2))return true;
        if(ReferenceEquals(v1,null) || ReferenceEquals(v2,null) || v1.GetHashCode() != v2.GetHashCode())return false;
        return v1.Values.SequenceEqual(v2.Values);
    }
    public static bool operator !=(MagicClass v1, MagicClass v2)
    {
        return !(v1 == v2);
    }

    //optional, use hard named properties as well
    public string FirstAttribute { get { return this[MagicClassValues.FirstAttribute]; } set { this[MagicClassValues.FirstAttribute] = value; } }
}

public enum MagicClassValues
{
    FirstAttribute,
    SecondAttribute,
    //etc
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top