Sunday, February 03, 2008

Never write a for loop again! Fun with Linq style extension methods.

One of the things I like about Ruby is the range operator. In most C style languages in order to create a list of  numbers you would usually use a for loop like this:

List<int> numbers = new List<int>();
for (int i = 0; i < 10; i++)
{
    numbers.Add(i);
}
int[] myArray = numbers.ToArray();

But in Ruby you just write this:

myArray = o..9.to_a

But now with extension methods and custom iterators we can do the same thing in C#. Here's a little extension method 'To':

public static IEnumerable<int> To(this int initialValue, int maxValue)
{
    for (int i = initialValue; i <= maxValue; i++)
    {
        yield return i;
    }
}

You can use it like this:

1.To(10).WriteAll();

1 2 3 4 5 6 7 8 9 10

Note the WriteAll() method, that's an extension method too, it simply writes each item in the list to the console:

public static void WriteAll<T>(this IEnumerable<T> values)
{
    foreach (T value in values)
    {
        Console.Write("{0} ", value);
    }
    Console.WriteLine();
}

You can mix your custom extension methods with the built in Linq methods, let's count up to fifty in steps of ten:

1.To(5).Select(i => i * 10).WriteAll();

10 20 30 40 50

Or maybe just output some even numbers:

1.To(20).Where(i => i % 2 == 0).WriteAll();

2 4 6 8 10 12 14 16 18 20

Here's another extension method 'Each', it just applies a Lambda expression to each value:

public delegate void Func<T>(T value);

public static void Each<T>(this IEnumerable<T> values, Func<T> function)
{
    foreach (T value in values)
    {
        function(value);
    }
}

Let's use it to output a ten by ten square of zero to ninety nine:

0.To(9).Each(i => 0.To(9).Select(j => (i * 10) + j).WriteAll());

0 1 2 3 4 5 6 7 8 9 
10 11 12 13 14 15 16 17 18 19 
20 21 22 23 24 25 26 27 28 29 
30 31 32 33 34 35 36 37 38 39 
40 41 42 43 44 45 46 47 48 49 
50 51 52 53 54 55 56 57 58 59 
60 61 62 63 64 65 66 67 68 69 
70 71 72 73 74 75 76 77 78 79 
80 81 82 83 84 85 86 87 88 89 
90 91 92 93 94 95 96 97 98 99 

Now here's something really cool, and more than a little dangerous. Because chaining extension methods of IEnumerable<T> means that you're effectively building a decorator chain of enumerators, you don't actually execute every iteration unless you ask for it. This means we can write infinite loop generators and then bound them by only asking for some members. Best to demonstrate. Here's a method that returns an infinite number of integers (not really, it'll end with an exception at int.MaxValue):

public static IEnumerable<int> Integers
{
    get
    {
        int i = 0;
        while (true)
        {
            yield return i++;
        }
    }
}

We can use it with the built in Linq method 'Take'. For example here we are printing out zero to nine:

Numbers.Integers.Take(10).WriteAll();

0 1 2 3 4 5 6 7 8 9

And here is Five to Fifteen:

Numbers.Integers.Skip(5).Take(11).WriteAll();

5 6 7 8 9 10 11 12 13 14 15

Why is it dangerous? If you put any operation in the chain that simply iterates all the values you'll get an infinite loop. So you couldn't say:

Numbers.Integers.Reverse.Take(10).WriteAll();

Let's wrap up with an infinite Fibonacci series:

public static IEnumerable<int> Fibonacci
{
    get
    {
        int a = 0;
        int b = 1;
        int t = 0;

        yield return a;
        yield return b;

        while (true)
        {
            yield return t = a + b;
            a = b;
            b = t;
        }
    }
}

And use it to print out the first ten Fibonacci numbers:

Numbers.Fibonacci.Take(10).WriteAll();

0 1 1 2 3 5 8 13 21 34

I'm really enjoying learning the possibilities of Linq style extension methods. It makes C# feel much more like a functional language and let's you write in a nicer declarative style. If I've tickled your interest, check out Wes Dyer's blog. Here he writes an ASCII art program using Linq. Nice!

4 comments:

Anonymous said...

The delegate Func<T> will conflict with the built-in delegate:
Result Func<TResult>()

Instead, you should use the built-in Action<T> delegate, which has existed since .NET 2.0.

Mike Hadlow said...

Thanks anonymous! That's a very good point. It doesn't actually conflict because it's a separate delegate type, but you're right I should have used Action<T>.

Atlantez said...

Enumerable.Range(1,10) could also be used for the range.

But I guess your extension also has it's beauty ;-)

Anonymous said...

Useful extensions. great.

I have written 2 For extensions to List which are lambda wrappers to a for loop on a list with Start, End and Step.

http://www.c-sharpcorner.com/Blogs/5435/for-extension-to-listt.aspx