Làm thế nào để bạn có được chỉ số của lần lặp hiện tại của một vòng lặp foreach?


939

Is there some rare language construct I haven't encountered (like the few I've learned recently, some on Stack Overflow) in C# to get a value representing the current iteration of a foreach loop?

For instance, I currently do something like this depending on the circumstances:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

1
foreach casting retrieval is generally not going to me more optimized than just using index-based access on a collection, though in many cases it will be equal. The purpose of foreach is to make your code readable, but it (usually) adds a layer of indirection, which isn't free.
Brian

8
I would say the primary purpose of foreach is to provide a common iteration mechanism for all collections regardless of whether they are indexable (List) or not (Dictionary).
Brian Gideon

2
Xin chào Brian Gideon - chắc chắn đồng ý (điều này đã được một vài năm trước và tôi ít kinh nghiệm hơn vào thời điểm đó). Tuy nhiên, mặc dù Dictionarykhông thể lập chỉ mục, nhưng việc lặp lại Dictionarysẽ đi qua nó theo một thứ tự cụ thể (tức là một Enumerator có thể lập chỉ mục bởi thực tế là nó mang lại các phần tử theo tuần tự). Theo nghĩa này, chúng ta có thể nói rằng chúng ta không tìm kiếm chỉ mục trong bộ sưu tập, mà là chỉ số của phần tử liệt kê hiện tại trong bảng liệt kê (nghĩa là chúng ta đang ở phần tử liệt kê thứ nhất hay thứ năm hoặc thứ năm).
Matt Mitchell

4
foreach cũng cho phép trình biên dịch bỏ qua giới hạn kiểm tra từng truy cập mảng trong mã được biên dịch. Sử dụng với một chỉ mục sẽ làm cho thời gian chạy kiểm tra xem truy cập chỉ mục của bạn có an toàn không.
IvoTops

1
Nhưng đó là sai. Nếu bạn không thay đổi biến lặp của vòng lặp for trong vòng lặp, trình biên dịch sẽ biết giới hạn của nó là gì và không cần kiểm tra lại chúng. Đây là một trường hợp phổ biến mà bất kỳ trình biên dịch phong nha sẽ thực hiện nó.
Jim Balter

Câu trả lời:


552

Đây foreachlà để lặp đi lặp lại trên các bộ sưu tập thực hiện IEnumerable. Nó thực hiện điều này bằng cách gọi GetEnumeratorvào bộ sưu tập, sẽ trả về một Enumerator.

Trình liệt kê này có một phương thức và thuộc tính:

  • MoveNext ()
  • Hiện hành

Currenttrả về đối tượng mà Enumerator hiện đang bật, MoveNextcập nhật Currentcho đối tượng tiếp theo.

Khái niệm về một chỉ số là xa lạ với khái niệm liệt kê, và không thể được thực hiện.

Do đó, hầu hết các bộ sưu tập có thể được duyệt qua bộ chỉ mục và cấu trúc vòng lặp for.

Tôi rất thích sử dụng vòng lặp for trong tình huống này so với theo dõi chỉ mục bằng một biến cục bộ.


165
"Obviously, the concept of an index is foreign to the concept of enumeration, and cannot be done." -- This is nonsense, as the answers by David B and bcahill make clear. An index is an enumeration over a range, and there's no reason one cannot enumerate two things in parallel ... that's exactly what the indexing form of Enumerable.Select does.
Jim Balter

11
Basic code example: for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock

22
@JimBalter: That's the first link I read. I don't see how it supports your position. I also don't hurl ad-homs and divisive words at people when replying. I cite as an example your use of "nonsense", "extraordinary confusion", "completely wrong and confused", "I got 37 upvotes", etc. (I think your ego is a bit on the line here.) Instead, I'd like to politely ask that you not browbeat others with your 'argumentum ad verecundiam' and I'd like to instead encourage you to think of ways to support people here on StackOverflow. I'd say Jon Skeet is a model citizen here in that regard.
Pretzel

11
Pretzel: Your use of Jon Skeet as a model citizen is mistaken, as he will browbeat (and downvote) someone who doesn't agree with him, as I've experienced. Jim Balter: Some of your comments were overly aggressive and you could have presented your points with less anger and more education.
Suncat2000

