foreach带索引

181

在C#中是否有与Python的enumerate()和Ruby的each_with_index相对应的功能?


如果您正在使用LINQ,则有各种函数的重载允许枚举。否则,通常需要使用自己递增的变量。 - GWLlosa
1
我们能否将这个问题更新到C# 7.0,因为现在有元组?我想知道使用元组会是什么样的解决方案。 - Hendrik Wiese
foreach 的一个特性不就是可以绝对解耦地处理列表中的每个元素吗? - Konstantin A. Magg
10个回答

276

我为此保留了这个扩展方法:

public static void Each<T>(this IEnumerable<T> ie, Action<T, int> action)
{
    var i = 0;
    foreach (var e in ie) action(e, i++);
}

然后像这样使用:

var strings = new List<string>();
strings.Each((str, n) =>
{
    // hooray
});

或者允许像break一样的行为:

public static bool Each<T>(this IEnumerable<T> ie, Func<T, int, bool> action)
{
    int i = 0;
    foreach (T e in ie) if (!action(e, i++)) return false;
    return true;
}

var strings = new List<string>() { "a", "b", "c" };

bool iteratedAll = strings.Each ((str, n)) =>
{
    if (str == "b") return false;
    return true;
});

30
在这个foreach循环中,你无法打破它。 - Tri Q Tran
29
那是因为它不是一个 foreach 循环。 - Dan
6
@TriQ 这只是更喜欢它的另一个原因,大部分时间都是如此。 :) - Yam Marcovic
1
@DanFinch 我并不是在挖苦你,我真的在寻找答案。 - TankorSmash
2
@TankorSmash 这是一个“扩展方法”。查一下吧。有些很棒的 C# 魔法。 - Kamil Szot
显示剩余4条评论

255

您可以执行以下操作

foreach (var it in someCollection.Select((x, i) => new { Value = x, Index = i }) )
{
   if (it.Index > SomeNumber) //      
}

这将为集合中的每个条目创建一个匿名类型的值。它将具有两个属性

  • Value: 包含原始集合中的值
  • Index: 包含在集合中的索引

26
聪明,但这就像用右手挠左耳朵一样。我想我会自己保留索引,这样我不会让未来的维护者感到困惑。 - Ken
47
@Neil,我很惊讶人们认为这是一个维护问题。选择器的过载(以及其他LINQ方法)完全是为了执行此类操作而提供的。 - JaredPar
5
我认为维护没有任何问题。如果未来的维护人员无法查阅MSDN文档并查找Select方法的重载,那就是他们自己的问题。担心变量名?只需使用以下lambda:(Value,Index) => select new { Value,Index }。 - Joshua Rodgers
12
如果没有后备集合,IEnumerable<T>.ElementAt(i) 的时间复杂度为O(n)(参见Schlemiel the Painter)。如果你有一个延迟枚举的序列,并且它在评估时很昂贵并且包含大量记录,那该怎么办?使用for循环时,必须等待IEnumerable<T>.Count()返回才能开始处理记录。因此,一般不应该使用for循环来处理 IEnumerable<T> - piedar
9
使用C# 7的ValueTuple可以让这段代码更短:foreach (var (x, i) in someCollection.Select((x, i) => (x, i)) ) { ... } - FernAndr
显示剩余9条评论

73

C#的foreach循环没有内置索引。您需要在foreach循环外部添加一个整数并在每次迭代中递增它。

int i = -1;
foreach (Widget w in widgets)
{
   i++;
   // do something
}

或者,您可以使用标准的for循环,如下所示:

for (int i = 0; i < widgets.Length; i++)
{
   w = widgets[i];
   // do something
}

20
我认为你应该将i初始化为-1,并在循环体的开头递增i,以确保“continue”语句不会出现问题。 - Tamas Czinege
8
为什么不直接将 i = 0 并在 foreach 语句的闭合括号前递增它呢?用非零值进行初始化会在以后浏览代码时引起关注…… - Adi
在JavaScript中工作过。 - Taufik Nurhidayat

17

我喜欢使用foreach,因此我创建了一个扩展方法和一个结构:

public struct EnumeratedInstance<T>
{
    public long cnt;
    public T item;
}

