#414 – Equivalence Can Be Based on a Subset of Fields

When you define value equality for a type, you typically compare all fields in the two instances to determine whether they are equivalent.  You can also use just a subset of the fields in the comparison.

Below, two Programmer instances are equivalent if their Name and Age properties match.  But their Mood properties are not used in the comparison.

    public class Programmer
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Mood { get; set; }

        // constructor omitted

        public static bool operator ==(Programmer p1, Programmer p2)
        {
            return (p1.Name == p2.Name) && (p1.Age == p2.Age);
        }

        public static bool operator !=(Programmer p1, Programmer p2)
        {
            return !(p1 == p2);
        }
    }

Test results:

            Programmer p1 = new Programmer("Sean", 47, "Elated");
            Programmer p2 = new Programmer("Sean", 47, "Surly");
            Programmer p3 = new Programmer("Bob", 47, "Surly");

            bool check = (p1 == p2);        // true
            check = (p2 == p3);             // false

#412 – Guidelines when Overloading == Operator for a Value Type

When you overload the == operator for a value type, you should also:

  • Overload != operator
  • Override Equals method
  • Overload < and > operators

This is also true for reference types.

#410 – Overloading the == Operator for a Value Type

A user-defined struct automatically inherits an Equals method that performs a value equality check by comparing each field of the struct. The == operator, however, is not automatically defined.  If you want to use the == operator for instances of a struct, you need to overload the == operator.

    public struct PersonHeight
    {
        public int Feet { get; set; }
        public int Inches { get; set; }

        public PersonHeight(int feet, int inches) : this()
        {
            Feet = feet;
            Inches = inches;
        }

        public static bool operator ==(PersonHeight ph1, PersonHeight ph2)
        {
            return (ph1.Feet == ph2.Feet) && (ph1.Inches == ph2.Inches);
        }

        public static bool operator !=(PersonHeight ph1, PersonHeight ph2)
        {
            return !(ph1 == ph2);
        }
    }

Some test cases:

            PersonHeight ph1 = new PersonHeight(5, 10);
            PersonHeight ph2 = new PersonHeight(5, 10);

            // Returns true, default Equals method compares each field
            bool check = ph1.Equals(ph2);

            // == operator also now works - true
            check = (ph1 == ph2);

#409 – Example of Overloading the == Operator

Here’s a full example that shows how to overload the == operator for a reference type.

    public class PersonHeight
    {
        public int Feet { get; set; }
        public int Inches { get; set; }

        // Constructor goes here

        public override bool Equals(object obj)
        {
            return ((obj is PersonHeight) && ((PersonHeight)obj == this));
        }

        public override int GetHashCode()
        {
            return Feet.GetHashCode() ^ Inches.GetHashCode();
        }

        public static bool operator ==(PersonHeight ph1, PersonHeight ph2)
        {
            if (ReferenceEquals(ph1, ph2))
                return true;

            if (((object)ph1 == null) || ((object)ph2 == null))
                return false;

            return (ph1.Feet == ph2.Feet) && (ph1.Inches == ph2.Inches);
        }

        public static bool operator !=(PersonHeight ph1, PersonHeight ph2)
        {
            return !(ph1 == ph2);
        }

        // Also overload < and > operators
    }

Test cases:

            PersonHeight ph1 = new PersonHeight(5, 10);
            PersonHeight ph2 = new PersonHeight(5, 10);
            PersonHeight ph3 = null;

            bool check = ph1.Equals(null);
            check = ph1.Equals("NO!");
            check = ph1.Equals(ph2);
            check = PersonHeight.Equals(ph1, ph2);

            check = (ph1 == null);
            check = (ph3 == null);
            check = (ph1 == ph2);

#408 – Overloading the == Operator for a Reference Type

You can override the Equals method for a type to implement value equality for the type. You can also overload the == operator.

By default, the == operator will do a reference equality check, only returning true if two references point to exactly the same object.  This is normally what you want for a reference type, using the Equals method if you want to check for value equality.  But there might be cases when you want to also overload the == operator, having it also check for value equality.  You might do this if your type represents some basic value, like a complex number.

Some guidelines.  Whenever you overload the == operator, you should also:

  • Overload != operator
  • Override Equals method
  • Overload < and > operators