7
@Pretzel Jim's point is that as long as you can map the elements to a sequence of integers, you can index it. That the class itself does not store an index is beside the point. Additionally, that the linked list does have an order only strengthens Jim's position. All you need to do is number each element in order. Specifically, you can achieve this by incrementing a count while you iterate, or you could generate a list of integers with the same length and then zip them (as in Python's zip function).
jpmc26

666

Ian Mercer posted a similar solution as this on Phil Haack's blog:

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

Điều này giúp bạn có mục ( item.value) và chỉ mục của nó ( item.i) bằng cách sử dụng quá tải LINQ nàySelect :

tham số thứ hai của hàm [bên trong Chọn] biểu thị chỉ mục của phần tử nguồn.

Việc new { i, value }tạo ra một đối tượng ẩn danh mới .

Có thể tránh phân bổ heap bằng cách sử dụng ValueTuplenếu bạn đang sử dụng C # 7.0 trở lên:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

Bạn cũng có thể loại bỏ item.bằng cách sử dụng tự động hủy:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

9
That solution is nice for the case of a Razor template where the neatness of the template is a non-trivial design concern and you are also wanting to use the index of every item that is enumerated. However, do bear in mind that the object allocation from 'wrapping' adds expense (in space and time) on top of the (unavoidable) incrementing of an integer.
David Bullock

6
@mjsr The documentation is here.
Thorkil Holm-Jacobsen

19
Sorry - that's clever, but is it really more readable than creating an index outside the foreach and incrementing it each loop?
jbyrd

13
Với các phiên bản C # sau này, do đó bạn cũng sử dụng bộ dữ liệu, vì vậy bạn sẽ có một cái gì đó như thế này: foreach (var (item, i) trong Model.Select ((v, i) => (v, i))) Cho phép bạn truy cập mục và chỉ mục (i) trực tiếp bên trong vòng lặp for với cấu trúc giải mã tuple.
Haukman

5
Ai đó có thể giải thích cho tôi tại sao đây là một câu trả lời tốt (hơn 450 upvote tại thời điểm viết)? Theo như tôi có thể thấy nó khó hiểu hơn là chỉ đơn giản là tăng một bộ đếm, do đó ít được bảo trì hơn, nó sử dụng nhiều bộ nhớ hơn và có lẽ chậm hơn. Tui bỏ lỡ điều gì vậy?
Giàu N

182

Cuối cùng, C # 7 có một cú pháp hợp lý để lấy chỉ mục bên trong một foreachvòng lặp (ví dụ: bộ dữ liệu):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Một phương pháp mở rộng sẽ là cần thiết:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

8
Câu trả lời này bị đánh giá thấp, có bộ dữ liệu sạch hơn nhiều
Todd

7
Được sửa đổi để xử lý các bộ sưu tập null:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad 25/11/18

Đẹp một. Tôi thực sự thích giải pháp này nhất.
FranzHuber23

Đây là câu trả lời hay nhất
w0ns88

2
Có thể hữu ích khi gọi phương thức này Enumerateddễ nhận biết hơn đối với những người được sử dụng sang các ngôn ngữ khác (và có lẽ cũng trao đổi thứ tự của các tham số tuple). WithIndexDù sao thì điều đó cũng không rõ ràng.
FernAndr

114

Could do something like this:

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));
    }
}

12
That doesn't "really" solve the problem. The idea is good but it doesn't avoid the additional counting variable
Atmocreations

This does not work if we have a return statement within our for loop, if you change "ForEachWithIndex" for that, then it is not generic, better off writing a regular for loop
Shankar Raju

Your ForEachWithIndex call is equivalent to this one using the Linq Select that takes a string and an index: values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

I disagree with comments that a for loop is a better choice in most cases.

foreach is a useful construct, and not replaceble by a for loop in all circumstances.

For example, if you have a DataReader and loop through all records using a foreach it automatically calls the Dispose method and closes the reader (which can then close the connection automatically). This is therefore safer as it prevents connection leaks even if you forget to close the reader.

