Mar 06 2009

Eliminating ToList() calls by TDD and Extensions Methods

Category: Uncategorizedbengtbe @ 00:34

One important principle of reusable object oriented design is “Program to an interface, not an implementation”. If you want to read more about the use of interfaces check out the great blog post Back to Basics: Interfaces by Karl Seguin. To summarize, instead of doing this:

interface IUserService

{

   List<User> GetUsers();

}

You should do something like this:

interface IUserService

{

   IList<User> GetUsers();

}

The I in front of List makes the big difference :)  However, if you often use the delegate methods of the List class then calls to ToList() will start to pop-up all around the code, e.g.:

users.ToList().ForEach(user => oldestAge = Math.Max(oldestAge, user.Age));

I would rather want this syntax:

users.ForEach(user => oldestAge = Math.Max(oldestAge, user.Age));

We can add this syntax using Extension methods that was introduces in C# 3.0.

Extension Methods

Extension methods enable you to “add” methods to existing classes and interfaces. They are static methods, but they are called as instance methods on the extended class or interface. The ForEach extension method could be added to the IList interface, however this would limit it to collections that implement this interface. A better match for this method is the generic IEnumerable interface. Then it can be used with virtually any of .NET’s collections.

We are going to use Test Driven Development to write the extension method, following the mantra of Red, Green, Refactor.

Red - Create a failing test

Red means writing and running a test that fails. Don’t skip this step! It is important to rule out bugs in the test. If your test passes before you write the code to satisfy the test, then it’s doesn’t mean much if it also passes after you write the code. Let’s write the test:

[TestFixture]

public class EnumerableExtensionsTest

{

    [Test]

    public void Generic_IEnumerable_should_have_ForEach_extension()

    {

        IEnumerable<int> listOfInts = new List<int> {2, 4, 6};

        int result = 0;

        listOfInts.ForEach(x => result += x);

        Assert.That(result, Is.EqualTo(12));

    }

}

This test uses NUnit, however the syntax is very similar for both MsTest and MbUnit. It testes the ForEach method by using it to calculate the sum of a collection of integers. This method will of course not compile, since IEnumerable<int> does not have a ForEach method. Let’s make it compile:

public static class EnumerableExtensions

{

    public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)

    {

        return;

    }

}

This is an extension method. It must satisfy three conditions:

  1. The class must be static
  2. The method must be static
  3. The first argument of the method starts with this, indicating that the extension method belongs to this parameter.

Hence the first argument enumerable is the collection that the extension method is called upon. The second argument action is a delegate that encapsulates a method that take a single parameter and does not return a value. In the test method this delegate encapsulated x => result += x.

The code will now compile. Let’s run the test:

 NUnit.Framework.AssertionException:   Expected: 12
But was:  0

As you can see the test failed. This finishes the Red part of TDD.

Green – Make the test pass

Now that we are sure that we have a failing test, we should make it pass:

public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)

{

    foreach (var item in enumerable)

    {

        action.Invoke(item);

    }

}

This is all the code that is needed. It iterates over the collection, invoking the action with the current item as the argument.

The test will now pass. This finishes the Green part of TDD. 

Refactor – Change the code

The last step of TDD is Refactor. The extension method is fairly simple, so there isn’t much to change there. So the refactoring in this case involves moving the class to a suitable place in the solution, updating the namespace and so forth. After these changes you should run the test again to make sure the code still works.

Another example - Convert All

I’m going to finish of this post by quickly showing you another example: How to create a ConvertAll extension method on the IEnumerable interface. First let’s write the test:

[Test]

public void Generic_IEnumerable_should_have_ConvertAll_extension()

{

    IEnumerable<int> listOfInts = new List<int>() { 2, 4, 6 };

    IList<string> listOfStrings = listOfInts.ConvertAll(x => “T” + x).ToList();

    Assert.That(listOfStrings.Count, Is.EqualTo(3));

    Assert.That(listOfStrings[0], Is.EqualTo(“T2″));

    Assert.That(listOfStrings[1], Is.EqualTo(“T4″));

    Assert.That(listOfStrings[2], Is.EqualTo(“T6″));

}

 

The code to satisfy this test is as follows:

public static IEnumerable<TOut> ConvertAll<T,TOut>(this IEnumerable<T> enumerable, Func<T,TOut> func)

{

    foreach (var item in enumerable)

    {

        yield return func.Invoke(item);

    }

}

I hope you found this post interesting!

kick it on DotNetKicks.com Shout it

Tags: