18

Introduction:
I have a few classes which do the same work, but with different value types (e.g. Vectors of floats or integers).
Now I want to be able to check for equality, this equality should also work in between the types (such as vectorF == vectorI).
Also, it should be possible to do a null check (vectorF == null).

Approach:
My approach is to create multiple overloads for the == and != operators, one for each possible combination.

public sealed class VectorF
{
    [...]

    public static bool operator == (VectorF left, VectorI right)
    {
        // Implementation...
    }

    public static bool operator == (VectorF left, VectorF right)
    {
        // Implementation...
    }

    // Same for != operator
    [...]
}

Problem:
Using multiple overloads, I cannot just do a null check with the == operator, as the call would be ambiguous.

var v = new VectorF([...]);

if (v == null)    // This call is ambiguous
[...]

I'm aware of the possibility to use a ReferenceEquals or null casting instead, but that approachis a serious restriction for me.

var v = new VectorF([...]);

if(object.ReferenceEquals(v, null))    // Would work, is not user friendly.
[...]

if(v == (VectorF)null)    // Would also work, is neither user friendly.
[...]

Question:
Is there a way to implement the == operator in a way, that it allows the simple null check, and allows for equality check between the different vectors?

Alternatively, is there another way how I could/should implement this?

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Chillersanim
  • 455
  • 3
  • 13
  • 5
    Seems likely that your Vector classes should really be immutable structs. Then the issue of `null` doesn't arise. (I'm assuming that your classes contain only 2 or 3 values.) – Matthew Watson Feb 21 '17 at 10:42
  • 2
    Sounds like [zugzwang](https://en.wikipedia.org/wiki/Zugzwang): either good looking `==` or `== null`. How about having methods to compare with vector of other type? E.g. `VectorF.IsSame(VectorI)` ? Compiler will show user mistake when he tries `vectorF == vectorI` and then user will search for methods to compare, tada, problem solved? Another thing: comparing `float` with `int` wouldn't be precise, how about casting `VectorF` to `VectorI` first and then comparing two `VectorI`? – Sinatr Feb 21 '17 at 10:45
  • why u need left-right overloads.. can u share us overload methods contents.. – levent Feb 21 '17 at 11:00
  • 1
    I think you can simplify a lot your problems adding an implicit conversion from VectorI to VectorF – Steve Feb 21 '17 at 11:03
  • @levent: I'm just used to calling the variables left and right, I've adopted this from other overloads, where the order could be important (e.g. with matrix multiplication). – Chillersanim Feb 21 '17 at 11:28
  • @Sinatr: Your approach of using another method sounds good, although only if there's a relatively huge overhead to converting from VectorI to VectorF. As InBetween described in his answer, the value conversion happens anyways. I think your approach would be good if the Vector would be backed by an array for example. – Chillersanim Feb 21 '17 at 11:36

3 Answers3

20

I'd push back on the whole design to begin with. I would never implement == with value semantics between different types, I'd find it rather confusing: instaceTypedA == instanceTypedB yells reference equality (at least to me).

If you need this to work, then implement an implicit conversion between VectorI and VectorF. This is how the framework works. When you do the following:

int i = 1;
double d = 1;
var b = i == d;

An oveload ==(int, double) isn't magically produced. What happens is that i is implicitly converted to double and ==(double, double) is invoked.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • Thank you. You are right, == should be reference equality. I didn't think of implicit conversion, as I wanted the call to be as optimized as possible. However, as the values would be converted anyways, it doesn't make much difference after all. – Chillersanim Feb 21 '17 at 11:24
7

You can turn around the comparison by using is:

if (v is VectorF)

This check will fail if v is null.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 6
    I don't quite like this solution. The semantics of what you are really doing are hidden in a "clever" trick; reading this code, its far from clear that you are checking for `null`, to be honest. – InBetween Feb 21 '17 at 10:57
  • 1
    Well, it is quite common to type check using `is` (or `as`), although not everyone knows it does the null check too. Once you know that it is quite obvious that `null` is not of a specific type. – Patrick Hofman Feb 21 '17 at 10:58
  • What is the improvement over object.ReferenceEquals except for the shorter code? I would assume that the is has quite a performance overhead for just a null check. – Chillersanim Feb 21 '17 at 11:34
  • I guess there is not much difference, but you could test that. – Patrick Hofman Feb 21 '17 at 11:36
3

What I would do in this case it not to overload the == operator, and instead do something like:

public static bool operator == (VectorF left, object right) {
    if (object.ReferenceEquals(null, right)) {
        // handle null case
    }
    VectorF rightF = right as VectorF;
    if (!object.ReferenceEquals(null, rightF)) {
        // Compare VectorF
    }
    VectorI rightI = right as VectorI;
    if (!object.ReferenceEquals(null, rightI)) {
        // Compare VectorI
    }
    // and so on...
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193