There is no doubt about it; out of all the programming languages I ever experimented with, C# has offered me the most streamlined positive development experience so far. It is a modern, ever-evolving language, which now that C# and the whole .NET framework is turning to open source, is guaranteed an even greater future. However, some core constructs commonly available in other languages, like intervals which I introduce an implementation of in this post, are missing. E.g., Ruby has had Ranges for quite some time.
A Range represents an interval—a set of values with a beginning and an end.
Straightforward, but due to the lack of support for generic calculations in C#, a hassle to implement. However, as introduced by Marc Gravell, with some runtime compilation trickery involving expression trees, far from out of reach. I’ve had an Interval class within my core library for quite some time, but just now refactored it to also support more complex intervals, e.g. an interval between two DateTime instances, which thus represents a TimeSpan.
Without further ado, an example of what using this looks like in practice.
// Mockup of a GUI element and mouse position. var timeBar = new { X = 100, Width = 200 }; int mouseX = 180; // Find out which date on the time bar the mouse is positioned on, // assuming it represents whole of 2014. var timeRepresentation = new Interval<int>( timeBar.X, timeBar.X + timeBar.Width ); DateTime start = new DateTime( 2014, 1, 1 ); DateTime end = new DateTime( 2014, 12, 31 ); var thisYear = new Interval<DateTime, TimeSpan>( start, end ); DateTime hoverOver = timeRepresentation.Map( mouseX, thisYear ); // If the user clicks, zoom in to this position. double zoomLevel = 0.5; double zoomInAt = thisYear.GetPercentageFor( hoverOver ); Interval<DateTime, TimeSpan> zoomed = thisYear.Scale( zoomLevel, zoomInAt ); // Iterate over the interval, e.g. draw labels. zoomed.EveryStepOf( TimeSpan.FromDays( 1 ), d => DrawLabel( d ) );
As you might notice, the timeRepresentation interval has just one generic parameter (Interval<int>), whereas thisYear has two (Interval<DateTime, TimeSpan>). The less generic (one type parameter) class is a simple wrapper around the more generic base type which has two type parameters; the first denotes the type used to represent any position within the range, whereas the second type is used to represent differences between these positions. When these types are the same, the simplified wrapper can be used. Likewise, a TimeInterval wrapper can easily be created if you find Interval to be too verbose.
Worth noting here to understand how it works under the covers is the constructor which sets two public static fields used during operations when conversions to double are needed. Arguably, this could be improved by having a factory creating the intervals and using constructor injection instead.
public TimeInterval( DateTime start, bool isStartIncluded, DateTime end, bool isEndIncluded ) : base( start, isStartIncluded, end, isEndIncluded ) { ConvertDoubleToSize = d => new TimeSpan( (long)Math.Round( d ) ); ConvertSizeToDouble = s => s.Ticks; }
Once you start incorporating the notion of an interval in your programming arsenal you will be amazed by the opportunities which present themselves where to use them! Some actual examples within my core library:
- A generic binary search algorithm.
- Generic interpolation classes, which I wrote about before.
- Common mapping of values from one range to another range, e.g. in GUI libraries, mapping pixels to percentages.
- Coercing values to a certain range.
To get an impression of the full range of currently supported operations, check out the unit tests.
One thought on “Interval: Generic Ranges in C#”