Browse DevX
Sign up for e-mail newsletters from DevX


Build a Generic Range Class with .NET 2.0 : Page 2

Find out how to take advantage of the generics capability introduced with .NET 2.0, which provides an elegant solution to performing range checks within your applications.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

There Must Be A Better Way
One great thing about code is that if you see something you don't like, you can refactor it. Range-checking in applications is a prime candidate for refactoring. Martin Fowler originally introduced the concept of a Range pattern in this section of his book "Analysis Patterns." He described a Range object as "a single object to represent the range as a whole, [which] then provides the relevant operations to test to see if values fall in the range and to compare ranges."

The DiscountWithoutGenerics class fits that description of a Range. It encapsulates the logic required to determine if a value (the order amount) falls within the range of values for which the discount is applicable. Clients of the DiscountWithoutGenerics class don't need to perform any explicit boundary checks; those are all encapsulated within the "Range" class itself.

While using this pattern is a great way to encapsulate Range checks within your applications, it does have one big flaw: Right now range-checking is localized to a very specific class of the application. Worse, the DiscountWithoutGenerics class can work only with decimal values.

Ultimately the basic operations on any Range are the same:

  • What is the start value?
  • What is the end value?
  • Does it contain "this" value?
To make the effort of building a Range class truly worthwhile, you need a way to create a Range class that can create ranges of any type. In other words, you should be able to create one Range class that can work with with ints, doubles, decimals, dates etc, and even our own custom types. The rest of this article discusses how to use Test-driven Development (TDD) to build a Range class that you can use with any type.

Building The Range Class
The key to creating a class that will be able to work with any type that you wish lies in the new generic language enhancements added to C# (and VB.NET) in .NET 2.0. Most people who have seen examples of the new collection classes in .NET have already had some exposure to the new generic language enhancements. Remember back to the ancient days of .NET 1.1, where if you needed a custom collection of "Order" objects you would have to create your own custom collection class—and you would usually end up using an ArrayList as a private backing store. The purpose of creating the custom collection class was to ensure type safety, not to provide people with the ability to add anything they wanted to your private collection. With .NET 2.0 and generics you can achieve the same result with much less code:

IList<Order> orders = new List<Order>(); orders.Add(new Order());

Take a look at the C# definition for the generic IList interface:

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

I should take a quick second to point out that there is no special significance to the use of the letter "T." The compiler doesn't enforce any particular naming convention for generic type parameters. The designers of the framework could just have easily defined the interface as follows:

IList<SomeItem> : ICollection<SomeItem>, IEnumerable<SomeItem>, IEnumerable

What's important to remember is that the T or SomeItem is a placeholder for a type. Notice all the T parameters in the interface definition. This is the magic that allows the generic IList<T> interface to work with any type. When you want to construct an implementation of a generic IList<T> (the most common implementation being List<T>), you have to provide it with a type to use in place of the generic type parameter T. The previous code segment constructed a List of <Order> that constrained all the methods so they accept only Order types. The creators of the generic List<T> class didn't actually care what type T was, leaving it up to the class consumer to decide what type the List should actually work with.

This is the same concept that you will have to apply to the formation of a generic Range class. The Range should be flexible enough to work with any type. Start by writing a test to see if you can create a Range of integers:

[Test] public void ShouldCreateARangeOfIntegers() { IRange<int> rangeOfIntegers = new Range<int>(20, 30); Assert.AreEqual(20,range.Start); Assert.AreEqual(30,range.End); }

When you write code from a TDD perspective, try to write from the point of view of a client programmer utilizing your library. Ask yourself "What is the ideal way for a client to consume and work with this object?" You then express your expectations in code as a test; get the test compiling, get the test passing, and then cleanup any mess you may have created in the process. If you are following along and coding with me, then your code will currently be in a non-compilable state. First, get the test to compile by adding the required classes and interfaces shown below:

public interface IRange<T> { T Start{get;} T End{get;} } public class Range<T> : IRange<T> { public Range(T start, T end) { throw new NotImplementedException(); } public T Start { get { throw new NotImplementedException(); } } public T End { get { throw new NotImplementedException(); } } }

Note that you've implicitly made some pretty big design decisions by writing this one test.

  • The Range class will implement an IRange interface.
  • The Range class will have one constructor that takes the start and end values of the range.
  • The Range class will expose its start and end values as properties.

Thanks for your registration, follow us on our social networks to keep up-to-date