2008-09-04 70 views
623

Có một số cấu trúc ngôn ngữ hiếm hoi mà tôi chưa từng gặp (như vài cái tôi đã học gần đây, một số trong Stack Overflow) trong C# để nhận giá trị đại diện cho vòng lặp hiện tại của vòng lặp foreach?Làm thế nào để bạn nhận được chỉ mục của vòng lặp hiện tại của vòng lặp foreach?

Ví dụ, tôi hiện đang làm một cái gì đó như thế này tùy theo hoàn cảnh:

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

foreach casting retrieval thường không giúp tôi tối ưu hóa nhiều hơn là chỉ sử dụng truy cập dựa trên chỉ mục trên một bộ sưu tập, mặc dù trong nhiều trường hợp nó sẽ bằng nhau. Mục đích của foreach là làm cho mã của bạn có thể đọc được, nhưng nó (thường) thêm một lớp của sự hướng dẫn, mà không phải là miễn phí. – Brian

+8

Tôi sẽ nói mục đích chính của 'foreach' là cung cấp một cơ chế lặp lại chung cho tất cả các bộ sưu tập bất kể chúng có thể lập chỉ mục (' List') hay không ('Dictionary'). –

+1

Xin chào Brian Gideon - chắc chắn đồng ý (đây là 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, trong khi 'Dictionary' không thể lập chỉ mục, một lần lặp lại của' Dictionary' không đ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ế nó tạo ra các phần tử 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ỉ mục của phần tử được liệt kê hiện tại trong liệt kê (nghĩa là chúng ta đang ở phần tử đầu tiên hoặc thứ năm hoặc cuối cùng). –

Trả lời

411

foreach dùng để lặp qua các bộ sưu tập triển khai IEnumerable. Nó thực hiện điều này bằng cách gọi số GetEnumerator trên bộ sưu tập, sẽ trả về Enumerator.

Enumerator này có một phương pháp và một tài sản:

  • MoveNext()
  • hiện

Current trả về đối tượng mà Enumerator hiện đang bật, MoveNext cập nhật Current đến đối tượng tiếp.

Rõ ràng, khái niệm chỉ mục là nước ngoài với khái niệm liệt kê và không thể thực hiện được.

Do đó, hầu hết các bộ sưu tập có thể được duyệt qua sử dụng trình lập 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 trường hợp này so với việc theo dõi chỉ mục bằng biến cục bộ.

+0

Cảm ơn câu trả lời này @FlySwat. Tôi sử dụng nó để lấy được giải pháp đơn giản của tôi (bạn có thể thấy nó hướng tới phía dưới hoặc ở đây http://stackoverflow.com/a/19236156/32495). – Gezim

+81

"Rõ ràng, khái niệm về một chỉ mục là nước ngoài với khái niệm đếm, và không thể được thực hiện." - Điều này là vô nghĩa, như câu trả lời của David B và bcahill làm rõ. Một chỉ mục là một liệt kê trên một phạm vi, và không có lý do người ta không thể liệt kê hai điều song song ... đó là chính xác những gì các hình thức lập chỉ mục của Enumerable.Select nào. –

+5

Ví dụ mã cơ bản: 'cho (var i = 0; i

2

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

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 mã phức tạp, chưa kể đến thời gian và không gian phức tạp.

3

Tôi không tin rằng có một cách để có được giá trị của vòng lặp hiện tại của vòng lặp foreach. Đếm mình, có vẻ 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 sẽ likley nhất được thực hiện một trong ba điều:

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

2) Đếm đối tượng để xử lý bài sau ... 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 cài đặt khi bạn thêm đối tượng vào bộ sưu tập.

+0

4) Trường hợp tôi đã nhấn nhiều lần là điều gì đó khác nhau phải được thực hiện trên thẻ đầu tiên hoặc cuối cùng - hãy 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. –

92

có thể làm một cái gì đó như thế này:

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

Điều đó không "thực sự" giải quyết vấn đề. Ý tưởng là tốt nhưng nó không tránh biến đếm bổ sung – Atmocreations

+0

không phải là ++ idx nhanh hơn idx ++? Không thực sự quan trọng trong nhiều vòng, nhưng một phần mở rộng nên sử dụng nó? – jgauffin

+0

@jgauffin: Không dành cho các loại tích phân. – Danvil

8

