在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++;
}
当前回答
出于兴趣,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提供的操作),这在非razor环境中不会是for/foreach语法的可靠替代。
其他回答
出于兴趣,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提供的操作),这在非razor环境中不会是for/foreach语法的可靠替代。
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
这适用于支持IList的集合。
除非你的集合可以通过某种方法返回对象的索引,否则唯一的方法就是使用像你的例子中那样的计数器。
然而,当使用索引时,唯一合理的答案是使用for循环。其他任何事情都会带来代码的复杂性,更不用说时间和空间的复杂性了。
使用LINQ, c# 7和系统。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。
我想更理论化地讨论这个问题(因为它已经有了足够多的实际答案)
.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();
}
as you might have noticed, the IEnumerator interface doesn't "know" what an index is, it just knows what element it's currently pointing to, and how to move to the next one. now here is the trick: foreach considers every input collection an IEnumerable, even if it is a more concrete implementation like an IList<T> (which inherits from IEnumerable), it will only see the abstract interface IEnumerable. what foreach is actually doing, is calling GetEnumerator on the collection, and calling MoveNext until it returns false. so here is the problem, you want to define a concrete concept "Indices" on an abstract concept "Enumerables", the built in foreach construct doesn't give you that option, so your only way is to define it yourself, either by what you are doing originally (creating a counter manually) or just use an implementation of IEnumerator that recognizes indices AND implement a foreach construct that recognizes that custom implementation.
就我个人而言,我会创建一个这样的扩展方法
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++;
}
}
}
像这样使用它
var x = new List<string>() { "hello", "world" };
x.FE((ind, ele) =>
{
Console.WriteLine($"{ind}: {ele}");
});
这也避免了在其他答案中看到的任何不必要的分配。