Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly override equality?

I am still new to overloading operators. I thought i was doing a great job until i hit this problem. NullReferenceException is thrown on the != operator. I assume its using it in the CompareTo method but i'm not totally sure. If anyone can point me in the right direction i would be very grateful.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            List<Task> tasks = new List<Task>();
            tasks.Add(new Task( "first",  DateTime.Now.AddHours(2)));
            tasks.Add(new Task( "second", DateTime.Now.AddHours(4)));
            tasks.TrimExcess();
            tasks.Sort();
        }
    }
    public class Task : IComparable
    {
        public Task()
        {
        }
        public Task(string nameIn, DateTime dueIn)
        {
            nameOfTask = nameIn;
            dateDue = dueIn;
        }
        DateTime dateDue;
        string nameOfTask;

        public static bool operator <(Task t1, Task t2)
        {
            return (t1.dateDue < t2.dateDue);
        }
        public static bool operator >(Task t1, Task t2)
        {
            return (t1.dateDue > t2.dateDue);
        }
        public static bool operator ==(Task t1, Task t2)
        {
            return (t1.dateDue == t2.dateDue);
        }
        public static bool operator !=(Task t1, Task t2)
        {
                return (t1.dateDue != t2.dateDue);

        }
        public override int GetHashCode()
        {
            return Int32.Parse(this.dateDue.ToString("yyyymmddhhmmss"));
        }
        public override bool Equals(System.Object obj)
        {
            if (obj == null) return false;    

            Task t = obj as Task;
            if ((System.Object)t == null) return false;
            return (this.dateDue == t.dateDue);
        }
        int IComparable.CompareTo(object obj)
        {
            if (obj == null) return 1;

            Task t = obj as Task;
            if (t != null)
            {
                return this.dateDue.CompareTo(t.dateDue);
            }
            else
                throw new ArgumentException("Object is not a Task");
        }
    }
}

When i comment out the binaory operators the program functions as intended. My question is how can i protect my binary operators from null references so i can keep them for manual comparisons? Thank you for your time.

like image 590
Powe8525 Avatar asked Apr 23 '12 21:04

Powe8525


2 Answers

Both the answers given so far are wrong. The accepted answer is wrong because it accidentally recurses. The other answer is wrong because it says that null is not equal to null.

Your implementations of the operators are all wrong; they are required to correctly handle null inputs.

Your implementation of GetHashCode is deeply broken; you attempt to put a fourteen digit number into a format that can accept nine digits. Simply call GetHashCode on the date; there is no need to go through this rigamarole of turning it into a string and then turning that into a number!

The right way to write the code is to use object.ReferenceEquals to do reference comparisons rather than using the == and != operators; it is far too easy to do an accidental recursion.

The typical pattern goes like this:

public static bool operator ==(Task t1, Task t2)         
{
    if (object.ReferenceEquals(t1, t2)) return true;
    // All right. We know that they are (1) not the same object, and
    // (2) not both null. Maybe one of them is null.
    if (object.ReferenceEquals(t1, null)) return false;
    if (object.ReferenceEquals(t2, null)) return false;
    // They are not the same object and both are not null.
    return t1.dateDue == t2.dateDue;
}

public static bool operator !=(Task t1, Task t2)         
{
    // Simply call the == operator and invert it.
    return !(t1 == t2);
}

public override bool Equals(object t)
{
    return (t as Task) == this;   
}

public override int GetHashCode()
{
    return this.dateDue.GetHashCode();
}

The other comparison operators are left as an exercise.

like image 194
Eric Lippert Avatar answered Oct 08 '22 18:10

Eric Lippert


It looks like one of the Task objects that you are comparing with != is set to null. The built-in operator != compares the references and does not break, but your operator tries to dereference the task, and breaks.

public static bool operator !=(Task t1, Task t2) {
    if (ReferenceEquals(t1, null)) {
        return !ReferenceEquals(t2, null); // return true only if t2 is *not* null
    }
    if (ReferenceEquals(t2, null)) {
        return true; // we know that t1 is not null
    }
    return (t1.dateDue != t2.dateDue);
}

This implementation returns false when both tasks are null. You should implement a symmetric null checking in the == operator.

like image 24
Sergey Kalinichenko Avatar answered Oct 08 '22 18:10

Sergey Kalinichenko