.NET has included monadic map functionality since the release of LINQ in .NET 3.5. You may know this by the extension method on IEnumerable<T>, Select. This allows you to project from one sequence to another. However, that’s as far as it went. Applying a map on multiple lists was not available in the core framework. That has changed in .NET 4 with the new Zip method.
Zip gives you the ability to map two sequences into one sequence; in effect, zipping it up. Here’s an example:
var ids = new[] { 1, 2, 3 };
var firstNames = new[] { "George", "John", "Thomas" };
var presidents = ids.Zip(firstNames, (i, f) => Tuple.Create(i, f));
I first created an array of numbers, then an array of strings. Then, I zipped it up to create pairs of each one. Here’s the output when the pairs are written to the console:
(1, George)
(2, John)
(3, Thomas)
One thing I find disappointing is that there are no overloads provided for Zip. What if I need to map more than two sequences? It’s fairly trivial to implement, and I believe it should have been included in the .NET framework. For now, we will have to settle with creating our own.
public static class Enumberable
{
public static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>
(this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
IEnumerable<TThird> third, Func<TFirst, TSecond, TThird, TResult> selector)
{
if (first == null)
{
throw new ArgumentNullException("first");
}
if (second == null)
{
throw new ArgumentNullException("second");
}
if (third == null)
{
throw new ArgumentNullException("third");
}
if (selector == null)
{
throw new ArgumentNullException("selector");
}
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
using (var thirdEnumerator = third.GetEnumerator())
{
while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext()
&& thirdEnumerator.MoveNext())
{
yield return selector(firstEnumerator.Current, secondEnumerator.Current,
thirdEnumerator.Current);
}
}
}
}
This creates a Zip extension utilizing three sequences. If you need more, just copy the code and add appropriate code for appropriate arguments. **EDIT** I forgot to add using clauses.
With this version, I will create an anonymous type to name the properties instead of using a tuple.
var ids = new[] { 1, 2, 3 };
var firstNames = new[] { "George", "John", "Thomas" };
var lastNames = new[] { "Washington", "Adams", "Jefferson" };
var presidents = ids.Zip(firstNames, lastNames,
(i, f, l) => new { Id = i, FirstName = f, LastName = l });
Here is the output when the presidents are written to the console.
{ Id = 1, FirstName = George, LastName = Washington }
{ Id = 2, FirstName = John, LastName = Adams }
{ Id = 3, FirstName = Thomas, LastName = Jefferson }
As with Select, you are not limited to anonymous types or tuples. This functionality is useful when reading data from disparate sources to create sequences of objects.