在c#中是否有一些我没有遇到过的罕见的语言构造(比如我最近学过的一些,一些在Stack Overflow上)来获得表示foreach循环的当前迭代的值?

例如,我目前根据具体情况做这样的事情:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

当前回答

可以用另一个包含索引信息的枚举数来包装原始枚举数。

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

下面是ForEachHelper类的代码。

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

其他回答

Ian Mercer在Phil Haack的博客上发表了一个类似的解决方案:

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

通过重载LINQ的Select函数,你可以得到item (item.value)和它的索引(item.i):

函数[在Select内部]的第二个参数表示源元素的索引。

新的{i, value}正在创建一个新的匿名对象。

如果你使用c# 7.0或更高版本,可以使用ValueTuple来避免堆分配:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

你也可以删除这个项目。通过使用自动解构:

foreach (var (value, i) in Model.Select((value, i) => ( value, i )))
{
    // Access `value` and `i` directly here.
}

可以这样做:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

最后,c# 7有一个不错的语法,用于在foreach循环(即元组)中获取索引:

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

需要一个小扩展方法:

using System.Collections.Generic;

public static class EnumExtension {
    public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
       => self.Select((item, index) => (item, index));
}

我不相信有一种方法可以获得foreach循环当前迭代的值。数数自己,似乎是最好的办法。

我能问一下,你为什么想知道?

看起来你最有可能做以下三件事中的一件:

1)从集合中获取对象,但在这种情况下,您已经拥有它。

2)为后期处理计算对象…集合有一个可以使用的Count属性。

3)根据对象在循环中的顺序设置它的属性…尽管您可以在将对象添加到集合时轻松地进行设置。

使用@FlySwat的答案,我想出了这个解决方案:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

你使用GetEnumerator来获取枚举器,然后使用for循环来进行循环。然而,诀窍是使循环的条件listEnumerator.MoveNext() == true。

由于枚举器的MoveNext方法如果存在下一个元素并且可以访问它,则返回true,因此循环条件使循环在耗尽要遍历的元素时停止。