BlackWaspTM
LINQ
.NET 3.5+

Implementing the Zip Operator in .NET 3.5

Microsoft introduced the Zip operator to Language-Integrated Query (LINQ) with the .NET framework version 4.0. This operator is not available in version 3.5 of the framework but can be implemented quite easily.

Zip Operator

The Zip method was introduced in the .NET framework version 4.0. This LINQ standard query operator processes two sequences, pairing items and applying a function to generate the values for the output sequence. If the sequences are of different lengths, the extra items from the longer list are ignored.

The Zip extension method was not available in the original version of LINQ, in .NET 3.5. This article describes how you can implement Zip and use it with the earlier framework.

Validating Parameters

Like most standard query operators that return a sequence, the method uses deferred execution when building the new collection but validates the parameters immediately. This means we will create two methods. The first is a public method that checks that neither input sequence is null. If the values are valid, the deferred method is called.

The method, which should appear in a static class, is as follows:

public static IEnumerable<T> Zip<A, B, T>(
    this IEnumerable<A> seqA, IEnumerable<B> seqB, Func<A, B, T> func)
{
    if (seqA == null) throw new ArgumentNullException("seqA");
    if (seqB == null) throw new ArgumentNullException("seqB");

    return Zip35Deferred(seqA, seqB, func);
}

Zipping the Sequences

The deferred part of the new operator executes several steps. First, an enumerator for each input sequence is obtained. These are shown below within using statements, ensuring that the sequences will be disposed if necessary when they are no longer needed. Next, we use a while loop to control the yielding of values. In the loop's predicate, we move to the next item in each sequence. If both sequences have remaining values we apply the function and yield the result. If one or both of the sequences is exhausted, the loop and the method exit.

private static IEnumerable<T> Zip35Deferred<A, B, T>(
    this IEnumerable<A> seqA, IEnumerable<B> seqB, Func<A, B, T> func)
{
    using (var iteratorA = seqA.GetEnumerator())
    using (var iteratorB = seqB.GetEnumerator())
    {
        while (iteratorA.MoveNext() && iteratorB.MoveNext())
        {
            yield return func(iteratorA.Current, iteratorB.Current);
        }
    }
}

Testing the Zip Operator

We can test the new Zip operator by passing sequences of various types and lengths. The following code shows two uses:

int[] integers1 = new int[] { 1, 2, 3, 4, 5 };
int[] integers2 = new int[] { 10, 20, 30, 40, 50 };
var sumsZip = integers1.Zip(integers2, (i, j) => i + j);
// 11, 22, 33, 44, 55

char[] characters = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
var items = characters.Zip(integers1, (c, i) => string.Format("{0}{1}", c, i));
// A1, B2, C3, D4, E5
10 May 2011