Nó chỉ đi làm việc cho một danh sách và không bất kỳ IEnumerable, nhưng trong LINQ có này:

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 Tôi không nói nó là một câu trả lời tuyệt vời, tôi chỉ nói rằng nó chỉ cho thấy nó có thể làm những gì anh ấy hỏi :)

@Graphain Tôi không mong đợi nó sẽ nhanh - Tôi không hoàn toàn chắc chắn nó hoạt động như thế nào có thể nhắc lại throu gh toàn bộ danh sách mỗi lần để tìm một đối tượng phù hợp, đó sẽ là một sự so sánh địa ngục.

Điều đó cho biết, Danh sách có thể giữ 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 xây dựng?

Sẽ tốt hơn nếu bạn chỉ cần đếm số nơi bạn đang ở trong cuộc điều tra, đơn giản hơn và dễ thích nghi hơn.

+4

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

+1

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. – CodesInChaos

54

Trả lời bằng chữ - cảnh báo, hiệu suất có thể không tốt bằng cách chỉ sử dụng int để theo dõi chỉ mục. Ít nhất nó tốt hơn là sử dụng IndexOf.

Bạn chỉ cần sử dụng quá tải chỉ mục của Chọn để bao bọc từng mục trong bộ sưu tập với một đối tượng ẩn danh biết chỉ mục. Điều này có thể được thực hiện chống lại bất cứ điều gì mà thực hiện 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

Lý do duy nhất để sử dụng OfType () thay vì Cast () là nếu một số mục trong liệt kê có thể thất bại một diễn viên rõ ràng. Đối với đối tượng, điều này sẽ không bao giờ xảy ra. – dahlbyk

+11

Chắc chắn, ngoại trừ các lý do khác để sử dụng OfType thay vì Cast - đó là tôi không bao giờ sử dụng Cast. –

73

Tôi không đồng ý với nhận xét rằng vòng lặp for là lựa chọn tốt hơn trong hầu hết các trường hợp.

foreach là cấu trúc hữu ích và không thay thế bằng vòng lặp for trong mọi trường hợp.

Ví dụ, nếu bạn có một DataReader và lặp qua tất cả hồ sơ sử dụng một foreach nó tự động gọi Vứt bỏ phương pháp và đóng người đọc (mà sau đó có thể đóng kết nối tự động). Điều này do đó an toàn hơn vì nó ngăn chặn rò rỉ kết nối ngay cả khi bạn quên đóng trình đọc.

(Chắc chắn là thực hành tốt để luôn đóng trình đọc nhưng trình biên dịch sẽ không bắt được nếu bạn không - bạn không thể đảm bảo bạn đã đóng tất cả người đọc nhưng bạn có thể làm cho nó có nhiều khả năng bạn sẽ không các kết nối bị rò rỉ bằng cách nhận thói quen sử dụng foreach.)

Có thể có các ví dụ khác về cuộc gọi ngầm của phương thức Dispose hữu ích.

+9

Điểm tốt và điều tôi không biết. –

+2

Cảm ơn bạn đã chỉ ra điều này. Thay vì tinh tế. Bạn có thể tìm thêm thông tin tại http://www.pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator/ và http://msdn.microsoft.com/en-us/library/aa664754 (VS.71) .aspx. –

+0

+1. Tôi đã viết chi tiết hơn về cách 'foreach' khác với' for' (và gần hơn với 'while') trên [Programmers.SE] (http://programmers.stackexchange.com/a/178225/6605). –

15

Dưới đây là một giải pháp tôi chỉ đưa ra cho vấn đề này

đang Original:

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

đang cập nhật

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

Phương pháp mở rộng:

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

+0

hahah, đã không vượt qua tâm trí của tôi ở tất cả, tuyệt vời, +1 :) – Marko

+59

Hai vấn đề: 1) Đây là 'O (n^2)' vì trong hầu hết các triển khai 'IndexOf' là' O (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. – CodesInChaos

+13

Lưu ý: O (n^2) có nghĩa là điều này có thể rất chậm đối với một bộ sưu tập lớn. –

21

Bạn có thể bọc bảng liệt kê ban đầu với một điều tra khác chứa thông tin chỉ mục.

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

Đây là mã cho lớp ForEachHelper.

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

Điều này sẽ không thực sự trả về chỉ mục của mục. trả về chỉ mục bên trong danh sách liệt kê, mà chỉ có thể là một danh sách con của danh sách, do đó chỉ cho bạn dữ liệu chính xác khi danh sách con và danh sách có kích thước bằng nhau. sted type chỉ mục của bạn sẽ không chính xác. –

