BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

LINQ
.NET 3.5+

A LINQ Style Operator to Set the Length of a Sequence

Sometimes it is necessary to modify the length of a sequence of values to achieve a fixed length. This may require that values at the end of the sequence are omitted or that new elements are added to achieve the desired length.

OfLength<T>

In some situations you may find that you need to work with sequences that are of a specific length. If you have a collection that does not meet the requirement, you might decide to remove some elements from the end of the sequence, or add new items to achieve the correct length. This operation can be useful but there is no standard query operator within Language-Integrated Query (LINQ) for this purpose.

In this article we will create a new extension method that sets the length of a sequence by adding or removing items as required. To match the functionality of the standard operators we'll support immediate validation of parameters but deferred execution. We'll also ensure that the operator can work with any sequence, even those that may only be enumerated once. At the end of the article we'll show the use of the new operator.

Creating the Class

As we are creating extension methods we need a static class within which to define them. Create a new class named, "OfLengthExtensions" and modify its declaration as follows. For most use cases you'll probably want this type to be within a class library project. However, in the downloadable source code it's included in a console application so that you can easily see it in action.

public static class OfLengthExtensions
{
}

Creating the Extension Methods

We'll actually create two public extension methods for the OfLength operator. The difference between the two is how they handle sequences that are shorter than desired. The simpler version will add extra elements using the default value for the type declared in the method's generic type parameter. The second will permit you to specify the value to use when additional items are required.

Both overloaded methods check that the sequence is not null and that the target length is valid before calling the iterator method, which provides the deferred execution. If either parameter is invalid we throw an exception.

Add the two extension methods to the OfLengthExtensions class.

public static IEnumerable<T> OfLength<T>(this IEnumerable<T> sequence, int size)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (size < 0) throw new ArgumentException("Size must be zero or greater");

    return OfLengthImpl(sequence, size, default(T));
}

public static IEnumerable<T> OfLength<T>(
    this IEnumerable<T> sequence, int size, T defaultValue)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (size < 0) throw new ArgumentException("Size must be zero or greater");

    return OfLengthImpl(sequence, size, defaultValue);
}

Adding the Iterator

We can now add the iterator. This is quite a simple method that employs two while loops. The first iterates until either the sequence is exhausted or the desired number of elements have been yielded.

The second loop will not execute at all if the input sequence is the same length as, or longer than, the desired size. However, if the source collection is too small, this later loop will add the correct number of new elements to achieve the correct length.

You can see that the source sequence is only enumerated once. This ensures that we can support sequences that cannot be read several times.

private static IEnumerable<T> OfLengthImpl<T>(
    IEnumerable<T> sequence, int size, T defaultValue)
{
    int remaining = size;
    var enumerator = sequence.GetEnumerator();

    while (enumerator.MoveNext() && remaining != 0)
    {
        yield return enumerator.Current;
        remaining--;
    }

    while (remaining != 0)
    {
        yield return defaultValue;
        remaining--;
    }
}

Testing the Methods

To try the new operator, add the following code to the Main method and step through the code in the debugger, examining the generated arrays in the Locals or Autos windows. The code creates a source array containing six elements. It then uses the OfLength methods to generate new arrays of varying lengths, padding where necessary with either the default for an integer, which is zero, or a specified value.

var sequence = new int[] { 1, 2, 3, 4, 5, 6 };
var sameLength = sequence.OfLength(6).ToArray();    // { 1, 2, 3, 4, 5, 6 }
var shorter = sequence.OfLength(5).ToArray();       // { 1, 2, 3, 4, 5 }
var longer1 = sequence.OfLength(10).ToArray();      // { 1, 2, 3, 4, 5, 6, 0, 0, 0, 0 }
var longer2 = sequence.OfLength(10, -1).ToArray();  // { 1, 2, 3, 4, 5, 6, -1, -1, -1, -1 }
28 May 2013