0

I have a struct representing a Measure in my program, which I want to be interchangeable with double.

I have implemented it, and everything seems to be working fine, except for one of my tests:

[Test]
public void TestMeasureDefaultConstructor()
{
    Measure m = new Measure();
    // These pass
    Assert.That(m.Equals(0), Is.True);
    Assert.That(0 == m, Is.True);
    Assert.That((double) m, Is.EqualTo(0));
    Assert.That((float) m, Is.EqualTo(0));
    Assert.That(0, Is.EqualTo(m));
    // The next two fail
    Assert.That(m, Is.EqualTo(0.0));
    Assert.That(0.0.Equals(m), Is.True);
}

Everything works up to the line with Is.EqualTo(0.0).

The two lines testing that 0.0 Equals m each fail as follows:

Message:   Expected: 0.0d
  But was:  MyCode.Model.Measure

(obviously only the first fails, so I have to swap them round to prove this).

I have clearly implemented Equals for my Measure class sufficiently, but I can't work out how to extend double.Equals to make the last two work. I have done a lot of searches, but keep coming on how to implement Equals for my class rather than the other way round.

Here is my class, possibly overkill now after trying all sorts of ways to make it work:

/// <summary>
/// The Measure class encapsulates the unit of measurement.
/// </summary>
public struct Measure
{
    #region Construction and destruction
    public Measure(double v)
    {
        Value = v;
    }
    #endregion
    #region Conversions
    public static implicit operator Measure(double v)
    {
        return new Measure(v);
    }

    public static implicit operator double(Measure m)
    {
        return m.Value;
    }

    public static implicit operator float(Measure m)
    {
        return (float) m.Value;
    }
    #endregion

    #region Properties
    public double Value { get; set; }
    #endregion

    #region Equality
    public static bool Equals(Measure a, Measure b)
    {
        // Constants.IsEqual checks abs(a - b) < Tolerance.
        return Constants.IsEqual(a.Value, b.Value);
    }
    public static bool Equals(Measure a, object b)
    {
        return a.Equals(b);
    }
    public static bool Equals(object a, Measure b)
    {
        return b.Equals(a);
    }
    public bool Equals(Measure other)
    {
        return Equals(this, other);
    }
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        try
        {
            double d = Convert.ToDouble(obj);
            return Equals(d);
        }
        catch (InvalidCastException)
        {
            return false;
        }
        catch (Exception e)
        {
            // Shouldn't get here!
            Debug.Print("Unexpected exception in Measure.Equals: ", e);
            if (System.Diagnostics.Debugger.IsAttached)
                System.Diagnostics.Debugger.Break();
            return false;
        }
    }

    public static bool operator == (Measure x, Measure y)
    {
        return x.Equals(y);
    }
    public static bool operator != (Measure x, Measure y)
    {
        return !(x == y);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
    #endregion
}

I have created a couple of extension methods:

public static class ExtensionMethods
{
    public static bool Equals(this object a, Measure b)
    {
        return b.Equals(a);
    }
    public static bool Equals(this double a, Measure b)
    {
        return b.Equals(a);
    }
}

I have put breakpoints on every Equals function, and they get hit during the passing tests, but none of these functions gets called for double.Equals so clearly the compiler doesn't think any of them are compatible.

Is this possible? If so, what am I missing, and how do I implement it?

Thanks in advance for any pointers.

Ian

Ian Brockbank
  • 497
  • 5
  • 14
  • Are you sure the problem is with your code rather than what `Is.EqualTo` does? What does `Assert.True(m == 0.0)` do? – Jon Skeet Jan 05 '19 at 15:14
  • Pretty sure this is just how NUnit works. The second parameter is a `IResolveConstraint` so somewhere internal to NUnit is where the comparison is happening, I assume because a `double` is not the same type as a `Measure` and it doesn't check one can be cast as the other. If you do `Assert.That(m, Is.EqualTo(0.0))` though, that will work. – DavidG Jan 05 '19 at 15:33
  • @JonSkeet that was my first suspicion, but Assert.That(0.0.Equals(m), Is.True); also fails. My original code was Assert.AreEqual(0.0, m) and that was failing, so it's not to do with the constraint style of assertion. – Ian Brockbank Jan 05 '19 at 17:35
  • 1
    @IanBrockbank: That one's simple: `0.0.Equals(m)` will call the `Equals` method on `double`, as per NaDeR-Star's answer. There's nothing you can do about that. – Jon Skeet Jan 05 '19 at 18:23
  • @JonSkeet so you think it's not possible to do Assert.AreEqual(0.0, m), and it always has to be the other way round - Assert.AreEqual(m. 0.0)? Is that an NUnit restriction? – Ian Brockbank Jan 05 '19 at 23:19
  • @Ian: I'd have to look at exactly what Assert.AreEqual does to answer that, but 0.0.Equals(m) is a different matter, and probably more important. That doesn't depend on NUnit at all. – Jon Skeet Jan 05 '19 at 23:29

0 Answers0