简体   繁体   English

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

[英]Iterate two Lists or Arrays with one ForEach statement in C#

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?如果我有两个,比如说List ,并且我想用相同的 foreach 循环迭代两个,我们可以这样做吗?

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 =)但是使用 foreach =)

This is known as a Zip operation and will be supported in .NET 4.这称为Zip操作,将在 .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:作为具有命名字段的匿名类型的替代方案,您还可以通过使用元组及其静态 Tuple.Create 助手来节省大括号:

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

If you don't want to wait for .NET 4.0, you could implement your own Zip method.如果您不想等待 .NET 4.0,您可以实现自己的Zip方法。 The following works with .NET 2.0.以下适用于 .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());
    }
}

You can use Union or Concat, the former removes duplicates, the later doesn't您可以使用 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
}

Since C# 7, you can use Tuples...从 C# 7 开始,您可以使用元组...

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

Here's a custom IEnumerable<> extension method that can be used to loop through two lists simultaneously.这是一个自定义的 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);
        }
    }

}

I often need to execute an action on each pair in two collections.我经常需要对两个集合中的每一对执行一个操作 The Zip method is not useful in this case. Zip 方法在这种情况下没有用。

This extension method ForPair can be used:可以使用此扩展方法ForPair

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.因此,在这种情况下,您自己问题中的示例可能是最好的方法。

No, you would have to use a for-loop for that.不,您必须为此使用 for 循环。

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)
{
    //...
}

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如果每个项目都等于第二个列表中的相应项目,则返回 true

If that's almost but not quite what you want it would help if you elaborated more.如果这几乎但不完全是您想要的,那么如果您详细说明会有所帮助。

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

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

You can also do the following:您还可以执行以下操作:

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

Note: the length of listA must be the same with listB .注意: listA的长度必须与listB的长度相同。

我理解/希望列表具有相同的长度:不,您唯一的赌注是使用普通的旧标准 for 循环。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM