A LINQ Style Range Generator

Language-Integrated Query (LINQ) provides the Enumerable.Range method that generates incrementing sequences of integers. This article describes a similar method that allows the creation of more complex ranges with the repeated application of a function.

Enumerable.Range

During the LINQ to Objects tutorial I described the Enumerable.Range method. This allows you to create ascending sequences of integer value by providing a starting value and the number if items you desire. Each value in the range is one greater than the previous. If you need a more complex range, you can combine the call to Range with the Select standard query operator, adding a lambda expression that performs a projection on each element.

The Range method is quite limited, as it only permits the generation of ascending ranges and can only create integer values. Even when adding projections, it is not easy to create a range where each value is based upon the previous or where the range contains complex types. In this article we'll create an alternative method that uses the style of LINQ and can be incorporated into your queries.

Creating the Class

The method could be added to a standard class or a static class and could be an instance or static method. For simplicity, we will create a static class in this article. The class is named, "RangeGenerator".

public static class RangeGenerator
{
}

Adding the Generate Method

The signature of the Generate method has an additional parameter to that of Enumerable.Range. The first parameter is still used to accept the initial value in the range and the final parameter determines the number of items to provide before the sequence is exhausted. The second parameter is new. This allows a Func delegate to be specified. After the first item in the sequence, each new element is generated by applying this delegate to the previous item.

The signature of the method is shown below. Note that this is a generic method. The initial value is of the generic type, "T". The function accepts and returns values of type T, these representing the previous and new elements of the sequence. The Generate method returns an IEnumerable<T>, allowing further LINQ operators to be applied and permitting the resultant sequence to be the source of a query created using LINQ's query expression syntax.

The first step within the method is to validate that the function is valid. This mirrors the process used by the standard query operators; if the Func delegate is null, we throw an ArgumentNullException.

if (func == null) throw new ArgumentNullException("func");

We can now add the code for our iterator and call it from the Generate method, as shown below. We have to separate the implementation from the validation of the parameter as the use of yield return means that the code in the GenerateImpl method will not be executed until the sequence is accessed. If the null check is performed within the same method as the yield keyword, the exception will not be thrown immediately upon calling Range and may be encountered unexpectedly at the time the sequence is evaluated.

GenerateImpl starts by storing the initial value in the lastValue variable. On the first invocation of the iterator, this value is simply returned. The remainder of the invocations are handled by the for loop. Each iteration of the loop calculates a new value by applying the Func delegate to lastValue. The new value is stored in readiness for the next pass of the loop, before being returned using yield return.

Testing the Method

You can now try out the new method. Let's begin with a simple example, which could actually be achieved reasonably easily with Enumerable.Range. Here we create a sequence of five integers, starting at one. The lambda expression specifies subsequent elements are calculated by multiplying the previous value by ten.

The next example is slightly more interesting. It estimates the amount of interest that could be paid on a bank account, where the interest rate is 5% and a fixed amount is added to the account annually. The initial value is the same as the annual deposit, in this case 1,000. The lambda expression first adds 5% to the previous balance, then adds the annual deposit. The final results are formatted according to your local currency settings so may vary slightly from the UK pounds version shown in the comments.