#405 – Equals Method for Equivalence, == Operator for Identity

You can check for equality between two objects by calling the Equals method or by using the == operator.  There are some differences between how the methods work, but both Equals and == can be overloaded in a user-defined type.

You can therefore change the behavior of one of these methods, or both.  The question is–how should these two behaviors work for a custom class?

Roughly speaking, the desired behavior, from a client’s point of view is:

  • Use Equals method to determine value equality, or equivalence–do the two objects represent the same thing?
  • Use == operator to determine reference equality, or identity–do the two references point to exactly the same object?

This argues for overloading Equals for a user-defined reference type, but not overloading the == operator.

This would result in the following behavior:

            Dog d1 = new Dog("Lassie", 7);
            Dog d2 = new Dog("Lassie", 7);

            bool bValueEquality = d1.Equals(d2);    // true
            bool bRefEquality = d1 == d2;    // false

#400 – Overloading Binary Operators

You can overload any of the following binary operators: +, -, *, /, %, &, |, ^, <<, >>.  A binary operator is an operator applied to two operands.

For example, assume we have a Dog class that has the following bool properties: BarksALot, LikesBalls and Sheds.  We might implement the & operator for the Dog class so that we get a new Dog instance with all of the boolean properties AND’d together.

        public static Dog operator &(Dog d1, Dog d2)
        {
            Dog newDog = new Dog(string.Format("{0} & {1}", d1.Name, d2.Name), 0);

            newDog.BarksALot = d1.BarksALot & d2.BarksALot;
            newDog.LikesBalls = d1.LikesBalls & d2.LikesBalls;
            newDog.Sheds = d1.Sheds & d2.Sheds;

            return newDog;
        }

We can now apply this operator to two Dog instances.

            Dog kirby = new Dog("Kirby", 13);
            kirby.LikesBalls = true;
            kirby.BarksALot = true;

            Dog jack = new Dog("Jack", 10);
            jack.Sheds = true;
            jack.BarksALot = true;

            Dog newguy = kirby & jack;

#399 – Overloading Unary Operators

You can overload any of the following unary operators: +, -, !, ~, ++, –, true, false.  A unary operator is an operator that can be applied to a single operand.

Overloading the negation (!) operator allows us to negate an instance of a class.

            Dog kirby = new Dog("Kirby", 13);

            Dog antiKirby = !kirby;

To overload the operator, we define a new method in our class that takes an instance of a Dog and “negates” it, returning a new instance.

        public static Dog operator !(Dog d1)
        {
            string notName = new string(d1.Name.Reverse().ToArray());

            return new Dog(notName, -1 * d1.Age);
        }

Below is an example of overloading the increment (++) operator.  (We really ought to overload the decrement operator as well).

            Dog olderKirby = kirby++;

        public static Dog operator ++(Dog d1)
        {
            return new Dog(d1.Name, d1.Age++);
        }

#398 – Overloadable Operators

When you define an operator for a class, you are defining the behavior for that operator when acting upon instances of the class.  This is also known as overloading the operator.

You can overload any of the operators listed below.

  • Unary operators  (apply to one operand):  +, -, !, ~, ++, –, true, false
  • Binary operators  (apply to two operands):  +, -, *, /, %, &, |, ^, <<, >>
  • Comparison operators  (apply to two operands): ==, !=, <, >, <=, >=

When you overload the comparison operators, you must overload them in pairs:

  • Implement ==, != together
  • Implement <, > together
  • Implement <=, >= together

#397 – Defining an Operator

You can define an operator in a class, so that the operator can be used in expressions that include instances of the class.

For example, suppose you want to define the plus (+) operator so that it has some meaning when applied to instances of the Dog class.

            Dog k = new Dog("Kirby", 13);
            Dog j = new Dog("Jack", 15);

            Dog mutant = k + j;

To define an operator in your class, you define a new public static method that includes the operator keyword.

In the example below, we define the plus (+) operator for the Dog class, which allows “adding” two dogs.

        public static Dog operator +(Dog d1, Dog d2)
        {
            return new Dog(d1.Name + d2.Name, d1.Age + d2.Age);
        }

This method returns a new instance of a Dog, with the two names appended together and the dogs’ ages added.

Follow

Get every new post delivered to your Inbox.

Join 43 other followers