(Sure it is good practise to always close readers but the compiler is not going to catch it if you don't - you can't guarantee you have closed all readers but you can make it more likely you won't leak connections by getting in the habit of using foreach.)

There may be other examples of the implicit call of the Dispose method being useful.


2
Thanks for pointing this out. Rather subtle. You can get more information at pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator and msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx.
Mark Meuer

+1. I was writing more in detail about how foreach is different from for (and closer to while) on Programmers.SE.
Arseni Mourzenko

64

Literal Answer -- warning, performance may not be as good as just using an int to track the index. At least it is better than using IndexOf.

You just need to use the indexing overload of Select to wrap each item in the collection with an anonymous object that knows the index. This can be done against anything that implements 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);
}

3
The only reason to use OfType<T>() instead of Cast<T>() is if some items in the enumeration might fail an explicit cast. For object, this will never be the case.
dahlbyk

13
Sure, except for the other reason to use OfType instead of Cast - which is that I never use Cast.
Amy B

36

Using LINQ, C# 7, and the System.ValueTuple NuGet package, you can do this:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

You can use the regular foreach construct and be able to access the value and index directly, not as a member of an object, and keeps both fields only in the scope of the loop. For these reasons, I believe this is the best solution if you are able to use C# 7 and System.ValueTuple.


How is this different from user1414213562's answer?
Edward Brey

@EdwardBrey I suppose it isn't. This question has a lot of answers, I probably just missed it or didn't notice what it was doing because he separated some of the logic out into an extension method.
Pavel

1
This is different because .Select is built-in from LINQ. You don't have to write your own function? You will need to have VS install "System.ValueTuple" though.
Anton

33

Using @FlySwat's answer, I came up with this solution:

//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
}

You get the enumerator using GetEnumerator and then you loop using a for loop. However, the trick is to make the loop's condition listEnumerator.MoveNext() == true.

Since the MoveNext method of an enumerator returns true if there is a next element and it can be accessed, making that the loop condition makes the loop stop when we run out of elements to iterate over.


10
There's no need to compare listEnumerator.MoveNext() == true. That's like asking the computer if true == true? :) Just say if listEnumerator.MoveNext() { }
Zesty

9
@Zesty, you're absolutely correct. I felt that it's more readable to add it in this case especially for people who aren't used to entering anything other than i < blahSize as a condition.
Gezim

@Gezim I don't mind predicates here, but I get your point in that this doesn't look like a predicate.
John Dvorak

1
You should dispose the enumerator.
Antonín Lejsek

@AntonínLejsek The enumerator does not implement IDisposable.
Edward Brey

27

There's nothing wrong with using a counter variable. In fact, whether you use for, foreach while or do, a counter variable must somewhere be declared and incremented.

So use this idiom if you're not sure if you have a suitably-indexed collection:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

Else use this one if you know that your indexable collection is O(1) for index access (which it will be for Array and probably for List<T> (the documentation doesn't say), but not necessarily for other types (such as 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'
}

It should never be necessary to 'manually' operate the IEnumerator by invoking MoveNext() and interrogating Current - foreach is saving you that particular bother ... if you need to skip items, just use a continue in the body of the loop.

And just for completeness, depending on what you were doing with your index (the above constructs offer plenty of flexibility), you might use 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));

We use AsParallel() above, because it's 2014 already, and we want to make good use of those multiple cores to speed things up. Further, for 'sequential' LINQ, you only get a ForEach() extension method on List<T> and Array ... and it's not clear that using it is any better than doing a simple foreach, since you are still running single-threaded for uglier syntax.


26

You could wrap the original enumerator with another that does contain the index information.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Here is the code for the ForEachHelper class.

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;
        }            
    }
}

This won't actually return the index of the item. Instead, it will return the index inside the enumerated list, which may only be a sublist of the list, thereby giving you accurate data only when the sublist and the list are of equal size. Basically, any time the collection has objects in it not in the requested type your index will be incorrect.
Lucas B

7
@Lucas: No, but it will return the index of the current foreach iteration. That was the question.
Brian Gideon

20

Here's a solution I just came up with for this problem

Original code:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Updated code

enumerable.ForEach((item, index) => blah(item, index));

Extension Method:

    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;
    }

16

Just add your own index. Keep it simple.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

15

It's only going to work for a List and not any IEnumerable, but in LINQ there's this:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@Jonathan I didn't say it was a great answer, I just said it was just showing it was possible to do what he asked :)

