是否有一些罕见的语言构造我没有遇到过(比如我最近学到的一些,有些是关于Stack Overflow)在C#中得到一个表示foreach循环当前迭代的值?
例如,我目前根据具体情况做这样的事情:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
答案 0 :(得分:524)
Ian Mercer在Phil Haack's blog上发布了类似的解决方案:
foreach (var item in Model.Select((value, i) => new { i, value }))
{
var value = item.value;
var index = item.i;
}
这可以使用this overload of Linq's Select
为您提供项目(item.value
)及其索引(item.i
):
函数的第二个参数[Select Select]表示源元素的索引。
new { i, value }
正在创建新的anonymous object。
如果您使用的是C#7.0或更高版本,则可以使用ValueTuple
来避免堆分配:
foreach (var item in Model.Select((value, i) => ( value, i )))
{
var value = item.value;
var index = item.i;
}
您还可以使用自动解构来消除item.
:
<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
<li id="item_@i">@value</li>
}
</ol>
答案 1 :(得分:493)
foreach
用于迭代实现IEnumerable
的集合。它通过调用集合上的GetEnumerator
来执行此操作,该集合将返回Enumerator
。
此枚举器具有方法和属性:
Current
返回Enumerator当前所在的对象,MoveNext
将Current
更新为下一个对象。
索引的概念对于枚举的概念来说是陌生的,并且无法完成。
因此,大多数集合都可以使用索引器和for循环结构遍历。
与使用局部变量跟踪索引相比,我更倾向于在这种情况下使用for循环。
答案 2 :(得分:106)
可以这样做:
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));
}
}
答案 3 :(得分:87)
最后,C#7有一个很好的语法来获取foreach
循环中的索引(即元组):
foreach (var (item, index) in collection.WithIndex())
{
Debug.WriteLine($"{index}: {item}");
}
需要一点扩展方法:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
=> self.Select((item, index) => (item, index));
答案 4 :(得分:83)
我不同意在大多数情况下for
循环是更好的选择的评论。
foreach
是一个有用的构造,在所有情况下都不能被for
循环替换。
例如,如果你有一个 DataReader 并使用foreach
遍历所有记录,它会自动调用 Dispose 方法并关闭阅读器(可以然后自动关闭连接)。因此,即使您忘记关闭阅读器,也可以更安全,因为它可以防止连接泄漏。
(当然,总是关闭读者是好的做法,但如果你不这样做,编译器就不会抓住它 - 你无法保证你已经关闭了所有的读者,但你可以让它更有可能你不会泄漏养成使用foreach的习惯的联系。)
可能还有其他Dispose
方法隐式调用的例子。
答案 5 :(得分:57)
字面答案 - 警告,性能可能不如仅使用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);
}
答案 6 :(得分:32)
使用@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,因此当我们用完迭代结束的元素时,使循环条件使循环停止。
答案 7 :(得分:26)
使用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
,这是最佳解决方案。
答案 8 :(得分:24)
您可以将原始枚举器包含在包含索引信息的另一个枚举器中。
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;
}
}
}
答案 9 :(得分:22)
使用计数器变量没有任何问题。实际上,无论您使用for
,foreach
while
还是do
,都必须在某处声明并递增计数器变量。
如果您不确定是否有适当索引的集合,请使用此惯用法:
var i = 0;
foreach (var e in collection) {
// Do stuff with 'e' and 'i'
i++;
}
如果你知道你的可索引集合是O(1)用于索引访问(它将用于Array
并且可能用于List<T>
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'
}
)):
IEnumerator
永远不需要通过调用MoveNext()
来“手动”操作Current
并询问foreach
- continue
可以节省您特别麻烦...如果您需要跳过项目,只需在循环体中使用// 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));
。
只是为了完整性,取决于你用你的索引做什么(上面的结构提供了很大的灵活性),你可以使用Parallel LINQ:
AsParallel()
我们使用上面的foreach
,因为它已经是2014年了,我们希望充分利用这些多核来加快速度。此外,对于“顺序”LINQ,you only get a ForEach()
extension method on List<T>
and Array
......并且不清楚使用它是否比执行简单的{{1}}更好,因为您仍在运行单线程以获得更粗糙的语法。
答案 10 :(得分:17)
这是我刚刚提出的解决此问题的解决方案
原始代码:
int index=0;
foreach (var item in enumerable)
{
blah(item, index); // some code that depends on the index
index++;
}
更新了代码
enumerable.ForEach((item, index) => blah(item, index));
扩展方法:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
{
var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
enumerable.Select((item, i) =>
{
action(item, i);
return unit;
}).ToList();
return pSource;
}
答案 11 :(得分:12)
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
这适用于支持IList
。
答案 12 :(得分:10)
它只适用于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我不希望它快 - 我不完全确定它是如何工作的,它可以在每次重复整个列表中找到一个匹配的对象,这将是比较的helluvalot。
也就是说,List可能会保留每个对象的索引以及计数。
Jonathan似乎有更好的主意,如果他会详细说明的话?
最好只记录你在foreach中所处的位置,更简单,更具适应性。
答案 13 :(得分:10)
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}");
}
}
}
答案 14 :(得分:9)
只需添加自己的索引即可。保持简单。
int i = 0;
foreach (var item in Collection)
{
item.index = i;
++i;
}
答案 15 :(得分:8)
为什么要foreach?!
最简单的方法是使用代替代替foreach ,如果您使用的是List 。
for(int i = 0 ; i < myList.Count ; i++)
{
// Do Something...
}
或者如果你想使用foreach:
foreach (string m in myList)
{
// Do something...
}
你可以用它来表示每个循环的khow索引:
myList.indexOf(m)
答案 16 :(得分:8)
我就是这样做的,这很简单/简洁,但是如果你在循环体obj.Value
中做了很多事情,它会变得很快。
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
答案 17 :(得分:5)
如果集合是列表,则可以使用List.IndexOf,如:
foreach (Object o in collection)
{
// ...
@collection.IndexOf(o)
}
答案 18 :(得分:5)
最好使用像这样的关键字continue
安全构造
int i=-1;
foreach (Object o in collection)
{
++i;
//...
continue; //<--- safe to call, index will be increased
//...
}
答案 19 :(得分:5)
主要答案指出:
“显然,索引的概念对于枚举的概念来说是陌生的,并且无法完成。”
虽然目前的C#版本也是如此,但这不是概念上的限制。
MS创建新的C#语言功能可以解决这个问题,并支持新的接口IIndexedEnumerable
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传递了一个IEnumerable并且无法解析一个IIndexedEnumerable,但是它被问到var index,那么C#编译器可以用一个IndexedEnumerable对象包装它,该对象会添加用于跟踪索引的代码。
interface IIndexedEnumerable<T> : IEnumerable<T>
{
//Not index, because sometimes source IEnumerables are transient
public long IterationNumber { get; }
}
为什么:
虽然这里的大多数人都不是MS,但这是一个正确的答案,你可以游说MS添加这样的功能。你可以使用extension function and use tuples构建自己的迭代器,但MS可以使用语法糖来避免扩展函数
答案 20 :(得分:3)
您可以像这样编写循环:
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
添加以下结构和扩展方法后。
struct和extension方法封装了Enumerable.Select功能。
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);
}
}
答案 21 :(得分:3)
我在LINQPad中构建了这个:
var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};
var listCount = listOfNames.Count;
var NamesWithCommas = string.Empty;
foreach (var element in listOfNames)
{
NamesWithCommas += element;
if(listOfNames.IndexOf(element) != listCount -1)
{
NamesWithCommas += ", ";
}
}
NamesWithCommas.Dump(); //LINQPad method to write to console.
您也可以使用string.join
:
var joinResult = string.Join(",", listOfNames);
答案 22 :(得分:3)
我不认为这应该是非常有效的,但它确实有效:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
答案 23 :(得分:3)
为了兴趣,Phil Haack在Razor Templated Delegate(http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx)的背景下写了一个这样的例子
实际上,他编写了一个扩展方法,该方法将迭代包装在“IteratedItem”类(见下文)中,允许在迭代期间访问索引和元素。
public class IndexedItem<TModel> {
public IndexedItem(int index, TModel item) {
Index = index;
Item = item;
}
public int Index { get; private set; }
public TModel Item { get; private set; }
}
然而,如果您在非Razor环境中执行单个操作(即可以作为lambda提供的操作),这将是正常的,但它不会成为非的for / foreach语法的可靠替代-Razor上下文。
答案 24 :(得分:3)
我对此问题的解决方案是扩展方法WithIndex()
,
像
一样使用它var list = new List<int> { 1, 2, 3, 4, 5, 6 };
var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));
答案 25 :(得分:2)
除非您的集合可以通过某种方法返回对象的索引,否则唯一的方法是使用类似示例的计数器。
但是,在处理索引时,问题的唯一合理答案是使用for循环。其他任何东西都会引入代码复杂性,更不用说时间和空间的复杂性了。
答案 26 :(得分:2)
//using foreach loop how to get index number:
foreach (var result in results.Select((value, index) => new { index, value }))
{
//do something
}
答案 27 :(得分:2)
我不确定您是根据问题对索引信息做了什么。但是,在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)。
答案 28 :(得分:2)
我刚遇到这个问题,但在我的案例中考虑问题给出了最佳解决方案,与预期的解决方案无关。
这可能是一个很常见的情况,基本上,我正在从一个源列表中读取并在目标列表中基于它们创建对象,但是,我必须首先检查源项是否有效并且想要返回一行任何错误。乍一看,我想把索引放到Current属性的对象的枚举器中,但是,当我复制这些元素时,我隐含地知道当前目标的当前索引。显然它取决于你的目标对象,但对我来说它是一个List,很可能它会实现ICollection。
即
var destinationList = new List<someObject>();
foreach (var item in itemList)
{
var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (stringArray.Length != 2)
{
//use the destinationList Count property to give us the index into the stringArray list
throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
}
else
{
destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
}
}
我认为并非总是适用,但往往足以值得一提。
无论如何,关键是有时你的逻辑中已经存在一个非显而易见的解决方案......
答案 29 :(得分:2)
这样的事情怎么样?请注意,如果myEnumerable为空,则myDelimitedString可能为null。
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;
}
答案 30 :(得分:2)
我不相信有一种方法可以获得foreach循环的当前迭代的值。算上自己,似乎是最好的方式。
请问,为什么你想知道?
似乎你最喜欢做三件事之一:
1)从集合中获取对象,但在这种情况下,您已经拥有它。
2)对对象进行计数以便以后进行后处理......这些集合具有您可以使用的Count属性。
3)根据循环中的顺序在对象上设置属性...虽然您可以在将对象添加到集合时轻松设置该属性。
答案 31 :(得分:0)
这不能回答您的具体问题,但它可以为您提供问题的解决方案:使用for循环来运行对象集合。那么你将拥有你正在处理的当前索引。
// Untested
for (int i = 0; i < collection.Count; i++)
{
Console.WriteLine("My index is " + i);
}
答案 32 :(得分:0)
以下是此问题的另一种解决方案,重点是尽可能使语法保持接近标准foreach
。
如果您希望在MVC中使您的视图看起来漂亮和干净,那么这种构造非常有用。例如,而不是以通常的方式编写这个(很难很好地格式化):
<%int i=0;
foreach (var review in Model.ReviewsList) { %>
<div id="review_<%=i%>">
<h3><%:review.Title%></h3>
</div>
<%i++;
} %>
你可以这样写:
<%foreach (var review in Model.ReviewsList.WithIndex()) { %>
<div id="review_<%=LoopHelper.Index()%>">
<h3><%:review.Title%></h3>
</div>
<%} %>
我已经编写了一些辅助方法来启用它:
public static class LoopHelper {
public static int Index() {
return (int)HttpContext.Current.Items["LoopHelper_Index"];
}
}
public static class LoopHelperExtensions {
public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) {
return new EnumerableWithIndex<T>(that);
}
public class EnumerableWithIndex<T> : IEnumerable<T> {
public IEnumerable<T> Enumerable;
public EnumerableWithIndex(IEnumerable<T> enumerable) {
Enumerable = enumerable;
}
public IEnumerator<T> GetEnumerator() {
for (int i = 0; i < Enumerable.Count(); i++) {
HttpContext.Current.Items["LoopHelper_Index"] = i;
yield return Enumerable.ElementAt(i);
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
在非网络环境中,您可以使用static
代替HttpContext.Current.Items
。
这本质上是一个全局变量,所以你不能嵌套多个WithIndex循环,但这不是这个用例中的主要问题。
答案 33 :(得分: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();
}
您可能已经注意到,IEnumerator
界面无法“知道”索引是什么,它仅知道当前指向的元素以及如何移动到下一个元素。 / p>
这是诀窍:foreach
将每个输入集合视为一个IEnumerable
,即使它是一个更具体的实现,例如IList<T>
(继承自{{1 }}),它只会看到抽象界面IEnumerable
。
IEnumerable
实际在做什么,正在对集合调用foreach
,并调用GetEnumerator
直到返回false。
这就是问题所在,您想在抽象概念“ Enumerables”上定义一个具体概念“ Indices”,内置的MoveNext
构造不会为您提供该选项,因此您只能一种方法是自己定义它,要么通过最初的操作(手动创建一个计数器),要么仅使用可识别索引的foreach
实现并实现可识别该自定义实现的IEnumerator
构造。 / p>
我个人会创建这样的扩展方法
foreach
并像这样使用它
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++;
}
}
}
这还避免了其他答案中看到的任何不必要的分配。
答案 34 :(得分:-1)
这样,您可以使用Linq使用索引和值
ListValues.Select((x, i) => new { Value = x, Index = i }).ToList().ForEach(element =>
{
//element.Index
//element.Value
});