在c#中是否有一些我没有遇到过的罕见的语言构造(比如我最近学过的一些,一些在Stack Overflow上)来获得表示foreach循环的当前迭代的值?
例如,我目前根据具体情况做这样的事情:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
在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}");
}
}
}
其他回答
字面答案——警告,性能可能不如仅使用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);
}
像这样的怎么样?注意,如果myEnumerable为空,myDelimitedString可能为空。
IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;
if( enumerator.MoveNext() )
current = (string)enumerator.Current;
while( null != current)
{
current = (string)enumerator.Current; }
myDelimitedString += current;
if( enumerator.MoveNext() )
myDelimitedString += DELIMITER;
else
break;
}
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.
}
这个答案是:游说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周期呢? 要求编码器添加索引跟踪代码,破坏了它的美感 它很容易实现(请微软),并且向后兼容
虽然这里的大多数人都不是微软的员工,但这是一个正确的答案,你可以游说微软增加这样一个功能。您已经可以使用扩展函数构建自己的迭代器并使用元组,但微软可以在语法上略加改进以避免使用扩展函数
它只适用于List而不是任何IEnumerable,但在LINQ中有这样的:
IList<Object> collection = new List<Object> {
new Object(),
new Object(),
new Object(),
};
foreach (Object o in collection)
{
Console.WriteLine(collection.IndexOf(o));
}
Console.ReadLine();
@Jonathan我没有说这是一个很好的答案,我只是说这只是表明,他要求的事情是可以做到的:)
@Graphain我不期望它会很快-我不完全确定它是如何工作的,它可以重复整个列表每次找到一个匹配的对象,这将是一个非常大量的比较。
也就是说,List可能保留每个对象的索引和计数。
乔纳森似乎有更好的主意,能详细说说吗?
不过,最好只是记录一下你在foreach中所做的事情,这样更简单,适应性更强。