Elegant comparisons with the null coalescing operator

A while ago I commented on how I'd like a return? statement, which only returned if the return value was non-null. The purpose of this was to remove the irritation of implementing Equals and IComparable.CompareTo on classes with several properties. For an example of the kind of thing I mean, consider an Address class with properties Country, State, City, ZipCode and HouseNumber. (Apologies to readers who aren't American - while I feel a traitor to my country for using state instead of county and zip code instead of post code, I'm guessing there are more readers from the US than from elsewhere.)

This Address class needs (for whatever reason) to be comparable to itself, comparing the properties in the order described above, in normal string comparison order. Let's see how annoying that is without doing anything clever. (I haven't included any property implementations or constructors, but I'm sure you can all guess what they'd look like. Similarly, I haven't overridden object.Equals or object.Hashcode, but the implementations are trivial.)

using System;

public sealed class Address : IComparable<Address>
{
    string country;
    string state;
    string city;
    string zipCode;
    int houseNumber;
    
    public int CompareTo(Address other)
    {
        if (other==null)
        {
            return 1;
        }
        int ret = country.CompareTo(other.country);
        if (ret != 0)
        {
            return ret;
        }
        ret = state.CompareTo(other.state);
        if (ret != 0)
        {
            return ret;
        }
        ret = city.CompareTo(other.city);
        if (ret != 0)
        {
            return ret;
        }
        ret = zipCode.CompareTo(other.zipCode);
        if (ret != 0)
        {
            return ret;
        }
        return houseNumber.CompareTo(other.houseNumber);
    }
}

That's ignoring the possibility of any of the properties being null. If we want to include that possibility, it's worth having a static helper method which copes with nulls, along the lines of object.Equals(object, object).

Now, if we don't care about doing more comparisons than we really want to and potentially creating an array each time, it wouldn't be hard to implement a series of overloaded methods along the lines of:

public static int ReturnFirstNonZeroElement(int first,
                                            int second,
                                            int third)
{
    return first != 0 ? first :
           second != 0 ? second :
           third;
}

(The array part would be when you implement ReturnFirstNonZeroElement(params int[] elements) after you'd got enough overloads to get bored.)

That still ends up being a lot of code though, and it's doing unnecessary comparisons. I'm not keen on micro-optimisation, of course, but it's the inelegance of it that bothers me. It feels like there must be a way of doing it nicely. With C# 2.0 and the null coalescing operator, we do. (At this point I'm reminded that the irritation actually came when writing Java, which of course doesn't have anything similar. Grr.) For those who are unaware of the null coalescing operator (and it's one of the least well publicised new features in C# 2.0) see my brief coverage of it. Now consider the following helper method:

public static int? CompareFirstPass<T>(IComparable<T> first, T second) 
    where T : IComparable<T>
{
    if (first==null)
    {
        return -1;
    }
    // Assume CompareTo deals with second being null correctly
    int comparison = first.CompareTo(second);
    return comparison==0 ? (int?)null : comparison;
}

In short, this returns the result of the comparison if it's non-zero, or null otherwise. Now, with the null coalescing operator, this allows the Address class implementation of CompareTo to be rewritten as:

public int CompareTo(Address other)
{        
    return other==null ? 1 :
           Helper.CompareFirstPass(country, other.country) ??
           Helper.CompareFirstPass(state, other.state) ??
           Helper.CompareFirstPass(city, other.city) ??
           Helper.CompareFirstPass(zipCode, other.zipCode) ??
           houseNumber.CompareTo(other.houseNumber);
}

It's short, simple and efficient. Now, doesn't that make you feel better? :)

Published Fri, Jul 28 2006 22:43 by skeet

Comments

# re: Elegant comparisons with the null coalescing operator

Very nice! But I can already hear the angry howling of VB types who don't even want to use the conditional operator...

Saturday, July 29, 2006 1:54 AM by Chris Nahr

# re: Elegant comparisons with the null coalescing operator

Imports System Module Module1 Public Function IsDifferent(Of T As IComparable)(ByVal first As T, ByVal second As T, ByRef how As Integer) As Boolean If (first Is Nothing) Then how = -1 Return False End If how = first.CompareTo(second) If how <> 0 Then Return True End If End Function Public NotInheritable Class Address Implements IComparable(Of Address) Private country As String Private state As String Private city As String Private zipCode As String Private houseNumber As Integer Public Function CompareTo(ByVal other As Address) As Integer Implements IComparable(Of Address).CompareTo If other Is Nothing Then Return 1 End If Dim how As Integer = 0 If IsDifferent(country, other.country, how) Then ElseIf IsDifferent(country, other.country, how) Then ElseIf IsDifferent(state, other.state, how) Then ElseIf IsDifferent(city, other.city, how) Then ElseIf IsDifferent(zipCode, other.zipCode, how) Then ElseIf IsDifferent(houseNumber, other.houseNumber, how) Then End If Return how End Function End Class End Module

Wednesday, September 27, 2006 5:23 PM by vbtype

# re: Elegant comparisons with the null coalescing operator

Yes, that's a reasonably neat solution. I prefer the C# 2.0 version, but I'll remember the solution using ByRef (which could be "out" in C#) for when I'm working in C# 1.1...

Jon

Thursday, September 28, 2006 1:11 AM by skeet