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? :)
Very nice! But I can already hear the angry howling of VB types who don’t even want to use the conditional operator…
LikeLike
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
LikeLike
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
LikeLike