一次循环2个列表

Gra*_*ton 19 c# list

我有两个长度相同的列表,是否可以一次循环这两个列表?

我正在寻找正确的语法来执行以下操作

foreach itemA, itemB in ListA, ListB
{
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
Run Code Online (Sandbox Code Playgroud)

你认为这在C#中有可能吗?如果是的话,lambda表达式与此相当的是什么?

Mar*_*ell 26

[编辑]:澄清; 这在通用LINQ/IEnumerable<T>context中非常有用,在这种情况下你不能使用索引器,因为a:它不存在于可枚举中,而b:你不能保证你可以多次读取数据.由于OP提到了lambdas,所以LINQ可能不会太远(是的,我确实意识到LINQ和lambdas不是完全相同的事情).

听起来你需要缺少的Zip操作员; 你可以欺骗它:

static void Main()
{
    int[] left = { 1, 2, 3, 4, 5 };
    string[] right = { "abc", "def", "ghi", "jkl", "mno" };

    // using KeyValuePair<,> approach
    foreach (var item in left.Zip(right))
    {
        Console.WriteLine("{0}/{1}", item.Key, item.Value);
    }

    // using projection approach
    foreach (string item in left.Zip(right,
        (x,y) => string.Format("{0}/{1}", x, y)))
    {
        Console.WriteLine(item);
    }
}

// library code; written once and stuffed away in a util assembly...

// returns each pais as a KeyValuePair<,>
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
    return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y));
}

// accepts a projection from the caller for each pair
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right,
    Func<TLeft, TRight, TResult> selector)
{
    using(IEnumerator<TLeft> leftE = left.GetEnumerator())
    using (IEnumerator<TRight> rightE = right.GetEnumerator())
    {
        while (leftE.MoveNext() && rightE.MoveNext())
        {
            yield return selector(leftE.Current, rightE.Current);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


jce*_*gin 11

相反,在一个普通的旧循环中做它会简单得多......

for(int i=0; i<ListA.Length; i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
Run Code Online (Sandbox Code Playgroud)

  • 区别在于您只需要一次*的Zip扩展方法*,您可以永久重用它.此外,它将适用于任何序列而不仅仅是列表. (4认同)
  • 我被Jon Skeet羞辱了:( (4认同)

Mar*_*ese 7

现代答案

LINQ 现在有一个内置的 Zip 方法,因此您无需创建自己的方法。生成的序列与最短的输入一样长。Zip 目前(从 .NET Core 3.0 开始)有 2 个重载。更简单的返回一个元组序列。它让我们可以生成一些非常简洁的代码,接近原始请求:

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

foreach (var (number, word) in numbers.Zip(words))
    Console.WriteLine($"{number}, {word}");

// 1, one
// 2, two
// 3, three
Run Code Online (Sandbox Code Playgroud)

或者,同样的事情,但没有元组解包:

foreach (var item in numbers.Zip(words))
    Console.WriteLine($"{item.First}, {item.Second}");
Run Code Online (Sandbox Code Playgroud)

另一个重载(早于 Core 3.0 出现)采用映射函数,使您可以更好地控制结果。此示例返回字符串序列,但您可以返回任何内容的序列(例如某些自定义类)。

var numbersAndWords = numbers.Zip(words, (number, word) => $"{number}, {word}");

foreach (string item in numbersAndWords)
    Console.WriteLine(item);
Run Code Online (Sandbox Code Playgroud)

如果您在常规对象上使用 LINQ(而不是使用它来生成 SQL),您还可以查看MoreLINQ,它提供了多种压缩方法。这些方法中的每一个最多需要 4 个输入序列,而不仅仅是 2 个:

  • EquiZip- 如果输入序列的长度不同,则会引发异常。

  • ZipLongest- 生成的序列将始终与最长的输入序列一样长,其中每个较短序列元素类型的默认值用于填充。

  • ZipShortest- 生成的序列与最短的输入序列一样短。

请参阅他们的示例和/或测试以了解用法。MoreLINQ 的压缩似乎出现在常规 LINQ 之前,因此如果您坚持使用旧版本的 .NET,它可能是一个不错的选择。


小智 5

你可以明确地做到这一点.

IEnumerator ListAEnum = ListA.GetEnumerator();
IEnumerator ListBEnum = ListB.GetEnumerator();

ListBEnum.MoveNext();
while(ListAEnum.MoveNext()==true)
{
  itemA=ListAEnum.getCurrent();
  itemB=ListBEnum.getCurrent();
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
Run Code Online (Sandbox Code Playgroud)

至少这个(或类似的东西)是编译器为foreach循环所做的事情.我没有测试它,我猜测枚举器缺少一些模板参数.

只需从List和IEnumerator-Interface中查找GetEnumerator().