有没有一些我还没有遇到的罕见语言结构(就像我最近在 Stack Overflow 上学到的一些)可以在 C# 中获得表示当前 foreach 循环迭代的值?
例如,根据情况,我目前会做类似于以下的操作:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
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.value
)及其索引(item.i
):
函数内部的第二个参数表示源元素的索引。
new { i, value }
创建了一个新的匿名对象。
如果您使用的是 C# 7.0 或更高版本,可以使用 ValueTuple
来避免堆分配:
foreach (var item in Model.Select((value, i) => ( value, i )))
{
var value = item.value;
var index = item.i;
}
你也可以通过使用自动解构来消除 item.
:
foreach (var (value, i) in Model.Select((value, i) => ( value, i )))
{
// Access `value` and `i` directly here.
}
foreach
用于迭代实现IEnumerable
的集合。它通过在集合上调用GetEnumerator
来实现,该方法将返回一个Enumerator
。MoveNext()
Current
Current
返回枚举器当前正在处理的对象,MoveNext
将Current
更新为下一个对象。for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
- Chad Hedgcockzip
函数)。 - jpmc26foreach (var (item, index) in collection.WithIndex())
{
Debug.WriteLine($"{index}: {item}");
}
using System.Collections.Generic;
public static class IEnumerableExtensions {
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
=> self.Select((item, index) => (item, index));
}
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
修改后的代码可以处理传入null的集合,如果传入的集合为null,则返回一个空列表。原本的意思没有改变,只是添加了对空集合的处理。 - 2ToadEnumerated
可能更容易被习惯于其他语言的人识别(也许还可以交换元组参数的顺序)。不过 WithIndex
本身已经很明显了。 - FernAndrEnumerable.Empty<(T, int)>()
,比创建一个空列表更加高效。 - Dan Diplo可以这样做:
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));
}
}
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
- user2023861我不同意那些认为在大多数情况下使用 for
循环更好的评论。
foreach
是一个有用的结构,在某些情况下无法被 for
循环替代。
例如,如果您有一个 DataReader 并使用 foreach
循环遍历所有记录,它会自动调用 Dispose 方法并关闭读取器(这样可以自动关闭连接)。因此,即使您忘记关闭阅读器,也更安全,因为它可以防止连接泄漏。
(当然,总是关闭读取器是一个良好的实践方法,但编译器不会捕捉到它,如果您不关闭读取器,您不能保证已经关闭了所有读取器,但您可以通过习惯使用 foreach 来使泄漏连接的可能性更小。)
还可能有其他隐式调用 Dispose
方法的例子是有用的。
字面回答 -- 警告:性能可能不如仅使用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);
}
使用 LINQ、C# 7 和 System.ValueTuple
NuGet 包,您可以这样做:
foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
Console.WriteLine(value + " is at index " + index);
}
您可以使用普通的foreach
语句,并且能够直接访问值和索引,而不是作为对象成员,并且在循环的范围内保留这两个字段。因此,如果您能够使用C# 7和System.ValueTuple
,我认为这是最好的解决方法。
var i = 0;
foreach (var e in collection) {
// Do stuff with 'e' and 'i'
i++;
}
如果您知道您的可索引集合在索引访问方面是O(1),则使用此方法(对于Array
和可能对于List<T>
(文档没有说明),它将是O(1),但对于其他类型(如LinkedList
)则不一定):
// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
var e = collection[i];
// Do stuff with 'e' and 'i'
}
// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
.AsParallel()
.Where((e,i) => /* filter with e,i */)
.ForAll(e => { /* use e, but don't modify it */ });
// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
.AsParallel()
.Select((e, i) => new MyWrapper(e, i));
AsParallel()
,因为现在已经是2014年了,我们希望充分利用那些多核来加快速度。此外,在“顺序”LINQ中,你只能在List<T>
和Array
上获取ForEach()
扩展方法...而且并不清楚使用它是否比使用简单的foreach
更好,因为你仍然在单线程下运行,语法更丑陋。借鉴了@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,如果有下一个元素可以访问,则将其作为循环条件,这样当我们遍历完所有元素时,循环就会停止。
listEnumerator
,它是一个通用枚举器,因此它确实实现了 IDisposable
并应该被处理。 - Antonín LejsekList<T>
的枚举器,你发现得很好。它实现了 System.Collections.Generic.IEnumerator<T>
,该接口继承自 IDisposable
。List<T>
的枚举器在 Dispose
中不执行任何操作,也没有终结器,因此在这种情况下调用 Dispose
没有任何效果,但对于其他可枚举对象可能会有影响。 - Edward Brey只需要添加自己的索引即可。保持简单。
int i = -1;
foreach (var item in Collection)
{
++i;
item.index = i;
}
foreach
的主要目的是为所有集合提供一个通用的迭代机制,无论它们是否可索引(如List
)或不可索引(如Dictionary
)。 - Brian GideonDictionary
不能被索引,但Dictionary
的迭代会按特定顺序遍历它(即一个枚举器是可索引的,因为它按顺序产生元素)。在这个意义上,我们可以说我们不是在寻找集合内的索引,而是当前枚举元素在枚举中的索引(即我们是否处于第一个、第五个或最后一个枚举元素)。 - Matt Mitchell