Click here to Skip to main content
15,907,687 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
Given any struct, such that its members are all value types,
C#
struct happy
{
    int foo;
    char bar;
}

ValueType.Equals(object other) works correctly automatically.

So why, if I implement the == operator, am I required warned to override ValueType.Equals? (Correction: I always have Warnings as Errors turned on)

Is this a safeguard in case the struct contains reference types?

Is it bad form to override ValueType.Equals just to call base.Equals?

What is considered best practice?
  • override Equals and GetHashCode and just call base
  • disable warning with #pragma
  • actually implement equality checks on all members or using hashcode generation
  • ?

The #pragma method seems cleanest and less confusion to another developer who might see an override/base and just delete it.

Cheers.
Posted
Updated 28-Nov-13 4:47am
v3
Comments
Rob Philpott 28-Nov-13 10:43am    
Yes, equality in .NET is far more complicated than it seems. My suspicion is that if you have two ways to determine equality, the warning is there just to ensure that they are functionally equivalent.
Sergey Alexandrovich Kryukov 28-Nov-13 10:52am    
No, this is not how it works. The developer is responsible to provide consistent implementation of ==, != and Equals (and also GetHashCode), depending on required semantic. One important aspect (for classes) is the possibility to implement some value semantic instead of referential equivalence.

Important advice: in implementation of any of these 4 methods, it's important not to use '==' or '!=' as it can cause "infinite" recursion...

—SA
Abhinav Gauniyal 29-Nov-13 4:10am    
I could not understand why '==' or '!=' could cause 'infinite recursion' :(
Sergey Alexandrovich Kryukov 29-Nov-13 13:20pm    
When someone tries to use == operator in the definition of == operator... :-)
—SA
Yvan Rodrigues 29-Nov-13 13:22pm    
And I think all of us have done it at least once. :)

Please see my comment to another comment to the question.

You just need to implement the mentioned methods in a consistent way; and your code takes sole responsibility for that.
There are no a "safeguard" for the composed reference-type instance members in your structure. This is also your sole responsibility. The idea is: you describe logical, semantic equivalence. You can completely ignore differences in those members, you can require referential equivalence of those reference members, or you can allow them to be referentially different by equivalent in some other semantic meaning, or any combination of the above. You just need to assume that the code using your structure should be able to freely use any of these three equality/non-equality methods/operators, and you want the results of any of such checks to be consistent.

—SA
 
Share this answer
 
I compared the sources of both Microsoft's and Mono's implementation of System.Drawing.Color.

Equals is overridden and each member is checked for equality.
Microsoft does this both in Equals and in ==.
Mono does this in ==, and calls == to test equality in Equals.

GetHashCode is overriden. Microsoft XORed the member's hashcodes i.e. value.GetHashCode() ^ state.GetHashCode() etc.; Mono does the same but bit shifts each hashcode by 16 bits to the last.

Both define != as !(a == b).

Neither implements IEquatable<color></color>.

For anyone interested,

Microsoft's equality implementation:
C#
    /// <include file='doc\Color.uex' path='docs/doc[@for="Color.operator=="]/*' />
    /// <devdoc>
    ///    <para>
    ///       Tests whether two specified <see cref='System.Drawing.Color'/> objects
    ///       are equivalent.
    ///    </para>
    /// </devdoc>
    public static bool operator ==(Color left, Color right) {
        if (left.value == right.value
            && left.state == right.state
            && left.knownColor == right.knownColor) {

            if (left.name == right.name) {
                return true;
            }

            if (left.name == (object) null || right.name == (object) null) {
                return false;
            }

            return left.name.Equals(right.name);
        }

        return false;
    }

    /// <include file='doc\Color.uex' path='docs/doc[@for="Color.operator!="]/*' />
    /// <devdoc>
    ///    <para>
    ///       Tests whether two specified <see cref='System.Drawing.Color'/> objects
    ///       are equivalent.
    ///    </para>
    /// </devdoc>
    public static bool operator !=(Color left, Color right) {
        return !(left == right);
    }

    /// <include file='doc\Color.uex' path='docs/doc[@for="Color.Equals"]/*' />
    /// <devdoc>
    ///    Tests whether the specified object is a
    /// <see cref='System.Drawing.Color'/>
    /// and is equivalent to this <see cref='System.Drawing.Color'/>.
    /// </devdoc>
    public override bool Equals(object obj) {
        if (obj is Color) {
            Color right = (Color)obj;
            if (value == right.value
                && state == right.state
                && knownColor == right.knownColor) {

                if (name == right.name) {
                    return true;
                }

                if (name == (object) null || right.name == (object) null) {
                    return false;
                }

                return name.Equals(name);
            }
        }
        return false;
    }

    /// <include file='doc\Color.uex' path='docs/doc[@for="Color.GetHashCode"]/*' />
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    public override int GetHashCode() {
        return  value.GetHashCode() ^
                state.GetHashCode() ^
                knownColor.GetHashCode();
    }
}


