在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++;
}
当前回答
我想更理论化地讨论这个问题(因为它已经有了足够多的实际答案)
.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}");
});
这也避免了在其他答案中看到的任何不必要的分配。
其他回答
像这样的怎么样?注意,如果myEnumerable为空,myDelimitedString可能为空。
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;
}
我只是遇到了这个问题,但在我的情况下思考这个问题给出了最好的解决方案,与预期的解决方案无关。
It could be quite a common case, basically, I'm reading from one source list and creating objects based on them in a destination list, however, I have to check whether the source items are valid first and want to return the row of any error. At first-glance, I want to get the index into the enumerator of the object at the Current property, however, as I am copying these elements, I implicitly know the current index anyway from the current destination. Obviously it depends on your destination object, but for me it was a List, and most likely it will implement ICollection.
i.e.
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]});
}
}
我认为,虽然不总是适用,但常常足以值得一提。
不管怎样,关键是有时在你的逻辑中已经有了一个不明显的解决方案……
这里是这个问题的另一个解决方案,重点是使语法尽可能接近标准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循环,但这不是这个用例中的主要问题。
我不认为这应该很有效,但它确实有效:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
我不同意在大多数情况下使用for循环是更好的选择的说法。
Foreach是一个有用的构造,在所有情况下都不能被for循环所取代。
例如,如果您有一个DataReader,并使用foreach循环遍历所有记录,它会自动调用Dispose方法并关闭阅读器(然后自动关闭连接)。因此,这是更安全的,因为它可以防止连接泄漏,即使您忘记关闭读取器。
(当然,总是关闭读取器是很好的做法,但如果你不这样做,编译器就不会捕捉到它——你不能保证你已经关闭了所有的读取器,但你可以通过养成使用foreach的习惯,使它更有可能不会泄漏连接。)
对于Dispose方法的隐式调用,可能还有其他有用的例子。