+7

@Lucas: Không, nhưng nó sẽ trả về chỉ mục của phép lặp foreach hiện tại. Đó là câu hỏi. –

6

Tốt hơn để sử dụng từ khoá continue xâ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 
    //... 
} 
7

Đây là cách tôi làm điều đó, đó là tốt đẹp vì đơn giản của nó/ngắn gọn, nhưng nếu bạn đang làm rất nhiều trong vòng lặp cơ thể obj.Value, nó sẽ nhận được cũ khá nhanh.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) { 
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value); 
    ... 
} 
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/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)); 
+0

Tôi muốn sử dụng 'struct' cho cặp (chỉ mục, mục). – CodesInChaos

3

Làm thế nào về một cái gì đó như thế này? Lưu ý rằng myDelimitedString có thể là null nếu myEnumerable rỗ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; 
} 
+0

Nhiều sự cố ở đây. A) thêm cú đúp. B) chuỗi concat + = mỗi vòng lặp lặp lại. – 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 dự kiến. Nó có thể là một trường hợp khá phổ biến, về cơ bả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 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 của bất kỳ lỗi nào. Thoạt nhìn, tôi muốn đưa chỉ mục vào điều tra 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 ngầm ngầm biết chỉ mục hiện tại từ điểm đến 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 nó là một List, và rất có thể nó sẽ thực hiện ICollection.

ví dụ:

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 áp dụng, nhưng thường đủ để đáng nói, tôi nghĩ.

Dù sao, Điểm được rằng đôi khi có một giải pháp không rõ ràng đã có trong logic bạn có ...

3

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

Một cách hiệu quả, ông viết một phương pháp mở rộng kết thúc lặp lại 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, trong khi điều này sẽ là tốt trong một môi trường không Razor nếu bạn đang làm một hoạt động đơn lẻ (tức là một mà có thể được cung cấp như một lambda) nó sẽ không phải là một sự thay thế vững chắc của cho/foreach cú pháp trong bối cảnh không Razor.

3

Tôi không chắc bạn đang cố gắng làm gì 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ể thích ứng với phương thức IEnumerable.Select để lấy chỉ mục ra khỏi bất cứ thứ gì bạn muốn. Ví dụ, tôi có thể sử dụng một cái gì đó như thế này cho dù một giá trị là lẻ hoặc thậm chí.

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 mục có lẻ (1) hoặc thậm chí (0) trong danh sách.

390

Ian Mercer gửi một giải pháp tương tự như thế này trên Phil Haack's blog:

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

này giúp bạn mục (item.value) và chỉ số của nó (item.i) bằng cách sử dụng this overload of Linq's Select:

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

new { i, value } đang tạo mới anonymous object.

+3

Giải pháp đó là tốt đẹp cho trường hợp của một mẫu Razor nơi sự gọn gàng của mẫu là một mối quan tâm thiết kế không tầm thường và bạn cũng muốn sử dụng chỉ mục của mỗi mục được liệt kê. Tuy nhiên, hãy ghi nhớ rằng việc phân bổ đối tượng từ 'gói' thêm chi phí (trong không gian và thời gian) trên đầu trang (tăng không thể tránh khỏi) của một số nguyên. –

+3

awesomeness tinh khiết, nhưng mà là tài liệu ....Ý tôi là hai tham số trong Select delegate, tôi chưa bao giờ thấy ... và tôi không thể tìm thấy giải thích tài liệu ..... thử nghiệm với biểu thức đó tôi thấy giá trị đó không có nghĩa gì cả và chỉ vị trí quan trọng, trước tiên tham số phần tử, tham số thứ hai chỉ mục. – mjsr

+5

