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

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

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

当前回答

c# 7最终为我们提供了一种优雅的方式:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

其他回答

foreach用于迭代实现IEnumerable的集合。它通过在集合上调用GetEnumerator来实现这一点,该集合将返回一个Enumerator。

这个枚举器有一个方法和一个属性:

MoveNext () 当前的

Current返回Enumerator当前所在的对象,MoveNext将Current更新为下一个对象。

索引的概念与枚举的概念是不同的,不能这样做。

因此,大多数集合都可以使用索引器和for循环构造来遍历。

在这种情况下,与使用局部变量跟踪索引相比,我更喜欢使用for循环。

我不确定你试图用基于问题的索引信息做什么。然而,在c#中,你通常可以使用IEnumerable。Select方法从您想要的任何内容中获取索引。例如,我可以使用类似这样的方法来判断一个值是奇数还是偶数。

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

这将根据列表中的项是奇数(1)还是偶数(0)的名称为您提供一个字典。

我想更理论化地讨论这个问题(因为它已经有了足够多的实际答案)

.net为数据组(又称集合)提供了一个非常好的抽象模型。

在最顶端,也是最抽象的,你有一个IEnumerable它只是一组你可以枚举的数据。你如何枚举并不重要,重要的是你可以枚举一些数据。这个枚举是由一个完全不同的对象完成的,IEnumerator

这些接口定义如下:

//
// Summary:
//     Exposes an enumerator, which supports a simple iteration over a non-generic collection.
public interface IEnumerable
{
    //
    // Summary:
    //     Returns an enumerator that iterates through a collection.
    //
    // Returns:
    //     An System.Collections.IEnumerator object that can be used to iterate through
    //     the collection.
    IEnumerator GetEnumerator();
}

//
// Summary:
//     Supports a simple iteration over a non-generic collection.
public interface IEnumerator
{
    //
    // Summary:
    //     Gets the element in the collection at the current position of the enumerator.
    //
    // Returns:
    //     The element in the collection at the current position of the enumerator.
    object Current { get; }

    //
    // Summary:
    //     Advances the enumerator to the next element of the collection.
    //
    // Returns:
    //     true if the enumerator was successfully advanced to the next element; false if
    //     the enumerator has passed the end of the collection.
    //
    // Exceptions:
    //   T:System.InvalidOperationException:
    //     The collection was modified after the enumerator was created.
    bool MoveNext();
    //
    // Summary:
    //     Sets the enumerator to its initial position, which is before the first element
    //     in the collection.
    //
    // Exceptions:
    //   T:System.InvalidOperationException:
    //     The collection was modified after the enumerator was created.
    void Reset();
}

as you might have noticed, the IEnumerator interface doesn't "know" what an index is, it just knows what element it's currently pointing to, and how to move to the next one. now here is the trick: foreach considers every input collection an IEnumerable, even if it is a more concrete implementation like an IList<T> (which inherits from IEnumerable), it will only see the abstract interface IEnumerable. what foreach is actually doing, is calling GetEnumerator on the collection, and calling MoveNext until it returns false. so here is the problem, you want to define a concrete concept "Indices" on an abstract concept "Enumerables", the built in foreach construct doesn't give you that option, so your only way is to define it yourself, either by what you are doing originally (creating a counter manually) or just use an implementation of IEnumerator that recognizes indices AND implement a foreach construct that recognizes that custom implementation.

就我个人而言,我会创建一个这样的扩展方法

public static class Ext
{
    public static void FE<T>(this IEnumerable<T> l, Action<int, T> act)
    {
        int counter = 0;
        foreach (var item in l)
        {
            act(counter, item);
            counter++;
        }
    }
}

像这样使用它

var x = new List<string>() { "hello", "world" };
x.FE((ind, ele) =>
{
    Console.WriteLine($"{ind}: {ele}");
});

这也避免了在其他答案中看到的任何不必要的分配。

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

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

你可以这样写你的循环:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

之后添加如下结构和扩展方法。

结构和扩展方法封装了Enumerable。选择功能。

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}