@Graphain Tôi không mong đợi nó sẽ nhanh - Tôi không hoàn toàn chắc chắn về cách thức hoạt động của nó, nó có thể nhắc lại trong toàn bộ danh sách mỗi lần để tìm một đối tượng phù hợp, sẽ là một so sánh tuyệt vời.

Điều đó nói rằng, Danh sách có thể giữ một chỉ mục của từng đối tượng cùng với số lượng.

Jonathan dường như có một ý tưởng tốt hơn, nếu anh ta sẽ giải thích?

Mặc dù vậy, sẽ tốt hơn nếu chỉ giữ một số lượng nơi bạn sẽ đến trong buổi thuyết trình, đơn giản hơn và dễ thích nghi hơn.


5
Không chắc chắn về việc downvote nặng. Chắc chắn hiệu suất làm cho điều này bị cấm nhưng bạn đã trả lời câu hỏi!
Matt Mitchell

4
Một vấn đề khác với điều này là nó chỉ hoạt động nếu các mục trong danh sách là duy nhất.
CodeInChaos

14

C # 7 cuối cùng cũng cho chúng ta một cách thanh lịch để làm điều này:

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}");
        }
    }
}

Có, và MS nên mở rộng CLR / BCL để biến loại này thành bản địa.
Todd

14

Tại sao lại thuyết minh?!

Cách đơn giản nhất là sử dụng cho thay vì foreach nếu bạn đang sử dụng Danh sách :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

Hoặc nếu bạn muốn sử dụng foreach:

foreach (string m in myList)
{
     // Do something...
}

Bạn có thể sử dụng điều này để biết chỉ số của mỗi vòng lặp:

myList.indexOf(m)

8
Giải pháp indexOf không hợp lệ cho danh sách có trùng lặp và cũng rất chậm.
tymtam

2
Vấn đề cần tránh là nơi bạn duyệt qua IEnumerable nhiều lần, ví dụ để lấy số lượng vật phẩm và sau đó từng vật phẩm. Điều này có ý nghĩa khi IEnumerable là kết quả của một truy vấn cơ sở dữ liệu chẳng hạn.
David Clarke

2
myList.IndexOf () là O (n), vì vậy vòng lặp của bạn sẽ là O (n ^ 2).
Patrick Beard

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Điều này sẽ làm việc cho các bộ sưu tập hỗ trợ IList.


68
Hai vấn đề: 1) Điều này là O(n^2)do trong hầu hết các triển khai IndexOfO(n). 2) Điều này không thành công nếu có các mục trùng lặp trong danh sách.
CodeInChaos

15
Lưu ý: O (n ^ 2) có nghĩa là điều này có thể chậm một cách thảm hại cho một bộ sưu tập lớn.
O'Rooney

Phát minh tuyệt vời để sử dụng phương pháp IndexOf! Đây là những gì tôi đang tìm kiếm để có được chỉ số (số) trong vòng lặp foreach! Big Thx
Mitja Bonca

19
Chúa ơi, tôi hy vọng bạn không sử dụng nó! :( Nó KHÔNG sử dụng biến mà bạn không muốn tạo - thực tế, nó sẽ tạo ra n + 1 int vì hàm đó phải tạo một biến để trả về, và quá trình tìm kiếm indexof chậm hơn rất nhiều so với một hoạt động gia tăng số nguyên trong mỗi bước. Tại sao mọi người sẽ không bỏ phiếu trả lời này?
canahari

13
Đừng sử dụng câu trả lời này, tôi thấy sự thật phũ phàng được đề cập trong một trong những bình luận. "Điều này không thành công nếu có các mục trùng lặp trong danh sách." !!!
Bruce

9

This is how I do it, which is nice for its simplicity/brevity, but if you're doing a lot in the loop body obj.Value, it is going to get old pretty fast.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

8

This answer: lobby the C# language team for direct language support.

The leading answer states:

Obviously, the concept of an index is foreign to the concept of enumeration, and cannot be done.

While this is true of the current C# language version (2020), this is not a conceptual CLR/Language limit, it can be done.

The Microsoft C# language development team could create a new C# language feature, by adding support for a new 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);
}

Nếu foreach ()được sử dụng và with var indexcó mặt, thì trình biên dịch dự kiến ​​bộ sưu tập mục sẽ khai báo IIndexedEnumerablegiao diện. Nếu giao diện vắng mặt, trình biên dịch có thể polyfill bọc nguồn với một đối tượng IndexedEnumerable, bổ sung mã để theo dõi chỉ mục.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