public static IEnumerable<EnumeratedInstance<T>> Enumerate<T>(this IEnumerable<T> collection)
{
    long counter = 0;
    foreach (var item in collection)
    {
        yield return new EnumeratedInstance<T>
        {
            cnt = counter,
            item = item
        };
        counter++;
    }
}

并且有一个使用示例:

foreach (var ii in new string[] { "a", "b", "c" }.Enumerate())
{
    Console.WriteLine(ii.item + ii.cnt);
}

有一个好处是,如果你已经习惯了Python的语法,你仍然可以使用它:

foreach (var ii in Enumerate(new string[] { "a", "b", "c" }))

那最后一部分中哪个部分看起来像是 Python 语法? - ArtOfWarfare
抱歉打扰了。我修改了你的函数,使其看起来更漂亮,使用起来更符合Python风格:http://pastebin.com/aExvenyY - KgOfHedgehogs

16

除了已经给出的LINQ答案外,我还有一个"SmartEnumerable"类,它允许您获取索引和“第一个/最后一个”的信息。从语法上来说,它有点丑陋,但您可能会发现它很有用。

我们可以使用非泛型类型中的静态方法来改进类型推断,并且隐式类型转换也会有所帮助。


2
太好了!这些小小的辅助属性(first/last/index)应该被包含在标准的 .net 框架中! - Philip Daubmeier
很好,我只是不喜欢它的长而且不具描述性的名称。 - Arek Bal

6
我的解决方案涉及我创建的一个简单的Pair类,用于一般实用程序,操作上本质上与框架类KeyValuePair相同。然后,我为IEnumerable创建了几个扩展函数称为Ordinate(来自集合论术语“ ordinal”)。
这些函数将为每个项返回一个包含索引和项本身的Pair对象。
public static IEnumerable<Pair<Int32, X>> Ordinate<X>(this IEnumerable<X> lhs)
{
    return lhs.Ordinate(0);
}

public static IEnumerable<Pair<Int32, X>> Ordinate<X>(this IEnumerable<X> lhs, Int32 initial)
{
    Int32 index = initial - 1;

    return lhs.Select(x => new Pair<Int32, X>(++index, x));
}

2
请注意,现在有一个 System.Tuple 可以用来代替你的 Pair - Asherah

4

不,没有。

正如其他人所展示的那样,有模拟Ruby行为的方法。但是有可能有一种类型实现了IEnumerable接口却没有暴露索引。


1

我刚找到一个有趣的解决方案:

public class DepthAware<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> source;

    public DepthAware(IEnumerable<T> source)
    {
        this.source = source;
        this.Depth = 0;
    }

    public int Depth { get; private set; }

    private IEnumerable<T> GetItems()
    {
        foreach (var item in source)
        {
            yield return item;
            ++this.Depth;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return GetItems().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// Generic type leverage and extension invoking
public static class DepthAware
{
    public static DepthAware<T> AsDepthAware<T>(this IEnumerable<T> source)
    {
        return new DepthAware<T>(source);
    }

    public static DepthAware<T> New<T>(IEnumerable<T> source)
    {
        return new DepthAware<T>(source);
    }
}

使用方法:

var chars = new[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'}.AsDepthAware();

foreach (var item in chars)
{
    Console.WriteLine("Char: {0}, depth: {1}", item, chars.Depth);
}

2
复活一个僵尸,因为我只需要这个:如果枚举被迭代超过一次,它将表现得令人困惑。 - millimoose
你说得对。如果我今天要实现这个功能,我肯定会选择被接受的答案。 - Pz.

1

这是您的收藏

var values = new[] {6, 2, 8, 45, 9, 3, 0};

为此集合创建一系列索引。
var indexes = Enumerable.Range(0, values.Length).ToList();

使用range函数进行索引迭代

indexes.ForEach(i => values[i] += i);
indexes.ForEach(i => Console.Write("[{0}] = {1}", i, values[i]));

0

这取决于您使用的类。

例如,Dictionary<(Of <(TKey, TValue>)>)类支持此操作

Dictionary<(Of <(TKey, TValue>)>)泛型类提供了从一组键到一组值的映射。

为了枚举,字典中的每个项都被视为表示值和其键的KeyValuePair<(Of <(TKey, TValue>)>)结构。返回项目的顺序是未定义的。

foreach (KeyValuePair kvp in myDictionary) {...}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接