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

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

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

当前回答

我不同意在大多数情况下使用for循环是更好的选择的说法。

Foreach是一个有用的构造,在所有情况下都不能被for循环所取代。

例如,如果您有一个DataReader,并使用foreach循环遍历所有记录,它会自动调用Dispose方法并关闭阅读器(然后自动关闭连接)。因此,这是更安全的,因为它可以防止连接泄漏,即使您忘记关闭读取器。

(当然,总是关闭读取器是很好的做法,但如果你不这样做,编译器就不会捕捉到它——你不能保证你已经关闭了所有的读取器,但你可以通过养成使用foreach的习惯,使它更有可能不会泄漏连接。)

对于Dispose方法的隐式调用,可能还有其他有用的例子。

其他回答

这个答案是:游说c#语言团队获得直接的语言支持。

开头的答案是这样的:

显然,索引的概念与的概念是不同的 枚举,并且不能执行。

虽然目前的c#语言版本(2020年)确实如此,但这并不是CLR/ language的概念限制,它是可以做到的。

微软c#语言开发团队可以通过添加对新的Interface IIndexedEnumerable的支持来创建一个新的c#语言特性

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

如果使用foreach()并且存在var索引,则编译器期望项集合声明IIndexedEnumerable接口。如果没有接口,编译器可以用IndexedEnumerable对象对源代码进行polyfill包装,该对象将添加到代码中用于跟踪索引。

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

稍后,可以将CLR更新为具有内部索引跟踪,这仅在指定了with关键字且源代码没有直接实现IIndexedEnumerable时使用

Why:

Foreach看起来更好,在业务应用程序中,Foreach循环很少成为性能瓶颈 Foreach可以更有效地使用内存。拥有一个函数管道,而不是在每一步都转换为新的集合。当CPU缓存故障和垃圾收集更少时,谁会关心它是否使用了更多的CPU周期呢? 要求编码器添加索引跟踪代码,破坏了它的美感 它很容易实现(请微软),并且向后兼容

虽然这里的大多数人都不是微软的员工,但这是一个正确的答案,你可以游说微软增加这样一个功能。您已经可以使用扩展函数构建自己的迭代器并使用元组,但微软可以在语法上略加改进以避免使用扩展函数

我不同意在大多数情况下使用for循环是更好的选择的说法。

Foreach是一个有用的构造,在所有情况下都不能被for循环所取代。

例如,如果您有一个DataReader,并使用foreach循环遍历所有记录,它会自动调用Dispose方法并关闭阅读器(然后自动关闭连接)。因此,这是更安全的,因为它可以防止连接泄漏,即使您忘记关闭读取器。

(当然,总是关闭读取器是很好的做法,但如果你不这样做,编译器就不会捕捉到它——你不能保证你已经关闭了所有的读取器,但你可以通过养成使用foreach的习惯,使它更有可能不会泄漏连接。)

对于Dispose方法的隐式调用,可能还有其他有用的例子。

最后,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));
}

字面答案——警告,性能可能不如仅使用int来跟踪索引。至少它比使用IndexOf要好。

您只需要使用Select的索引重载,用一个知道索引的匿名对象来包装集合中的每个项。这可以针对任何实现IEnumerable的对象执行。

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

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