Using LINQ's Zip with a closure that doesn't return a value
Asked Answered
O

2

14

Disclaimer: this question is driven by my personal curiosity more than an actual need to accomplish something. So my example is going to be contrived. Nevertheless I think it's an issue that might very well crop up.

Let's say we are using Zip to iterate over two sequences, invoking a void method that just throws an exception if one item of the couple is found to be different from the other (therefore discarding any return value). The point here is not that the method throws an exception, so much as it returns void.

In other words, we're kind of doing a ForEach over two collections (and by the way, I know what Eric Lippert thinks about ForEach, and fully agree with him and never use it).

Now, Zip wants a Func<TFirst, TSecond, TResult>, so of course passing something equivalent to Action<TFirst, TSecond> won't work.

My question is: is there an idiomatic way that is better than this (i.e. returning a dummy value)?

var collection1 = new List<int>() { ... };
var collection2 = new List<int>() { ... };

collection1.Zip(collection2, (first, second) => 
{
    VoidMethodThatThrows(first, second);
    return true;
});
Orchidaceous answered 25/6, 2012 at 13:30 Comment(0)
H
23

Use Zip() to throw the items into an object, then do your foreach however way you choose (do a normal foreach loop please, not the bad ToList/ForEach combo).

var items = collection1.Zip(collection2, (x, y) => new { First = x, Second = y });
foreach (var item in items)
{
    VoidMethodThatThrows(item.First, item.Second);
}

As of C# 7.0, improved tuple support and deconstruction makes it far more pleasing to work with.

var items = collection1.Zip(collection2, (x, y) => (x, y));
// or collection1.Zip(collection2, ValueTuple.Create);
foreach (var (first, second) in items)
{
    VoidMethodThatThrows(first, second);
}

Furthermore, .NET Core and 5 adds an overload which automatically pairs the values into tuples so you don't have to do that mapping.

var items = collection1.Zip(collection2); // IEnumerable<(Type1, Type2)>

.NET 6 adds a third collection to the mix.

var items = collection1.Zip(collection2, collection3); // IEnumerable<(Type1, Type2, Type3)>
Handmaid answered 25/6, 2012 at 13:53 Comment(0)
B
2

I often need to execute an action on each pair in two collections. The Zip method is not useful in this case.

This extension method ForPair can be used:

public static void ForPair<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second,
    Action<TFirst, TSecond> action)
{
    using (var enumFirst = first.GetEnumerator())
    using (var enumSecond = second.GetEnumerator())
    {
        while (enumFirst.MoveNext() && enumSecond.MoveNext())
        {
            action(enumFirst.Current, enumSecond.Current);
        }
    }
}

So for your example, you could write:

var collection1 = new List<int>() { 1, 2 };
var collection2 = new List<int>() { 3, 4 };

collection1.ForPair(collection2, VoidMethodThatThrows);
Borecole answered 8/12, 2020 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.