Sau đó, CLR có thể được cập nhật để có theo dõi chỉ mục nội bộ, chỉ được sử dụng nếu with từ khóa được chỉ định và nguồn không trực tiếp thực hiệnIIndexedEnumerable

Tại sao:

  • Foreach trông đẹp hơn và trong các ứng dụng kinh doanh, các vòng lặp foreach hiếm khi là một nút cổ chai hiệu năng
  • Foreach có thể hiệu quả hơn trên bộ nhớ. Có một hệ thống các chức năng thay vì chuyển đổi sang các bộ sưu tập mới ở mỗi bước. Ai quan tâm nếu nó sử dụng một vài chu kỳ CPU nữa khi có ít lỗi bộ đệm CPU hơn và ít bộ sưu tập rác hơn?
  • Yêu cầu bộ mã hóa để thêm mã theo dõi chỉ mục, làm hỏng vẻ đẹp
  • Nó khá dễ thực hiện (vui lòng Microsoft) và tương thích ngược

Mặc dù hầu hết mọi người ở đây không phải là nhân viên của Microsoft, đây là một câu trả lời đúng, bạn có thể vận động Microsoft để thêm một tính năng như vậy. Bạn đã có thể xây dựng trình lặp của riêng mình với chức năng mở rộng và sử dụng bộ dữ liệu , nhưng Microsoft có thể rắc đường cú pháp để tránh chức năng mở rộng


Đợi đã, vậy tính năng ngôn ngữ này đã tồn tại chưa, hay nó được đề xuất cho tương lai?
Pavel

1
@Pavel Tôi cập nhật câu trả lời cho rõ ràng. Câu trả lời này được cung cấp để chống lại câu trả lời hàng đầu trong đó nêu rõ "Rõ ràng, khái niệm chỉ số là xa lạ với khái niệm liệt kê và không thể thực hiện được."
Todd

6

Nếu bộ sưu tập là một danh sách, bạn có thể sử dụng List.IndexOf, như trong:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

13
Và bây giờ thuật toán là O (n ^ 2) (nếu không nói là tệ hơn). Tôi sẽ suy nghĩ rất kỹ trước khi sử dụng nó. Đây cũng là một bản sao câu trả lời của
@crucible

1
@BradleyDotNET hoàn toàn chính xác, không sử dụng phiên bản này.
Oskar

1
Hãy cẩn thận với điều này! Nếu bạn có một mục trùng lặp trong danh sách của mình, nó sẽ có được vị trí của mục đầu tiên!
Sonhja

5

Tốt hơn nên sử dụng từ khóa continuexây dựng an toàn như thế này

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

5

Bạn có thể viết vòng lặp của bạn như thế này:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Sau khi thêm các cấu trúc và phương pháp mở rộng sau đây.

Phương thức struct và phần mở rộng đóng gói chức năng 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);
    }
}

3

Giải pháp của tôi cho vấn đề này là một phương pháp mở rộng WithIndex(),

http://code.google.com.vn/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

Sử dụng nó như

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));

Tôi sẽ sử dụng một structcặp (chỉ mục, mục).
CodeInChaos

3

Để thu hút sự chú ý, Phil Haack chỉ viết một ví dụ về điều này trong bối cảnh của một Đại biểu Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Thực tế, ông viết một phương thức mở rộng bao bọc phép lặp trong lớp "IteratedItem" (xem bên dưới) cho phép truy cập vào chỉ mục cũng như phần tử trong quá trình lặp.

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; }
}

Tuy nhiên, mặc dù điều này sẽ ổn trong môi trường không có Dao cạo nếu bạn đang thực hiện một thao tác duy nhất (nghĩa là có thể được cung cấp dưới dạng lambda), nó sẽ không thay thế vững chắc cú pháp for / foreach trong ngữ cảnh không dao cạo .


3

Tôi không nghĩ rằng nó sẽ khá hiệu quả, nhưng nó hoạt động:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

Tôi đã xây dựng điều này trong 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.

Bạn cũng có thể chỉ cần sử dụng string.join:

var joinResult = string.Join(",", listOfNames);

