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; }
}
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
23
你可以像这样写你的循环:
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
添加以下struct和extension方法之后 .
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);
}
}
16
只需添加自己的索引 . 把事情简单化 .
int i = 0;
foreach (var item in Collection)
{
item.index = i;
++i;
}
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]});
}
}
我认为并不总是适用,但往往足以值得一提 .
无论如何,关键是有时候你的逻辑中已经存在一个非显而易见的解决方案......
2
字面答案 - 警告,性能可能不如仅使用 int 来跟踪索引一样好 . 至少它比使用 IndexOf 更好 .
// 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() ,因为它's 2014 already, and we want to make good use of those multiple cores to speed things up. Further, for '顺序'LINQ,you only get a ForEach() extension method on List<T> and Array ...并且不清楚使用它比做一个简单的 foreach 更好,因为你仍然在运行单线程以获得更好的语法 .
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);
57
Why foreach ?!
最简单的方法是使用 for 而不是foreach if you are using List .
for(int i = 0 ; i < myList.Count ; i++)
{
// Do Something...
}
或者如果你想使用foreach:
foreach (string m in myList)
{
// Do something...
}
//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
}
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;
}
2
它只适用于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();
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 (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);
}
interface IIndexedEnumerable<T> : IEnumerable<T>
{
//Not index, because sometimes source IEnumerables are transient
public long IterationNumber { get; }
}
30 回答
我不确定你是根据问题对索引信息做了什么 . 但是,在C#中,您通常可以调整IEnumerable.Select方法以获取您想要的索引 . 例如,我可能会使用类似的东西来判断值是奇数还是偶数 .
这将根据名称为您提供列表中的项目是奇数(1)还是偶数(0)的字典 .
可以做这样的事情:
为了兴趣,Phil Haack在Razor模板化代表的背景下写了一个这样的例子(http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx)
实际上,他编写了一个扩展方法,该方法将迭代包装在“IteratedItem”类(见下文)中,允许在迭代期间访问索引和元素 .
但是,如果您在非Razor环境中执行单个操作(即可以作为lambda提供的操作),这将是正常的,但它不会成为非Razor上下文中for / foreach语法的可靠替代品 . .
我就是这样做的,它的简洁/简洁很好,但是如果你在循环体_407610中做了很多,那么它会变得很快 .
你可以像这样写你的循环:
添加以下struct和extension方法之后 .
struct和extension方法封装了Enumerable.Select功能 .
只需添加自己的索引 . 把事情简单化 .
我刚刚遇到这个问题,但在我的案例中考虑问题给出了最好的解决方案,与预期的解决方案无关 .
这可能是一个很常见的情况,基本上,我正在从一个源列表中读取并在目标列表中基于它们创建对象,但是,我必须首先检查源项是否有效并且想要返回任何行错误 . 乍一看,我想将索引放入Current属性对象的枚举器中,但是,当我复制这些元素时,我隐含地知道当前目标的当前索引 . 显然它取决于你的目标对象,但对我来说它是一个List,很可能它会实现ICollection .
即
我认为并不总是适用,但往往足以值得一提 .
无论如何,关键是有时候你的逻辑中已经存在一个非显而易见的解决方案......
字面答案 - 警告,性能可能不如仅使用
int
来跟踪索引一样好 . 至少它比使用IndexOf
更好 .您只需要使用Select的索引重载来使用知道索引的匿名对象来包装集合中的每个项目 . 这可以针对实现IEnumerable的任何事情来完成 .
您可以将原始枚举器包含在包含索引信息的另一个枚举器中 .
这是
ForEachHelper
类的代码 .使用计数器变量没有错 . 实际上,无论您使用
for
,foreach
while
还是do
,都必须在某处声明并递增计数器变量 .如果你不确定你是否有一个适当索引的集合,请使用这个习惯用法:
如果你知道你的可索引集合是O(1)用于索引访问(它将用于
Array
,可能用于List<T>
(文档没有说)),但是不一定用于其他类型(例如LinkedList
),否则使用这个):永远不需要'manually'通过调用
MoveNext()
来操作IEnumerator
并查询Current
-foreach
正在为您节省特别麻烦...如果您需要跳过项目,只需在循环体中使用continue
即可 .而且为了完整性,取决于你在索引中做了什么(上面的结构提供了很大的灵活性),你可以使用Parallel LINQ:
我们使用上面的
AsParallel()
,因为它's 2014 already, and we want to make good use of those multiple cores to speed things up. Further, for '顺序'LINQ,you only get a ForEach() extension method on List<T> and Array ...并且不清楚使用它比做一个简单的foreach
更好,因为你仍然在运行单线程以获得更好的语法 .我在LINQPad Build 了这个:
你也可以使用
string.join
:Why foreach ?!
最简单的方法是使用 for 而不是foreach if you are using List .
或者如果你想使用foreach:
你可以用它来表示每个循环的khow索引:
我不同意评论,在大多数情况下,
for
循环是更好的选择 .foreach
是一个有用的构造,在所有情况下都不能被for
循环替换 .例如,如果你有一个 DataReader 并循环遍历所有记录使用
foreach
它会自动调用 Dispose 方法并关闭阅读器(然后可以自动关闭连接) . 因此,即使您忘记关闭阅读器,也可以更安全,因为它可以防止连接泄漏 .(当然总是关闭读者是好的做法,但是如果你不这样做,编译器就不会 grab 它 - 你不能保证你已经关闭了所有的读者,但是你可以更有可能通过获取它来避免泄漏连接习惯使用foreach . )
可能存在
Dispose
方法的隐式调用的其他示例 .我不认为这应该是非常有效的,但它的工作原理:
foreach
用于迭代实现IEnumerable的集合 . 它通过在集合上调用GetEnumerator来执行此操作,该集合将返回Enumerator .此枚举器具有方法和属性:
MoveNext()
当前
Current
返回Enumerator当前所在的对象,MoveNext
将Current
更新为下一个对象 .显然,索引的概念对于枚举的概念是陌生的,并且不能完成 .
因此,大多数集合都可以使用索引器和for循环结构遍历 .
与使用局部变量跟踪索引相比,我更倾向于在这种情况下使用for循环 .
除非您的集合可以通过某种方法返回对象的索引,否则唯一的方法是使用类似示例的计数器 .
但是,在使用索引时,问题的唯一合理答案是使用for循环 . 其他任何东西都会引入代码复杂性,更不用说时间和空间的复杂性 .
使用@FlySwat的答案,我提出了这个解决方案:
您使用
GetEnumerator
获取枚举器,然后使用for
循环进行循环 . 然而,诀窍是使循环的条件listEnumerator.MoveNext() == true
.由于枚举器的
MoveNext
方法如果存在下一个元素并且可以访问它,则返回true,因此当我们用尽元素进行迭代时,使循环条件使循环停止 .最后,C#7有一个很好的语法来获取
foreach
循环中的索引(即元组):需要一点扩展方法:
使用LINQ,C#7和
System.ValueTuple
NuGet包,您可以这样做:您可以使用常规
foreach
构造,并且能够直接访问值和索引,而不是作为对象的成员,并且仅将两个字段保留在循环的范围内 . 出于这些原因,我相信如果您能够使用C#7和System.ValueTuple
,这是最佳解决方案 .这是我刚刚提出的解决这个问题的解决方案
Original code:
Updated code
Extension Method:
它只适用于List而不是任何IEnumerable,但在LINQ中有这样的:
@Jonathan我没有说这是一个很好的答案,我只是说它只是表明它可以做他所要求的:)
@Graphain我不认为它会很快 - 我不完全确定它是如何工作的,它可以在每次重复整个列表中找到匹配的对象,这将是比较的确认 .
也就是说,List可能会保留每个对象的索引以及计数 .
乔纳森似乎有更好的主意,如果他会详细说明的话?
最好只计算你在foreach中所处的位置,更简单,更具适应性 .
C#7最终为我们提供了一种优雅的方式:
我对这个问题的解决方案是扩展方法
WithIndex()
,http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
Use it like
如果集合是列表,则可以使用List.IndexOf,如下所示:
我不相信有一种方法可以获得foreach循环的当前迭代的值 . 算上自己,似乎是最好的方式 .
请问,为什么你想知道?
看起来你最喜欢做三件事之一:
1)从集合中获取对象,但在这种情况下,您已经拥有它 .
2)计算对象以供以后后处理...集合具有您可以使用的Count属性 .
3)根据循环中的顺序在对象上设置属性...虽然您可以在将对象添加到集合时轻松设置该属性 .
这样的事怎么样?请注意,如果myEnumerable为空,myDelimitedString可能为null .
这适用于支持
IList
的集合 .主要答案是:
“显然,指数的概念对于枚举的概念来说是陌生的,而且无法完成 . ”
虽然目前的C#版本也是如此,但这不是概念上的限制 .
MS创建新的C#语言功能可以解决这个问题,同时支持新的Interface IIndexedEnumerable
如果foreach传递了一个IEnumerable并且无法解析一个IIndexedEnumerable,但是它被问到var index,然后C#编译器可以使用IndexedEnumerable对象包装源,该对象添加了用于跟踪索引的代码 .
为什么:
Foreach看起来更好,在业务应用程序中很少是性能瓶颈
Foreach可以更有效地记忆 . 拥有一系列功能,而不是在每一步都转换为新的集合 . 如果CPU缓存故障较少且GC.Collects较少,谁会关心它是否会使用更多的CPU周期
要求编码人员添加索引跟踪代码,破坏美感
它很容易实现(感谢MS)并且向后兼容
虽然这里的大多数人都不是MS,但这是一个正确的答案,你可以游说MS添加这样的功能 . 您可以使用extension function and use tuples构建自己的迭代器,但MS可以使用语法糖来避免扩展函数
最好使用像这样的关键字
continue
安全构造Ian Mercer在Phil Haack's blog上发布了类似的解决方案:
这将通过使用this overload of Linq's Select获取项目(
item.value
)及其索引(item.i
):new { i, value }
正在创建一个新的anonymous object .