Thursday, November 22, 2007

Over the past month I have been preparing for my presentation at DevTeach in Vancouver. I am filling in for JP on the topic "Generics they're not just about collections". I am really nervous and one thing that I have been trying to work on is my ability to better communicate the concepts that I have learned and have trapped in my head.

I feel that my past 2 presentations on Windsor Container could have been a lot better if I were able to explain the underlying concepts behind a product like Windsor instead of just taking for granted the audience's knowledge.

After practicing my presentation multiple times in front of my dogs, I am not satisfied with how I am communicating these topics and I feel that writing out my presentation in front of my blog readership and asking for feedback is the next best thing.

If you have any suggestions or if there is anything that is unclear to you based on my explanation of things, please feel free to comment on this post or to send me a email at stevenrockarts@gmail.com.

Using Generics without Collections

Please not that this part of the presentation is based off of JP's original Generics article. How many times have you written a class like the following to check if something is within a range?

public interface IRange
{
    bool Contains(int valueToCheck);
}
 
public class Range : IRange
{
    private readonly int start;
    private readonly int end;
 
    public Range(int start, int end)
    {
        this.start = start;
        this.end = end;
    }
 
    public bool Contains(int valueToCheck)
    {
        return valueToCheck >= start && valueToCheck <= end;
    }
}

If we look at the test for this code we can see that it works great for checking to see if an integer is within a certain range:

[Test]
public void ShouldFindRangeOfIntegers()
{
    IRange range = new Range(10, 20);
 
    Assert.IsTrue(range.Contains(12));
}

The range class works great checking integer ranges but what if we want to check DateTime ranges or our own custom Ranges? Creating a Range class that utilizes generics can help us save typing out a new Range class for every different type that we want to check. Before we jump in and create a Range class that uses generics lets create a couple tests that express our intent:

[Test]
public void ShouldFindRangeOfIntegers()
{
    IRange<int> range = new Range<int>(10, 20);
 
    Assert.IsTrue(range.Contains(12));
}
 
[Test]
public void ShouldFindRangeOfDateTime()
{
    IRange<DateTime> range = new Range<DateTime>(DateTime.Today, DateTime.Today.AddDays(2));
 
    Assert.IsTrue(range.Contains(DateTime.Today.AddDays(1)));
}

The first test shows that we want to check if the integer 12 is in between 10 and 20. The second test shows that we want to check if tomorrow is in between today and two days from now. Now that we have 2 tests that show what we want to accomplish, lets write a generic range class that can handle both of these types. Our first attempt might look something like this:

public interface IRange<T>
{
    bool Contains(T rangeToFind);
}
 
public class Range<T> : IRange<T>
{
    private readonly T start;
    private readonly T end;
 
    public Range(T start, T end)
    {
        this.start = start;
        this.end = end;
    }
 
    public bool Contains(T rangeToFind)
    {
        return rangeToFind >= start && rangeToFind <= end;
    }
}

 As it stands, this code will not compile because the compiler does not know if it can apply the >= and <= signs to any type that may be supplied to our generic range class. For example this may make sense with integers but what does the compiler do with this code:

IRange<Customer> customerRange = new Range<Customer>(customer, anotherCustomer);
customerRange.Contains(someOtherCustomer);

When we try and use the above code we get the following feedback from the compiler:

Error 3 The type 'Generics.Customer' must be convertible to 'System.IComparable<Generics.Customer>' in order to use it as parameter 'T' in the generic type or method 'Generics.Range<T>' C:\presentations\generics\src\test\Generics.Test\RangeTest.cs 24 50 Generics.Test

The IComparable interface contains one method on it called CompareTo() which compares the current instance of an object with another object of the same type and returns an integer based on the result. If the integer is less than 0 the instance is less than the object it is being compared to, if the integer returned is zero the instance is equal to the object it is being compared to and if the integer returned is greater that 0 the instance is greater than the object it is being compared to. Luckily for us most of the value types in the .NET Framework implement IComparable so we can utilize it to compare many different types using our generic Range class.

How can we ensure that any consumer of our generic range class implements the IComparable interface? Generic constraints to the rescue! We simply add the following to our class which specifies that any class that uses our generic Range class must implement the IComparable interface.

public class Range<T> : IRange<T> where T : IComparable

We also need to change the contains method to compare the value to check to the start and end of the range like so:

public bool Contains(T rangeToFind)
{
    return rangeToFind.CompareTo(start) >= 0 && rangeToFind.CompareTo(end) <= 0;
}

Now we can run our tests and discover that both tests pass and our range class now works with integers and date time values.

Thursday, November 22, 2007 8:14:58 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Thursday, February 21, 2008 9:49:58 AM (GMT Standard Time, UTC+00:00)
I don't get it, why use generics here if you are constraining only to IComparable. I would use such code

public interface IRange
{
bool Contains(IComparable rangeToFind);
}

public class Range : IRange
{
private readonly IComparable start;
private readonly IComparable end;

public Range(IComparable start, IComparable end)
{
this.start = start;
this.end = end;
}

public bool Contains(IComparable rangeToFind)
{
return rangeToFind.CompareTo(start) >= 0 && rangeToFind.CompareTo(end) <= 0;
}

}

Is there anything I don't see ??
SeeR
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview

Theme design by Jelle Druyts

Pick a theme: