Overloading == operator for class containing only string attributes
Asked Answered
M

2

1

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
Miki answered 28/4, 2014 at 11:51 Comment(4)
The fact that it's mutable throughout makes this non-ideal for equality checks in general. Does it have to be mutable?Invulnerable
The obvious implementation (compare all strings directly) is likely the most performant...Bicorn
Beware that it's very hard to come up with an hash function that uniquely identifies an object (unless it's an Int32 or UInt32 or the likes). An hash code on guarantees that if the hash code of two objects is different, than the objects are different. If the hash codes are equal, than the objects might be equal.Astrogation
In the problem I am working on, the values are mutable. However, I am interested now: what would be an optimization in case the values are only assigned at construction and not modified later (basically readonly)?Lander
W
2

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.

Windswept answered 28/4, 2014 at 12:7 Comment(0)
F
0

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
}
Fader answered 28/4, 2014 at 12:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.