在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++;
}
当前回答
字面答案——警告,性能可能不如仅使用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);
}
其他回答
这里是这个问题的另一个解决方案,重点是使语法尽可能接近标准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();
}
}
在非web环境中,你可以使用static来代替HttpContext.Current.Items。
这本质上是一个全局变量,因此不能嵌套多个WithIndex循环,但这不是这个用例中的主要问题。
可以这样做:
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));
}
}
使用@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,因此循环条件使循环在耗尽要遍历的元素时停止。
我不同意在大多数情况下使用for循环是更好的选择的说法。
Foreach是一个有用的构造,在所有情况下都不能被for循环所取代。
例如,如果您有一个DataReader,并使用foreach循环遍历所有记录,它会自动调用Dispose方法并关闭阅读器(然后自动关闭连接)。因此,这是更安全的,因为它可以防止连接泄漏,即使您忘记关闭读取器。
(当然,总是关闭读取器是很好的做法,但如果你不这样做,编译器就不会捕捉到它——你不能保证你已经关闭了所有的读取器,但你可以通过养成使用foreach的习惯,使它更有可能不会泄漏连接。)
对于Dispose方法的隐式调用,可能还有其他有用的例子。
foreach用于迭代实现IEnumerable的集合。它通过在集合上调用GetEnumerator来实现这一点,该集合将返回一个Enumerator。
这个枚举器有一个方法和一个属性:
MoveNext () 当前的
Current返回Enumerator当前所在的对象,MoveNext将Current更新为下一个对象。
索引的概念与枚举的概念是不同的,不能这样做。
因此,大多数集合都可以使用索引器和for循环构造来遍历。
在这种情况下,与使用局部变量跟踪索引相比,我更喜欢使用for循环。