Bạn cũng có thể sử dụng chuỗi.join trong c # Ví dụ: var joResult = string.Join (",", listOfNames); '
Warren LaFrance

1
Ai đó có thể giải thích cho tôi những gì O (n * n) này không?
Axel

@Axel về cơ bản có nghĩa là các hoạt động cần thiết để tính kết quả tăng theo phương pháp bậc hai, tức là nếu có ncác mục thì các hoạt động đó n * n, hoặc được yêu cầu n. vi.wikipedia.org/wiki/ Kẻ
Richard Hansell

2

Tôi không tin có một cách để có được giá trị của lần lặp hiện tại của vòng lặp foreach. Đếm chính mình, dường như là cách tốt nhất.

Tôi có thể hỏi, tại sao bạn muốn biết?

Có vẻ như bạn hầu như sẽ làm một trong ba điều sau:

1) Lấy đối tượng từ bộ sưu tập, nhưng trong trường hợp này bạn đã có nó.

2) Đếm các đối tượng để xử lý bài sau này ... các bộ sưu tập có thuộc tính Đếm mà bạn có thể sử dụng.

3) Đặt thuộc tính trên đối tượng dựa trên thứ tự của nó trong vòng lặp ... mặc dù bạn có thể dễ dàng thiết lập điều đó khi bạn thêm đối tượng vào bộ sưu tập.


4) Trường hợp tôi đã đánh nhiều lần là một điều khác biệt phải được thực hiện ở lần đầu tiên hoặc lần cuối - nói một danh sách các đối tượng bạn sẽ in và bạn cần dấu phẩy giữa các mục nhưng không phải sau mục cuối cùng.
Loren Pechtel

2

Trừ khi bộ sưu tập của bạn có thể trả về chỉ mục của đối tượng thông qua một số phương thức, cách duy nhất là sử dụng bộ đếm như trong ví dụ của bạn.

Tuy nhiên, khi làm việc với các chỉ mục, câu trả lời hợp lý duy nhất cho vấn đề là sử dụng vòng lặp for. Bất cứ điều gì khác giới thiệu độ phức tạp của mã, chưa kể đến độ phức tạp về thời gian và không gian.


2

Còn những thứ như thế này thì sao? Lưu ý rằng myDelrictString có thể là null nếu myEnumerable trống.

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;
}

Nhiều vấn đề ở đây. A) thêm nẹp. B) chuỗi concat + = mỗi vòng lặp.
enorl76

2

Tôi chỉ gặp vấn đề này, nhưng suy nghĩ xung quanh vấn đề trong trường hợp của tôi đã đưa ra giải pháp tốt nhất, không liên quan đến giải pháp mong đợi.

Về cơ bản, đây có thể là một trường hợp phổ biến, tôi đang đọc từ một danh sách nguồn và tạo các đối tượng dựa trên chúng trong danh sách đích, tuy nhiên, tôi phải kiểm tra xem các mục nguồn có hợp lệ trước không và muốn trả về hàng bất kỳ lỗi. Thoạt nhìn, tôi muốn đưa chỉ mục vào trình liệt kê của đối tượng tại thuộc tính Hiện tại, tuy nhiên, khi tôi sao chép các phần tử này, tôi hoàn toàn biết chỉ mục hiện tại từ đích hiện tại. Rõ ràng nó phụ thuộc vào đối tượng đích của bạn, nhưng đối với tôi đó là Danh sách và rất có thể nó sẽ thực hiện 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]});
  }
}

Không phải lúc nào cũng áp dụng, nhưng thường đủ để được đề cập, tôi nghĩ.

Dù sao đi nữa, vấn đề là đôi khi có một giải pháp không rõ ràng đã có trong logic mà bạn có ...


2

Tôi không chắc chắn những gì bạn đã cố gắng làm với thông tin chỉ mục dựa trên câu hỏi. Tuy nhiên, trong C #, bạn thường có thể điều chỉnh phương thức IEnumerable.Select để lấy chỉ mục ra khỏi bất cứ thứ gì bạn muốn. Chẳng hạn, tôi có thể sử dụng một cái gì đó như thế này cho dù giá trị là số lẻ hay số chẵn.

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);

Điều này sẽ cung cấp cho bạn một từ điển theo tên cho dù mục đó là số lẻ (1) hay thậm chí (0) trong danh sách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.