Relational lifted operators are confusing to me

| 3 comments

I am glad to know that I could access BlogSpot from my workplace (although not from my rental house), which means that I could continue writing something I feel interesting and worthy sharing.

Several months ago, we had a discussion internally about the operator lifting feature in C#, and how we could leverage upon this feature in our own library. Our library is mainly used for SI unit conversion, one requirement of this library is to support the basic arithmetic operations on those unit classes, for example, we have unit classes like Length, Time and Velocity, and we should support operation like follows:

Velocity = Length / Time, Length = Velocity * Time

Apparently, we could use the operator overloading feature in C# to enable the above scenarios, so that we could overload the division or multiplication operators as follows:

public static Length operator *(Velocity velocity, Time time)
{
    return velocity.Amount * time.Amount * Length.Meter;
}

This looks great and okay, but how about if the “velocity” or “time” parameter is null, then we end up having NullReferenceException thrown at us. The correct implementation should be something like the following:

public static Length operator *(Velocity velocity, Time time)
{
    if(velocity == null|| time == null)
    {
        return null;
    }

    return velocity.Amount * time.Amount * Length.Meter;
}

This just follows the rules when overloading operator which could be lifted as Eric Lippert illustrated in his blog post:

3) For the binary operators + - * / % & | ^ << >>, where the operator takes an S and a T and returns a U there is a corresponding lifted operator that takes an S? and a T? and returns a U?. It returns null if either argument is null.

I almost totally agree with the rules described by Eric Lippert except this one:

1) For every equality and relational operator that compares an S to a T and returns a bool there is a corresponding lifted operator that compares an S? to a T? and returns a bool. It returns false if either argument is null. (With the additional exception that if one argument to an equality operator is the null literal then the result may be determined by checking if the nullable term has a value.)

Consider this:

double? a = null;
double? b = 1;
Console.WriteLine(a > b);
Console.WriteLine(a == b);

Console.WriteLine(a < b);

This three comparison operation will all return false which doesn’t make any sense, since the three comparison should be mutually exclusive in logical sense.

Actually I recommend to return bool? when overloading those relational operators as follows:

public class Length
{
    public Length(double amount)
    {
        this.Amount = amount;
    }

    public double Amount
    {
        get;
        private set;
    }

    public static bool? operator >(Length a, Length b)
    {
        if(ReferenceEquals(a, null) || ReferenceEquals(b, null))
        {
            return null;
        }

        return a.Amount > b.Amount;
    }

    public static bool? operator <(Length a, Length b)
    {
        if(ReferenceEquals(a, null) || ReferenceEquals(b, null))
        {
            return null;
        }

        return a.Amount < b.Amount;
    }

    public static bool? operator ==(Length a, Length b)
    {
        if(ReferenceEquals(a, null) || ReferenceEquals(b, null))
        {
            return null;
        }

        return a.Amount == b.Amount;
    }


    public static bool? operator !=(Length a, Length b)
    {
        if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
        {
            return null;
        }

        return a.Amount != b.Amount;
    }
}

With this Length class at hand, then three comparisons shown previously will all return correct result (aka null):

Length a = null;
var b = new Length(1);
Console.WriteLine(a > b);
Console.WriteLine(a == b);

Console.WriteLine(a < b);

I’ve no idea why nullable type in C# doesn’t follow this, but they do have their own explanation which is even more confusing to me:

This seems like a very natural thing for a C# user to want to write. But if equality is three-valued, comparing anything to null always returns a null value (ie null isn't equal to anything), and therefore such a comparison can never succeed. Instead, the user would have to write:

if (!p.HasValue)

or something similar. We decided that the value of having a model that was consistent with the way users were used to dealing with reference null was pretty high, and therefore decided to make the relational operators return bool.

I personally think writing line like if(!p.HasValue) is okay for me, because otherwise I need to deal with other confusing scenario like the one I’ve shown above with three comparisons on double?.