@mjsr Tài liệu là [ở đây] (https://msdn.microsoft.com/en-us/library/bb534869 (v = vs.110) .aspx). –

3

Tôi không nghĩ rằng đây nên được khá hiệu quả, nhưng nó hoạt động:

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

Điều đó giả định trước danh sách không có bản sao, tất nhiên ... –

1

Dưới đây là một giải pháp cho vấn đề này, với một tập trung vào việc giữ cú pháp càng gần với một tiêu chuẩn foreach càng tốt.

Loại cấu trúc này hữu ích nếu bạn muốn làm cho khung nhìn của bạn trông đẹp và sạch sẽ trong MVC. Ví dụ thay vì viết này theo cách thông thường (đó là khó khăn để định dạng độc đáo):

<%int i=0; 
foreach (var review in Model.ReviewsList) { %> 
    <div id="review_<%=i%>"> 
     <h3><%:review.Title%></h3>      
    </div> 
    <%i++; 
} %> 

Bạn thay vì có thể viết này:

<%foreach (var review in Model.ReviewsList.WithIndex()) { %> 
    <div id="review_<%=LoopHelper.Index()%>"> 
     <h3><%:review.Title%></h3>      
    </div> 
<%} %> 

Tôi đã viết một số phương pháp helper để cho phép này:

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

Trong môi trường không phải web bạn có thể sử dụng static thay vì HttpContext.Current.Items.

Đây thực chất là một biến toàn cầu, và do đó bạn không thể có nhiều vòng lặp WithIndex lồng nhau, nhưng đó không phải là vấn đề lớn trong trường hợp sử dụng này.

32

Sử dụng @ câu trả lời FlySwat, tôi đã đưa ra giải pháp này:

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

Bạn nhận được các điều tra viên sử dụng GetEnumerator và sau đó bạn vòng lặp sử dụng một vòng lặp for. Tuy nhiên, mẹo là để làm cho điều kiện của vòng lặp listEnumerator.MoveNext() == true.

Vì phương pháp MoveNext của một điều tra viên trả về true nếu có yếu tố tiếp theo và có thể truy cập, điều kiện vòng lặp làm cho vòng lặp dừng khi chúng ta hết các phần tử lặp lại.

+8

Không cần phải so sánh listEnumerator.MoveNext() == true. Điều đó giống như yêu cầu máy tính nếu đúng == đúng? :) Chỉ cần nói nếu listEnumerator.MoveNext() {} – Zesty

+6

@Zesty, bạn hoàn toàn chính xác. Tôi cảm thấy rằng nó dễ đọc hơn khi thêm vào trong trường hợp này đặc biệt là đối với những người không quen với việc nhập bất cứ điều gì khác hơn là tôi làm điều kiện. – Gezim

+0

@Gezim Tôi không ngại định vị ở đây, nhưng tôi nhận được quan điểm của bạn rằng điều này không giống như một biến vị ngữ. –

4

tôi đã xây dựng 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); 
+5

Đây là O (n * n). –

+0

Bạn cũng có thể sử dụng string.join trong C# Ví dụ: var joinResult = string.Join (",", listOfNames); ' –

+1

Ai đó có thể giải thích cho tôi điều O (n * n) này là gì? – Axel

11

Không có gì sai với việc sử dụng một biến truy cập là. Thực tế, cho dù bạn sử dụng for, foreachwhile hoặc do, biến số lượt truy cập phải được khai báo và tăng lên ở đâu đó.

Vì vậy, sử dụng thành ngữ này nếu bạn không chắc chắn nếu bạn có một bộ sưu tập phù hợp-lập chỉ mục:

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

Else sử dụng này nếu bạn biết rằng indexable sưu tập của bạn là O (1) để truy cập chỉ mục (mà nó sẽ được cho Array và có lẽ cho List<T> (các tài liệu không nói), nhưng không nhất thiết với nhiều loại khác (chẳng hạn như 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' 
} 

Không bao giờ cần thiết phải 'thủ công' hoạt động IEnumerator bằng cách gọi MoveNext() và thẩm vấn Current - foreach giúp bạn tiết kiệm một số sự cố cụ thể ... nếu bạn cần bỏ qua các mục, chỉ cần sử dụng continue trong phần thân của vòng lặp.

Và chỉ cho đầy đủ, tùy thuộc vào những gì bạn đã làm với chỉ số của bạn (các cấu trúc ở trên cung cấp nhiều linh hoạt), bạn có thể sử dụng LINQ Parallel:

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

Chúng tôi sử dụng AsParallel() trên, bởi vì nó Năm 2014 đã và chúng tôi muốn tận dụng tốt những lõi này để tăng tốc mọi thứ. Hơn nữa, đối với 'tuần tự' LINQ, you only get a ForEach() extension method on List<T> and Array ... và không rõ ràng rằng việc sử dụng nó tốt hơn so với thực hiện đơn giản foreach, vì bạn vẫn đang chạy một luồng cho cú pháp xấu xí hơn.

5

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) 
} 
+12

