使用C#中的一个ForEach语句迭代两个列表或数组


142

这只是为了常识:

如果我有两个,比如说List,我想用相同的foreach循环来迭代两个,我们能做到吗?

编辑

只是为了澄清,我想这样做:

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];

但是用foreach =)


10
这里的重要词是“ zip”。
Mark Byers

3
是否要并行迭代两个列表?还是要先遍历一个列表,然后再遍历另一个列表(使用单个语句)?
帕维尔米纳夫

我认为您的方式比拉链好看
亚历山大

Answers:


271

这被称为Zip操作,并且在.NET 4中受支持。

这样,您将可以编写如下内容:

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);
}

作为使用命名字段的匿名类型的替代方法,您还可以使用元组及其静态元组来节省花括号。

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{
    Console.WriteLine(nw.Item1 + nw.Item2);
}


2
对于这些Zip操作一无所知,我将对该主题进行小型研究。谢谢!
雨果

4
@Hugo:这是函数式编程的标准结构:)
Mark Seemann

您还需要使用System.Linq;。
Jahmic '17

5
从C#7开始,您还可以使用ValueTuple(请参见stackoverflow.com/a/45617748)代替匿名类型或Tuple.Create。即foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }
Erlend Graff

14

如果不想等待.NET 4.0,则可以实现自己的Zip方法。以下适用于.NET 2.0。您可以根据如何处理两个枚举(或列表)具有不同长度的情况来调整实现。这将继续到较长的枚举的结尾,从较短的枚举返回缺失项的默认值。

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());
    }
}

1
好方法!:)。您可以进行一些调整,以使用与.NET 4 Zip方法msdn.microsoft.com/en-us/library/dd267698.aspx相同的签名,并返回resultSelector(first,second)而不是KVP。
马丁·柯尔

请注意,此方法不会处理可能会成为问题的枚举器,例如,如果它与打开文件行中的枚举一起使用。
2015年

11

您可以使用Union或Concat,前者删除重复项,后者不删除

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

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

使用Union的另一个问题是,如果实例评估为相等,它可能会丢弃实例。那可能并不总是您想要的。
Mark Seemann

1
我很难相信他的意图是使用相同类型的收藏集
albertein

@Mark Seemann,我已经指出,他也可以使用Concat
albertein

与Union一样,Concat仅在两个列表属于同一类型时才起作用。不过,无法确定这是否是OP所需要的...
Mark Seemann,

这将创建一个包含所有元素的新列表。这是浪费内存。请改用Linq Concat。
Drew Noakes

3

这是一个自定义IEnumerable <>扩展方法,可用于同时循环浏览两个列表。

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);
        }
    }

}

3

从C#7开始,您可以使用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}");
}

1
在这种情况下,如果两个列表的长度不同,会发生什么情况?
约翰

(x, y) => (x, y)我们可以使用named tuple.xtuple.y这是优雅的。因此,第二种形式也可能是(Num, Word) => (Num, Word)
破旧的'19

2
@JohnAugust在遍历较短的序列后终止。来自文档:“如果序列不具有相同数量的元素,则该方法将序列合并,直到到达其中一个的末尾。例如,如果一个序列具有三个元素,而另一个具有四个元素,则结果序列将只有三个要素。”
gregsmi

0

不,您必须为此使用一个for循环。

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

你不能做这样的事情

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
    //...
}

如果他们的计数不同怎么办?
Drew Noakes

然后,接受任何可枚举列表的foreach也将无法正常工作,从而使整个事情变得毫无用处。
Maximilian Mayerl 09年

0

如果您想要一个元素和相应的元素,您可以做

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

如果每个项目都等于第二个列表中的对应项目,则将返回true

如果这几乎是但不完全是您想要的,那么在您进行详细说明时会有所帮助。


0

此方法适用于列表实现,并且可以作为扩展方法实现。

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]);
    }
}

0

如果列表的长度相同,也可以只使用局部整数变量:

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

var i = 0;
foreach(var itemA in listA)
{
    Console.WriteLine(itemA  + listB[i++]);
}

-1

您还可以执行以下操作:

var i = 0;
foreach (var itemA in listA)
{
  Console.WriteLine(itemA + listB[i++]);
}

注意:的长度listA必须与相同listB


-3

我了解/希望列表具有相同的长度:不,您唯一的选择是使用普通的循环标准。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.