As soon as I started learning Ruby, a few years ago, I got immediately hooked
on its Range class.
I could not believe I had been programming in .NET without them for so long.
I like to think of range objects as the specification for a for
loop, packaged in an object that can be passed around. That's not the whole story, though.
Ranges also represent sequences or intervals, which can be queried for intersection or
containment. See the following Ruby sample code.
#declare a Range object
summer_months = 6..9
#enumerate it
summer_months.each {|m| puts "#{Date.new(2000, m, 1).strftime('%B')} is a Summer month." }
#other handy features
summer_months.include? 7 # ==> true
summer_months.to_a # ==> [6, 7, 8, 9] (converted to array)
I needed that in C#
That was back when the CLR 2.0 was just about to be released and I ended up
writing my own Range class. I have used this class in a handful of
projects since then and I'm still surprised that we don't have it in the
standard .NET class library. The closest thing I'm aware of is the method
System.Linq.Enumerable.Range(int, int),
which is rather limited (it only enumerates integers one by one).
Here's the code that I came up with, which has served me well for many years. First
an abstract base class. I'll omit parts of the code for brevity but I'll give you
a link for the complete source at the end.
public abstract class RangeBase<T> : IEnumerable<T> where T : IComparable
{
public T Start { get; set; }
public T End { get; set; }
protected RangeBase(T start, T end)
{
// ... snip ...
}
protected abstract T GetNextItem(T currentItem);
public IEnumerator<T> GetEnumerator()
{
T item = Start;
while (true)
{
if (item.CompareTo(End) > 0)
break;
yield return item;
item = GetNextItem(item);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Intersects(RangeBase<T> otherRange)
{
// ... snip ...
}
public bool Contains(RangeBase<T> otherRange)
{
// ... snip ...
}
public bool Contains(T element)
{
return (Start.CompareTo(element) <= 0) && (End.CompareTo(element) >= 0);
}
}
There's also a generic concrete type and a bunch of the more common
implementations for convenience.
public class Range<T> : RangeBase<T> where T : IComparable
{
public Func<T, T> GetNext { get; private set; }
public Range(T start, T end, Func<T, T> getNext)
: base(start, end)
{
GetNext = getNext;
}
protected override T GetNextItem(T currentItem)
{
return GetNext(currentItem);
}
}
public class Int32Range : RangeBase<int>
{
public Int32Range(int start, int end) : base(start, end){}
protected override int GetNextItem(int currentItem)
{
return currentItem + 1;
}
}
public class Int64Range : RangeBase<long> { /* ... snip ... */ }
public class DayRange : RangeBase<DateTime> { /* ... snip ... */ }
public class HourRange : RangeBase<DateTime> { /* ... snip ... */ }
//...etc...
Coding with Ranges
With the above classes I can write code that is similar in functionality to the
Ruby version.
//declare a range of numbers
var summerMonths = new Int32Range(6, 9);
//enumerate it
foreach(var m in summerMonths)
Console.WriteLine(new DateTime(2000, m, 1).ToString("MMMM") + " is a Summer month.");
//other handy features
summerMonths.Contains(7); // ==> true
summerMonths.Contains(new Int32Range(7, 8)); // ==> true
summerMonths.ToArray(); // ==> [6, 7, 8, 9] (using LINQ extensions)
We can also use the generic Range type to create less orthodox
iterations, like a sequence of dates that repeat every other week:
var startDate = new DateTime(2010, 1, 1);
var endDate = startDate.AddMonths(3);
var appointmentDates = new Range<DateTime>(startDate, endDate, d => d.AddDays(14));
appointmentDates.ToArray();
// ==> [ 1/1/2010, 1/15/2010, 1/29/2010,
// 2/12/2010, 2/26/2010, 3/12/2010, 3/26/2010 ]
The complete source for these classes and even some unit tests can be found
here.
Other implementations
Of course I was not the first to implement a Range class in C#. A quick search
yielded at least a
couple
of articles with
different approaches to the same goal.
Another indication that ranges are a popular data structure is the fact that F# has them too.
With luck we will see ranges in a future version of the .NET framework. Oh,
and since we are wishing for things. let's hope they introduce range literals in C# at that same time.
Posted
01-02-2010 4:55 AM
by
sergiopereira