Và bây giờ thuật toán là O (n^2) (nếu không tệ hơn). Tôi sẽ nghĩ * rất cẩn thận trước khi sử dụng nó. Nó cũng trùng lặp với câu trả lời của @crucible – BradleyDotNET

+1

@BradleyDotNET là 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 bạn, nó sẽ nhận được vị trí của một trong những đầu tiên! – Sonhja

1

này không trả lời câu hỏi cụ thể của bạn, nhưng nó cung cấp cho bạn một giải pháp cho vấn đề của bạn: sử dụng một vòng lặp for để chạy qua bộ sưu tập đối tượng. thì bạn sẽ có chỉ mục hiện tại bạn đang làm việc.

// Untested 
for (int i = 0; i < collection.Count; i++) 
{ 
    Console.WriteLine("My index is " + i); 
} 
9

Tại sao foreach?!

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 để khow chỉ số của mỗi Loop:

myList.indexOf(m) 
+5

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. – mayu

+1

Vấn đề cần tránh là nơi bạn duyệt qua nhiều lần IEnumerable, ví dụ: để có được số lượng mục và sau đó mỗi mục. Đ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. –

3

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ấu trúc sau và phương pháp mở rộng.

Cấu trúc và phương pháp 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); 
    } 
} 
24

Cuối cùng C# 7 có một cú pháp phong nha để có được một chỉ số bên trong một vòng lặp foreach (tức là các tuple):

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

Một phương pháp mở rộng chút 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)); 
+0

Wow, tôi thực sự đang tìm cách trả lời câu hỏi này và đã viết độc lập nhân vật phương thức này cho nguyên văn, ngoại trừ tôi đã sử dụng "nguồn" thay vì "tự". Có quy ước nào cho phương thức mở rộng 'this' paramter name? Tôi vừa sao chép LINQ. – Ryan

+0

Câu trả lời này được đánh giá thấp, có bộ nhớ sạch hơn nhiều không – Todd

3

Trạng thái câu trả lời hàng đầu:

"Rõ ràng, khái niệm về chỉ mục là nước ngoài với khái niệm đếm, và không thể làm được. "

Mặc dù điều này đúng với phiên bản C# hiện tại nhưng đây không phải là giới hạn khái niệm.

Việc tạo ra một tính năng ngôn ngữ C# mới bằng MS có thể giải quyết việc này, cùng với sự hỗ trợ cho một giao diện mới 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 truyền một IEnumerable và không thể giải quyết một IIndexedEnumerable, nhưng nó là hỏi với chỉ số var, sau đó trình biên dịch C# có thể bọc nguồn bằng một đối tượng IndexedEnumerable, nó thêm vào mã để theo dõi chỉ mục.

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

Tại sao:

  • Foreach trông đẹp hơn, và trong các ứng dụng kinh doanh là hiếm khi một nút cổ chai hiệu suất
  • Foreach có thể hiệu quả hơn vào bộ nhớ. Có một đường ống các hàm thay vì chuyển đổi thành 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 hơn, nếu có ít lỗi bộ nhớ cache CPU và ít GC.Thu thập
  • Yêu cầu các coder để thêm mã theo dõi chỉ số, làm hỏng vẻ đẹp
  • Đó là khá dễ dàng để thực hiện (nhờ MS) và tương thích

ngược Trong khi hầu hết người dân ở đây không phải là MS, đây là một đúng câu trả lời, và bạn có thể vận động MS để thêm một tính năng như vậy. Bạn đã có thể xây dựng iterator của riêng bạn với một extension function and use tuples, nhưng MS có thể rắc đường cú pháp để tránh các chức năng mở rộng

+0

Chờ, tính năng ngôn ngữ này đã tồn tại hay nó được đề xuất cho tương lai? – Pavel

+1

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

7

C# 7 cuối 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}"); 
     } 
    } 
} 
+0

Như đã xem ở trên: https://stackoverflow.com/a/39997157/887092 – Todd

6

Sử dụng LINQ, C# 7, và gói System.ValueTuple NuGet, bạn có thể làm điều này:

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

bạn có thể sử dụng thường xuyên foreach xây dựng và có thể truy cập giá trị và chỉ mục trực tiếp, không phải là thành viên của đối tượng và chỉ giữ cả hai trường trong phạm vi vòng lặp. Vì những lý do này, tôi tin rằng đây là giải pháp tốt nhất nếu bạn có thể sử dụng C# 7 và System.ValueTuple.

Các vấn đề liên quan