185

This just for general knowledge:

If I have two, let's say, List, and I want to iterate both with the same foreach loop, can we do that?

Edit

Just to clarify, I wanted to do this:

List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };

for(int i = 0; i < listA.Count; i++)
    listB[i] = listA[i];

But with a foreach =)

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
Hugo
  • 6,244
  • 8
  • 37
  • 43

11 Answers11

344

This is known as a Zip operation and has been supported since .NET 4.

With that, you would be able to write something like:

var numbers = new [] { 1, 2, 3, 4 };
var words = new [] { "one", "two", "three", "four" };

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach(var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

As an alternative to the anonymous type with the named fields, you can also save on braces by using a Tuple and its static Tuple.Create helper:

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{
    Console.WriteLine(nw.Item1 + nw.Item2);
}
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 2
    Here's an article on it: http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx – James Kolpack Dec 23 '09 at 23:01
  • 4
    @Hugo: It's a standard construct in Functional Programming :) – Mark Seemann Dec 23 '09 at 23:03
  • 8
    Since C# 7, you could also use a ValueTuple (see https://stackoverflow.com/a/45617748) instead of anonymous types or Tuple.Create. I.e. `foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }`. – Erlend Graff Feb 14 '18 at 16:39
  • Any performance gain other than simplicity? Zip will anyhow enumerate the lists. – Frank Q. May 08 '18 at 17:18
  • Excellent work ! I used to think that the difference between for and foreach is that foreach is bound to list one list only but for can list multiple thanks to you for correcting me:) – TAHA SULTAN TEMURI May 25 '18 at 05:29
  • I came here searching for guarantee of ordering, so it might be worth noting that the docs say. "The method merges each element of the first sequence with an element that has the same index in the second sequence." – General Grievance Apr 27 '23 at 15:59
19

If you don't want to wait for .NET 4.0, you could implement your own Zip method. The following works with .NET 2.0. You can adjust the implementation depending on how you want to handle the case where the two enumerations (or lists) have different lengths; this one continues to the end of the longer enumeration, returning the default values for missing items from the shorter enumeration.

static IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> first, IEnumerable<U> second)
{
    IEnumerator<T> firstEnumerator = first.GetEnumerator();
    IEnumerator<U> secondEnumerator = second.GetEnumerator();

    while (firstEnumerator.MoveNext())
    {
        if (secondEnumerator.MoveNext())
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, secondEnumerator.Current);
        }
        else
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, default(U));
        }
    }
    while (secondEnumerator.MoveNext())
    {
        yield return new KeyValuePair<T, U>(default(T), secondEnumerator.Current);
    }
}

static void Test()
{
    IList<string> names = new string[] { "one", "two", "three" };
    IList<int> ids = new int[] { 1, 2, 3, 4 };

    foreach (KeyValuePair<string, int> keyValuePair in ParallelEnumerate(names, ids))
    {
        Console.WriteLine(keyValuePair.Key ?? "<null>" + " - " + keyValuePair.Value.ToString());
    }
}
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Joe
  • 122,218
  • 32
  • 205
  • 338
  • 1
    Nice method! :). You can make a few adjustments to use the same signature as .NET 4 Zip method http://msdn.microsoft.com/en-us/library/dd267698.aspx and return resultSelector(first, second) instead of a KVP. – Martín Coll Jun 12 '13 at 19:17
  • Note that this method does not dispose its enumerators which could become a problem, e.g. if it is used with enumerables over the lines of open files. – Lii Mar 09 '15 at 14:59
19

Since C# 7, you can use Tuples...

int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };

foreach (var tuple in nums.Zip(words, (x, y) => (x, y)))
{
    Console.WriteLine($"{tuple.Item1}: {tuple.Item2}");
}

// or...
foreach (var tuple in nums.Zip(words, (x, y) => (Num: x, Word: y)))
{
    Console.WriteLine($"{tuple.Num}: {tuple.Word}");
}

EDIT 2022-04-14

The Zip extension method just got better since the original answer (and .NET Core 3.0), so you can now just write

int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };

foreach (var (x, y) in nums.Zip(words))
{
    Console.WriteLine($"{x}: {y}");
}

and variant for three arrays is also supported since .NET 6

int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };
string[] roman = { "I", "II", "III", "IV" };

foreach (var (x, y, z) in nums.Zip(words, roman))
{
    Console.WriteLine($"{x}: {y} ({z})");
}
Matěj Pokorný
  • 16,977
  • 5
  • 39
  • 48
  • 1
    What happens when the two lists are not the same length in this situation? – Hooplator15 Jun 10 '19 at 20:08
  • 1
    With `(x, y) => (x, y)` we can use named `tuple.x` and `tuple.y` which is elegant. So The second form could also be `(Num, Word) => (Num, Word)` – dashesy Jul 30 '19 at 19:24
  • 3
    @JohnAugust It terminates after traversing the shorter sequence. From docs:" If the sequences do not have the same number of elements, the method merges sequences until it reaches the end of one of them. For example, if one sequence has three elements and the other one has four, the result sequence will have only three elements." – gregsmi Nov 05 '19 at 17:27
