Showing posts with label c# Equals. Show all posts
Showing posts with label c# Equals. Show all posts

March 6, 2012

Implementing ==, != Operators In Reference Types

For more on equality operators see the Equals Operator Pattern

Microsoft guideline: Guidelines for Overloading Equals() and Operator == (C# Programming Guide)

Find out about The ReferenceEquals() method - Standard class operator == does reference comparison, so it will end up returning false if the two arguments point to different references where both refences hold the same state. To define == operator for a reference type you must be careful to define it in terms of "RefenceEqual()" and "Equals()". If you use == within the definition you will likely get an infinite recursion. This blogs explains why. Value types do not have the same problem as "==" does not do a reference comparison, it compares the value types field by field for equality.

Following example shows how a reference object behaves when the '==' and '!=' operators are overriden and when they are not. The class 'With' has the operators overriden and the class 'Sans' has not.
class TestRefernceTypeEqualityOperator
{
  internal class With
  {
    public string Name { get; set; }
    public int UniqueId { get; set; }

    public override bool Equals(object obj)
    {
      if (obj == null)
        return false;

      if (object.ReferenceEquals(this, obj))
        return true;

      if (!(obj is With))
        return false;
      With selection2 = (With)obj;

      if ((this.UniqueId == selection2.UniqueId) &&
        (this.Name == selection2.Name))
        return true;

      return base.Equals(obj);
    }


    public static bool operator ==(With obj1, With obj2)
    {
      // IF obj1 1 is null
      //   so obj2 must be for equality
      // ELSE obj1 is not null,  
      //   compare it with obj2 using above Equals() operator
      if (ReferenceEquals(obj1, null))    
        return ReferenceEquals(obj2, null); 
      else                  
        return obj1.Equals(obj2);
    }

    public static bool operator !=(With obj1, With obj2)
    {
      return !(obj1 == obj2);
    }


    public override int GetHashCode()
    {
      string ensemble = this.UniqueId.ToString() + this.Name;
      return ensemble.GetHashCode();
    }

  }


  internal class Sans
  {
    public string Name { get; set; }
    public int UniqueId { get; set; }

    public override bool Equals(object obj)
    {
      if (obj == null)
        return false;

      if (object.ReferenceEquals(this, obj))
        return true;

      if (!(obj is Sans))
        return false;
      Sans selection2 = (Sans)obj;

      if ((this.UniqueId == selection2.UniqueId) &&
        (this.Name == selection2.Name))
        return true;

      return base.Equals(obj);
    }


    public override int GetHashCode()
    {
      string ensemble = this.UniqueId.ToString() + this.Name;
      return ensemble.GetHashCode();
    }


  }

  public void Test()
  {
    // Note that 'with' and 'with2' are different references 
    // with the same state
    With with = new With() { Name= "Smeg", UniqueId = 2739 };
    With with2 = new With() { Name = "Smeg", UniqueId = 2739 };
    With with3 = new With() { Name = "Smeg", UniqueId = 2740 };
    Trace.Assert(with.Equals(with2));
    Trace.Assert(with == with2);
    Trace.Assert(!(with != with2));
    Trace.Assert(!(with == null));
    Trace.Assert(!(null == with));

    Trace.Assert(!with.Equals(with3));
    Trace.Assert(with != with3);     
    Trace.Assert(!with2.Equals(with3));
    Trace.Assert(with2 != with3);

    // Note that 'sans' and 'sans2' are different references 
    // with the same state
    Sans sans = new Sans() { Name = "Smeg", UniqueId = 2739 };
    Sans sans2 = new Sans() { Name = "Smeg", UniqueId = 2739 };
    Sans sans3 = new Sans() { Name = "Smeg", UniqueId = 2740 };
    Trace.Assert(sans.Equals(sans2));
    Trace.Assert(!(sans == sans2));
    Trace.Assert(sans != sans2);
    Trace.Assert(!(sans == null));
    Trace.Assert(!(null == sans));

    Trace.Assert(!sans.Equals(sans3));
    Trace.Assert(sans != sans3);
    Trace.Assert(!sans2.Equals(sans3));
    Trace.Assert(sans2 != sans3);
  }
}
The 2 highlited lines show the difference. Without '==', '!=' operator the comparison is by reference so where 'with == with2' succeeds 'sans == sans2' fails even though the different references refer to objects with exactly the same state (and even though the 'Equals()' method does return true in this case).

August 30, 2005

Equals Operator Pattern

Equality in C#

Pattern for implementing required operators to support '==', '!=', 'Equals()' and 'GetHashCode()' for a new reference or value type. Need to implement these methods together.

For REFERENCE types
public override bool Equals(object rhs)
{
    if (rhs == null) // this cannot be null
        return false;

    if (object.ReferenceEquals(this, rhs)
        return true;

    // Check for null values and compare run-time types.
    if (this.GetType() != rhs.GetType())
         return false;

    return CompareFields(rhs as XXX);
}

private bool CompareFields(XXX p)
{ // Field by field comparison
  return (this.field1 == this.field2) && ...; 
}


public override int GetHashCode()
{   // Aim to get a unique number from the objects state 
    int tmp = ((int)(field1 << 16) + field2) ... ;
    return tmp;
}

In general it is not recommended to override the '==' and '!=' operators for reference types. They should only be overriden for immutable types. However here is a suitable implementation:
public static bool operator ==(XXX obj1, XXX obj2)
{
// IF obj1 is null
//  so obj2 must be for equality
// ELSE obj1 is not null,  
//  compare it with obj2 using above Equals() operator
    if (ReferenceEquals(obj1, null))        
        return ReferenceEquals(obj2, null); 
    else                                    
        return obj1.Equals(obj2);           
}

public static bool operator !=(XXX obj1, XXX obj2)
{
    return !(obj1 == obj2);
}
For VALUE types
public override bool Equals(object rhs)
{
    if (rhs == null) // this cannot be null
        return false;

    return Equals(rhs as XXX);
}

public override bool Equals(XXX rhs)
{
    return CompareFields(rhs);
}


private bool CompareFields(XXX p)
{ // Field by field comparison
  return (this.field1 == this.field2) && ...; 
}

public override int GetHashCode()
{   // Aim to get a unique number from the objects state 
    int tmp = ((int)(field1 << 16) + field2) ... ;

    return tmp;
}

// For VALUE types
public static bool operator ==(XXX lhs, XXX rhs)
{
    bool res = lhs.Equals(rhs);
    return res;
}

public static bool operator !=(XXX lhs, XXX rhs)
{ 
    return !(lhs==rhs); 
}

Here is a Unit Test To Test the Equals operator Create 'x', 'y' and 'z' that are the same and an object 'a' that is not

TestEqualsOperator()
{
  // Ensure all the XXX objects have the same constructor 
  // except the one called 'different'
  XXX a = new XXX(...);
  XXX x = new XXX(...);
  XXX y = new XXX(...);
  XXX z = new XXX(...);
  XXX different = new XXX(...); 

  Debug.Assert(x.Equals(x) == true);
  Debug.Assert(x.Equals(y) == y.Equals(x));
  Debug.Assert((x.Equals(y) && y.Equals(z)) && x.Equals(z));
  Debug.Assert(x.Equals(null) == false);

  Debug.Assert(a.Equals(different) == false);
  Debug.Assert(different.Equals(a) == false);
  Debug.Assert(x.Equals(different) == false);
  Debug.Assert(different.Equals(x) == false);
  Debug.Assert(y.Equals(different) == false);
  Debug.Assert(different.Equals(y) == false);
  Debug.Assert(z.Equals(different) == false);
  Debug.Assert(different.Equals(z) == false);

}