在C#中是否有一些我没有遇到过的稀有语言构造(例如我最近学到的一些,有些是关于Stack Overflow的)来获取代表foreach循环当前迭代的值?
例如,我目前根据情况执行以下操作:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
#1楼
我不同意在大多数情况下for
循环是更好的选择的评论。
foreach
是一个有用的构造,在所有情况下都不能被for
循环替换。
例如,如果您有一个DataReader并使用foreach
所有记录,它将自动调用Dispose方法并关闭阅读器(然后可以自动关闭连接)。 因此,这样做更安全,因为即使您忘记关闭阅读器,它也可以防止连接泄漏。
(确保始终关闭读者是一个好习惯,但是如果您不这样做,则编译器不会捕获它-您不能保证已经关闭了所有读者,但是可以通过以下方式使自己更有可能不会泄漏连接:养成使用foreach的习惯。)
可能还有其他示例,例如, Dispose
方法的隐式调用很有用。
#2楼
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
):
函数[inside Select]中的第二个参数表示源元素的索引。
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.
通过使用自动解构:
<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
<li id="item_@i">@value</li>
}
</ol>
#3楼
我认为这应该不是很有效,但是它可以工作:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
#4楼
使用@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
获得枚举GetEnumerator
,然后使用for
循环for
循环。 但是,技巧是使循环的条件为listEnumerator.MoveNext() == true
。
由于枚举器的MoveNext
方法在存在下一个元素时可以返回true,并且可以访问它,因此当我们用尽元素进行迭代时,循环条件使循环停止。
#5楼
我在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);
#6楼
这是我刚想出的解决方案
原始代码:
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;
}
#7楼
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
这将适用于支持IList
集合。
#8楼
使用计数器变量没有错。 实际上,无论您使用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
的循环体。
只是为了完整起见,这取决于您对索引所做的操作 (上述构造提供了足够的灵活性),您可以使用Parallel LINQ:
// 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
更好,因为您仍在运行单-用于更丑陋的语法。
#9楼
如果集合是列表,则可以使用List.IndexOf,如下所示:
foreach (Object o in collection)
{
// ...
@collection.IndexOf(o)
}
#10楼
您可以将原始的枚举数与另一个包含索引信息的枚举数包装在一起。
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;
}
}
}
#11楼
更好地使用关键字continue
安全构建
int i=-1;
foreach (Object o in collection)
{
++i;
//...
continue; //<--- safe to call, index will be increased
//...
}
#12楼
这就是我的操作方式,这很简单/简洁,但是如果您在循环主体obj.Value
做很多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);
...
}
#13楼
我对这个问题的解决方案是一个扩展方法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));
#14楼
为什么要foreach?
如果使用List ,最简单的方法是使用for而不是foreach:
for (int i = 0 ; i < myList.Count ; i++)
{
// Do something...
}
或者,如果您要使用foreach:
foreach (string m in myList)
{
// Do something...
}
您可以使用它来了解每个循环的索引:
myList.indexOf(m)
#15楼
您可以这样编写循环:
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
添加以下结构和扩展方法之后。
struct和扩展方法封装了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楼
最后,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));
#17楼
主要答案指出:
“显然,索引的概念与枚举的概念是陌生的,无法做到。”
尽管当前的C#版本是这样,但这不是概念上的限制。
MS创建新的C#语言功能可以解决此问题,并支持新的Interface 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; }
}
为什么:
- Foreach看起来更好,并且在业务应用程序中很少出现性能瓶颈
- Foreach在内存上可以更有效。 具有功能管道,而不是在每个步骤都转换为新集合。 谁在乎它是否使用更多的CPU周期,更少的CPU缓存故障和更少的GC。
- 要求编码器添加索引跟踪代码,破坏美观
- 它非常容易实现(感谢MS)并且向后兼容
虽然这里的大多数人都不是MS,但这是正确的答案,您可以游说MS来添加这样的功能。 您已经可以使用扩展功能构建自己的迭代器并使用元组 ,但是MS可以撒上语法糖以避免扩展功能
#18楼
这样的事情怎么样? 请注意,如果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;
}
#19楼
除非您的集合可以通过某种方法返回对象的索引,否则唯一的方法就是使用示例中的计数器。
但是,使用索引时,对该问题的唯一合理答案是使用for循环。 其他任何事情都会导致代码复杂性,更不用说时间和空间的复杂性了。
#20楼
我不相信有一种方法可以获取foreach循环当前迭代的值。 盘点自己,似乎是最好的方法。
请问,为什么您想知道?
似乎您大多数会想做以下三件事之一:
1)从集合中获取对象,但是在这种情况下,您已经拥有它。
2)计算对象以便以后进行后期处理...这些集合具有Count属性,您可以利用它。
3)根据对象在循环中的顺序为其设置属性...尽管您可以轻松地在将对象添加到集合时进行设置。
#21楼
foreach
用于迭代实现IEnumerable集合。 它通过在集合上调用GetEnumerator来执行此操作,该集合将返回Enumerator 。
此枚举器具有一个方法和一个属性:
- MoveNext()
- 当前
Current
返回Enumerator Current
的对象, MoveNext
Current
更新为下一个对象。
索引的概念与枚举的概念是陌生的,无法做到。
因此,大多数集合都可以使用索引器和for循环结构遍历。
与使用局部变量跟踪索引相比,在这种情况下,我非常喜欢使用for循环。
#22楼
可以做这样的事情:
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));
}
}
#23楼
它仅适用于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();
@乔纳森我没有说这是一个很好的答案,我只是说这只是表明有可能做到他所要求的:)
@Graphain我不希望它很快-我不完全确定它是如何工作的,它每次可以在整个列表中重复以找到匹配的对象,这将是一个比较对象。
也就是说,List可以保留每个对象的索引以及计数。
乔纳森(Jonathan)是否有一个更好的主意?
最好只统计一下自己在哪个方面的优势,更简单,更适应。
#24楼
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}");
}
}
}
#25楼
使用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
那么这是最好的解决方案。
#26楼
只需添加您自己的索引。 把事情简单化。
int i = 0;
foreach (var item in Collection)
{
item.index = i;
++i;
}
#27楼
我只是遇到了这个问题,但是在我的案例中考虑问题会提供最佳解决方案,而与预期解决方案无关。
这可能是一个很常见的情况,基本上,我正在从一个源列表中读取并在目标列表中基于它们创建对象,但是,我必须先检查源项是否有效,并想返回任何行错误。 乍一看,我想将索引添加到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]});
}
}
我认为,这并不总是适用,但经常值得一提。
无论如何,关键是有时您的逻辑中已经存在一个非显而易见的解决方案...
#28楼
出于兴趣,Phil Haack只是在Razor模板化委托( 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提供的操作),但在非Razor上下文中,它不会完全替代for / foreach语法。
#29楼
我不确定您要根据问题使用索引信息做什么。 但是,在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)。
#30楼
文字答案-警告,性能可能不如仅使用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);
}