13

You can use Union or Concat, the former removes duplicates, the later doesn't

foreach (var item in List1.Union(List1))
{
   //TODO: Real code goes here
}

foreach (var item in List1.Concat(List1))
{
   //TODO: Real code goes here
}
albertein
  • 26,396
  • 5
  • 54
  • 57
3

Here's a custom IEnumerable<> extension method that can be used to loop through two lists simultaneously.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    public static class LinqCombinedSort
    {
        public static void Test()
        {
            var a = new[] {'a', 'b', 'c', 'd', 'e', 'f'};
            var b = new[] {3, 2, 1, 6, 5, 4};

            var sorted = from ab in a.Combine(b)
                         orderby ab.Second
                         select ab.First;

            foreach(char c in sorted)
            {
                Console.WriteLine(c);
            }
        }

        public static IEnumerable<Pair<TFirst, TSecond>> Combine<TFirst, TSecond>(this IEnumerable<TFirst> s1, IEnumerable<TSecond> s2)
        {
            using (var e1 = s1.GetEnumerator())
            using (var e2 = s2.GetEnumerator())
            {
                while (e1.MoveNext() && e2.MoveNext())
                {
                    yield return new Pair<TFirst, TSecond>(e1.Current, e2.Current);
                }
            }

        }


    }
    public class Pair<TFirst, TSecond>
    {
        private readonly TFirst _first;
        private readonly TSecond _second;
        private int _hashCode;

        public Pair(TFirst first, TSecond second)
        {
            _first = first;
            _second = second;
        }

        public TFirst First
        {
            get
            {
                return _first;
            }
        }

        public TSecond Second
        {
            get
            {
                return _second;
            }
        }

        public override int GetHashCode()
        {
            if (_hashCode == 0)
            {
                _hashCode = (ReferenceEquals(_first, null) ? 213 : _first.GetHashCode())*37 +
                            (ReferenceEquals(_second, null) ? 213 : _second.GetHashCode());
            }
            return _hashCode;
        }

        public override bool Equals(object obj)
        {
            var other = obj as Pair<TFirst, TSecond>;
            if (other == null)
            {
                return false;
            }
            return Equals(_first, other._first) && Equals(_second, other._second);
        }
    }

}
Samuel Neff
  • 73,278
  • 17
  • 138
  • 182
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 example, you could write:

var people = new List<Person> { person1, person2 };
var wages = new List<decimal> { 10, 20 };

people.ForPair(wages, (p, w) => p.Wage = w);

Note however that this method cannot be used to modify the collection itself. This for example will not work:

List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };

listA.ForPair(listA, (c1, c2) => c1 = c2);  // Nothing will happen!

So in this case, the example in your own question is probably the best way.

Hakakou
  • 522
  • 3
  • 6
0

No, you would have to use a for-loop for that.

for (int i = 0; i < lst1.Count; i++)
{
    //lst1[i]...
    //lst2[i]...
}

You can't do something like

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
    //...
}
Maximilian Mayerl
  • 11,253
  • 2
  • 33
  • 40
0

If you want one element with the corresponding one you could do

Enumerable.Range(0, List1.Count).All(x => List1[x] == List2[x]);

That will return true if every item is equal to the corresponding one on the second list

If that's almost but not quite what you want it would help if you elaborated more.

albertein
  • 26,396
  • 5
  • 54
  • 57
0

This method would work for a list implementation and could be implemented as an extension method.

public void TestMethod()
{
    var first = new List<int> {1, 2, 3, 4, 5};
    var second = new List<string> {"One", "Two", "Three", "Four", "Five"};

    foreach(var value in this.Zip(first, second, (x, y) => new {Number = x, Text = y}))
    {
        Console.WriteLine("{0} - {1}",value.Number, value.Text);
    }
}

public IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(List<TFirst> first, List<TSecond> second, Func<TFirst, TSecond, TResult> selector)
{
    if (first.Count != second.Count)
        throw new Exception();  

    for(var i = 0; i < first.Count; i++)
    {
        yield return selector.Invoke(first[i], second[i]);
    }
}
Rohan West
  • 9,262
  • 3
  • 37
  • 64
0

You could also simply use a local integer variable if the lists have the same length:

List<classA> listA = fillListA();
List<classB> listB = fillListB();

var i = 0;
foreach(var itemA in listA)
{
    Console.WriteLine(itemA  + listB[i++]);
}
thalm
  • 2,738
  • 2
  • 35
  • 49
  • If you have to index one of the lists, the advantage of `foreach` is lost. And in this case, your constraint is actually that `listB` must be **at least** as long as `listA`, which seems like an accident waiting to happen. – General Grievance Apr 27 '23 at 20:32
-3

I understand/hope that the lists have the same length: No, your only bet is going with a plain old standard for loop.

Benjamin Podszun
  • 9,679
  • 3
  • 34
  • 45