Mono's equality implementation:
C#
/// <summary>
/// Equality Operator
/// </summary>
///
/// <remarks>
/// Compares two Color objects. The return value is
/// based on the equivalence of the A,R,G,B properties
/// of the two Colors.
/// </remarks>

public static bool operator == (Color left, Color right)
{
    if (left.Value != right.Value)
        return false;
    if (left.IsNamedColor != right.IsNamedColor)
        return false;
    if (left.IsSystemColor != right.IsSystemColor)
        return false;
    if (left.IsEmpty != right.IsEmpty)
        return false;
    if (left.IsNamedColor) {
        // then both are named (see previous check) and so we need to compare them
        // but otherwise we don't as it kills performance (Name calls String.Format)
        if (left.Name != right.Name)
            return false;
    }
    return true;
}

/// <summary>
/// Inequality Operator
/// </summary>
///
/// <remarks>
/// Compares two Color objects. The return value is
/// based on the equivalence of the A,R,G,B properties
/// of the two colors.
/// </remarks>

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

		/// <summary>
		///	Equals Method
		/// </summary>
		///
		/// <remarks>
		///	Checks equivalence of this Color and another object.
		/// </remarks>
		
		public override bool Equals (object obj)
		{
			if (!(obj is Color))
				return false;
			Color c = (Color) obj;
			return this == c;
		}

		/// <summary>
		///	Reference Equals Method
		///	Is commented out because this is handled by the base class.
		///	TODO: Is it correct to let the base class handel reference equals
		/// </summary>
		///
		/// <remarks>
		///	Checks equivalence of this Color and another object.
		/// </remarks>
		//public bool ReferenceEquals (object o)
		//{
		//	if (!(o is Color))return false;
		//	return (this == (Color) o);
		//}



		/// <summary>
		///	GetHashCode Method
		/// </summary>
		///
		/// <remarks>
		///	Calculates a hashing value.
		/// </remarks>
		
		public override int GetHashCode ()
		{
			int hc = (int)(Value ^ (Value >> 32) ^ state ^ (knownColor >> 16));
			if (IsNamedColor)
				hc ^= Name.GetHashCode ();
			return hc;
		}
 
Share this answer
 
Comments
BillWoodruff 28-Nov-13 17:22pm    
+5 Very thoughtful analysis. I look forward, one day, to reading a CodeProject article by you on this topic.
Best practice is:
- implement IEquatable<happy> interface
- override GetHashCode() method
- set your == and != operators to use the Equals() method.

This way:

C#
struct happy : IEquatable<happy>
{
   int foo;
   char bar;

   public bool Equals(happy value)
   {
      return ((this.foo == value.foo) && (this.bar == value.bar));
   }

   override bool Equals(object obj)
   {
      return ((obj is happy) && obj.Equals(this));
   }

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

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

   public override int GetHashCode()
   {
      return foo.GetHashCode() ^ (bar.GetHashCode() << 5);
   }
}


(GetHashCode implementation is just a quick example, you have to take care that there are no collisions in the algorithm you choose)

Hope this helps.
 
Share this answer
 
v6
Comments
Sergey Alexandrovich Kryukov 28-Nov-13 11:03am    
Good clarifying example, even though you probably did not answer to OP's concern... Anyway, a 5. I tried to explain the purpose of such design in Solution 2...
—SA
phil.o 28-Nov-13 11:07am    
Yes, thank you Sergey, I realized that as I read your comment/solution.
My bad, I did not take into account the reference problem.
This is just a quick template of how I do things when I have to implement Equality/Inequality.
I even have two different implementations for structs and classes, but I use code snippets for that and I'm not on my development machine at the moment.
Yvan Rodrigues 28-Nov-13 11:49am    
Good points. I use a similar template for reference types. Keep in mind, I don't this there is a non-generic IEquatable, is there? And operators require two arguments.
phil.o 28-Nov-13 12:11pm    
Yeah, you're right :)
As I told, it was a quick reply from my memory as I'm curretly not on my development machine.
I will correct my solution soon.
And no, there isn't any IEquatable interface (I mixed with IComparable).
Joezer BH 29-Nov-13 1:05am    
5ed!
See Microsoft's view for the topic at Guidelines for Overriding Equals() and Operator == (C# Programming Guide)[^].
Cheers
Andi
 
Share this answer
 
Comments
Yvan Rodrigues 28-Nov-13 11:52am    
Good link. Actually it was my starting point, but I asked because I couldn't find any mentions about the fact that Equals automatically works on value types without being overridden.
Andreas Gieriet 28-Nov-13 12:07pm    
Follow the link at the bottom of that page where the == operator is described. It tells in a few words about the semantic of the implicit == operator of value types.
Cheers
Andi

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900