在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++;
}
当前回答
我对这个问题的解决方案是一个扩展方法WithIndex(),
http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs
像这样使用它
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));
其他回答
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 (item.value)和它的索引(item.i):
函数[在Select内部]的第二个参数表示源元素的索引。
新的{i, value}正在创建一个新的匿名对象。
如果你使用c# 7.0或更高版本,可以使用ValueTuple来避免堆分配:
foreach (var item in Model.Select((value, i) => ( value, i )))
{
var value = item.value;
var index = item.i;
}
你也可以删除这个项目。通过使用自动解构:
foreach (var (value, i) in Model.Select((value, i) => ( value, i )))
{
// Access `value` and `i` directly here.
}
这里是这个问题的另一个解决方案,重点是使语法尽可能接近标准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循环,但这不是这个用例中的主要问题。
这样你就可以使用LINQ来使用索引和值:
ListValues.Select((x, i) => new { Value = x, Index = i }).ToList().ForEach(element =>
{
// element.Index
// element.Value
});
foreach用于迭代实现IEnumerable的集合。它通过在集合上调用GetEnumerator来实现这一点,该集合将返回一个Enumerator。
这个枚举器有一个方法和一个属性:
MoveNext () 当前的
Current返回Enumerator当前所在的对象,MoveNext将Current更新为下一个对象。
索引的概念与枚举的概念是不同的,不能这样做。
因此,大多数集合都可以使用索引器和for循环构造来遍历。
在这种情况下,与使用局部变量跟踪索引相比,我更喜欢使用for循环。
我不认为这应该很有效,但它确实有效:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}