How do I create a class to store a range of any type provided that the type allows comparison operators to ensure that that the first value provided to the constructor is less than the second?
public class Range<T> where T : IComparable<T>
{
private readonly T lowerBound;
private readonly T upperBound;
/// <summary>
/// Initializes a new instance of the Range class
/// </summary>
/// <param name="lowerBound">The smaller number in the Range tuplet</param>
/// <param name="upperBound">The larger number in the Range tuplet</param>
public Range(T lowerBound, T upperBound)
{
if (lowerBound > upperBound)
{
throw new ArgumentException("lowerBlound must be less than upper bound", lowerBound.ToString());
}
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
I am getting the error:
Error 1 Operator '>' cannot be applied to operands of type 'T' and 'T' C:\Source\MLR_Rebates\DotNet\Load_MLR_REBATE_IBOR_INFO\Load_MLR_REBATE_IBOR_INFO\Range.cs 27 17 Load_MLR_REBATE_IBOR_INFO
To combine two suggestions already given, we combine the ability to create Ranges with a manually defined comparison rule, with an over-ride for those types that implement
IComparable<T>, and with compile-time safety on the latter.We take much the same approach as the static
Tupleclass’Createmethod. This can also offer concision in allowing us to rely upon type inference:Now we can’t accidentally construct a
Rangewith the default comparer where it won’t work, but can also leave out the comparer and have it compile only if it’ll work.Edit:
There are two main approaches to having items comparable in an order-giving way in .NET and this uses both.
One way is to have a type define its on way of being compared with another object of the same type*. This is done by
IComparable<T>(or the non-genericIComparable, but then you have to catch type mis-matches at run-time, so it isn’t as useful post .NET1.1).intfor example, implementsIComparable<int>, which means we can do3.CompareTo(5)and receive a negative number indicating that 3 comes before 5 when the two are put into order.Another way is to have an object that implements
IComparer<T>(and likewise a non-genericIComparerthat is less useful post .NET1.1). This is used to compare two objects, generally of a different type to the comparer. We explicitly use this either because a type we are interested in doesn’t implementIComparable<T>or because we want to override the default sorting order. For example we could create the following class:If we used this to sort a list of integers (
list.Sort(new EvenFirst())), it would put all the even numbers first, and all the odd numbers last, but have the even and odd numbers in normal order within their block.Okay, so now we’ve got two different ways of comparing instances of a given type, one which is provided by the type itself and which is generally the “most natural”, which is great, and one which gives us more flexibility, which is also great. But this means that we will have to write two versions of any piece of code that cares about such comparisons – one that uses
IComparable<T>.CompareTo()and one that usesIComparer<T>.Compare().It gets worse if we care about two types of objects. Then we need 4 different methods!
The solution is provided by
Comparer<T>.Default. This static property gives us an implementation ofIComparer<T>.Compare()for a givenTthat calls intoIComparable<T>.CompareTo.So, now we generally only ever write our methods to make use of
IComparer<T>.Compare(). Providing a version that usesCompareTofor the most common sort of comparisons is just a matter of an override that uses the default comparer. E.g. instead of:We have:
As you can see, we’ve the best of both worlds here. Someone who just wants the default behaviour calls
SortStrings(), someone who wants a more specific comparison rule to be used calls e.g.SortStrings(StringComparer.CurrentCultureIgnoreCase), and the implementation only had to do a tiny bit of work to offer that choice.This is what is done with the suggestion for
Rangehere. The constructor always takes anIComparer<T>and always uses it’sCompare, but there’s a factory method that calls it withComparer<T>.Defaultto offer the other behaviour.Note that we don’t strictly need this factory method, we can just use an overload on the constructor:
The downside though, is that we can’t add a
whereclause to this to restrict it to cases where it’ll work. This means that if we called it with types that didn’t implementIComparer<T>we’d get anArgumentExceptionat runtime rather than a compiler error. Which was Jon’s point when he said:The use of the factory method is purely to ensure this wouldn’t happen. Personally, I’d probably just go with the constructor override and try to be sure not to call it inappropriately, but I added the bit with the factory method since it does combine two things that had come up on this thread.
*Strictly, there’s nothing to stop e.g.
A : IComparable<B>, but while this is of little use in the first place, one also doesn’t know for most uses whether the code using it will end up callinga.CompareTo(b)orb.CompareTo(a)so it doesn’t work unless we do the same on both classes. In sort, if it can’t be pushed up to a common base-class it’s just going to get messy fast.