C # foreach cải tiến?


9

Tôi thường xuyên gặp phải vấn đề này trong quá trình lập trình, nơi tôi muốn có một chỉ số đếm vòng lặp bên trong một foreach và phải tạo một số nguyên, sử dụng nó, tăng dần, v.v. Sẽ không phải là một ý tưởng hay nếu có một từ khóa được giới thiệu số vòng lặp bên trong của một foreach? Nó cũng có thể được sử dụng trong các vòng lặp khác.

Điều này có đi ngược lại với việc sử dụng các từ khóa như cách trình biên dịch sẽ không cho phép từ khóa này được sử dụng ở bất cứ đâu nhưng trong một cấu trúc vòng lặp không?


7
Bạn có thể tự làm điều đó bằng một phương pháp mở rộng, xem câu trả lời từ Dan Finch: stackoverflow.com/a/521894/866172 (không phải là câu trả lời được đánh giá tốt nhất, nhưng tôi nghĩ đó là câu trả lời "sạch hơn")
Jalayn

Perl và những thứ như $ _ xuất hiện trong tâm trí. Tôi ghét những thứ đó.
Yam Marcovic

2
Từ những gì tôi biết về C #, nó có nhiều từ khóa dựa trên ngữ cảnh, vì vậy điều này sẽ không "đi ngược lại việc sử dụng từ khóa". Như đã chỉ ra trong một câu trả lời dưới đây, có lẽ bạn chỉ muốn sử dụng một for () vòng lặp.
Craige

@Jalayn Xin vui lòng gửi đó như là một câu trả lời. Đó là một trong những cách tiếp cận tốt nhất.
Apoorv Khurasia

@MonsterTruck: Tôi không nghĩ anh ấy nên. Giải pháp thực sự sẽ là chuyển câu hỏi này sang SO và đóng nó dưới dạng bản sao của câu hỏi mà Jalayn liên kết đến.
Brian

Câu trả lời:


54

Nếu bạn cần số vòng lặp trong foreachvòng lặp, tại sao bạn không sử dụng forvòng lặp thông thường . Các foreachvòng lặp được dự định để làm cho sử dụng cụ thể của forvòng lặp đơn giản hơn. Có vẻ như bạn có một tình huống mà sự đơn giản foreachkhông còn có lợi.


1
+1 - điểm tốt. Thật dễ dàng để sử dụng IEnumerable.Count hoặc object.length với một vòng lặp for thông thường để tránh mọi vấn đề với việc tìm ra số lượng phần tử trong đối tượng.

11
@ GlenH7: Tùy thuộc vào việc triển khai, IEnumerable.Countcó thể tốn kém (hoặc không thể, nếu đó chỉ là một lần liệt kê). Bạn cần biết nguồn gốc bên dưới là gì trước khi bạn có thể làm điều đó một cách an toàn.
Nhị phân nhị phân

2
-1: Bill Wagner ở More Effective C # mô tả tại sao người ta nên luôn luôn dính vào foreachtrên for (int i = 0…). Anh ấy đã viết một vài trang nhưng tôi có thể tóm tắt bằng hai từ - tối ưu hóa trình biên dịch trên lần lặp. Ok đó không phải là hai từ.
Apoorv Khurasia

13
@MonsterTruck: bất cứ điều gì Bill Wagner viết, tôi đã thấy đủ các tình huống như mô tả của OP, trong đó một forvòng lặp cho mã dễ đọc hơn (và tối ưu hóa trình biên dịch lý thuyết thường là tối ưu hóa sớm).
Doc Brown

1
@DocBrown Điều này không đơn giản như tối ưu hóa sớm. Trước đây tôi đã tự xác định các nút thắt cổ chai và sửa chúng bằng cách sử dụng phương pháp này (nhưng tôi thừa nhận tôi đã xử lý hàng ngàn đối tượng trong bộ sưu tập). Hy vọng sớm đăng một ví dụ làm việc. Trong khi đó, đây là Jon Skeet . [Ông nói rằng cải thiện hiệu suất sẽ không đáng kể nhưng điều đó phụ thuộc nhiều vào bộ sưu tập và số lượng đối tượng].
Apoorv Khurasia

37

Bạn có thể sử dụng các loại ẩn danh như thế này

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Điều này tương tự như enumeratechức năng của Python

for i, v in enumerate(items):
    print v, i

+1 ... bóng đèn. Tôi thích cách tiếp cận đó. Tại sao tôi chưa bao giờ nghĩ về nó trước đây?
Phil

17
Bất cứ ai thích điều này trong C # hơn một vòng lặp cổ điển rõ ràng có một ý kiến ​​kỳ lạ về khả năng đọc. Đây có thể là một mánh khóe gọn gàng nhưng tôi sẽ không cho phép một thứ như thế trong mã chất lượng sản xuất. Nó ồn hơn nhiều so với vòng lặp cổ điển và không thể hiểu là nhanh.
Falcon

4
@Falcon, đây là một giải pháp thay thế hợp lệ NẾU BẠN KHÔNG THỂ SỬ DỤNG kiểu cũ cho vòng lặp (cần số đếm). Đó là một vài bộ sưu tập của đối tượng mà tôi phải làm việc xung quanh ...
Fabricio Araujo

8
@FovenioAraujo: Chắc chắn C # cho các vòng lặp không yêu cầu số đếm. Theo như tôi biết thì chúng giống như C cho các vòng lặp, có nghĩa là bạn có thể sử dụng bất kỳ giá trị boolean nào để kết thúc vòng lặp. Chẳng hạn như kiểm tra kết thúc phép liệt kê khi MoveNext trả về sai.
Zan Lynx

1
Điều này có thêm lợi ích là có tất cả các mã để xử lý số gia khi bắt đầu khối foreach thay vì phải tìm nó.
JeffO

13

Tôi chỉ thêm câu trả lời của Dan Finch vào đây, theo yêu cầu.

Đừng cho tôi điểm, hãy cho điểm cho Dan Finch :-)

Giải pháp là viết phương pháp mở rộng chung sau:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Mà được sử dụng như vậy:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
Điều đó khá thông minh, nhưng tôi nghĩ rằng chỉ cần sử dụng một cách bình thường là "dễ đọc" hơn, đặc biệt là nếu người nào đó cuối cùng duy trì mã có thể không phải là một át chủ bài trong .NET và không quá quen thuộc với khái niệm phương thức mở rộng . Tôi vẫn đang lấy một ít mã này. :)
MetalMikester

1
Có thể tranh luận một trong hai phía của câu hỏi này và câu hỏi ban đầu - Tôi đồng ý với ý định của người đăng, có những trường hợp foreach đọc tốt hơn nhưng chỉ số là cần thiết, nhưng tôi luôn bất lợi khi thêm đường cú pháp vào ngôn ngữ nếu không rất cần thiết - Tôi có chính xác cùng một giải pháp trong mã của riêng tôi với một tên khác - chuỗi.Apply Indexed <T> (......)
Mark Mullin

Tôi đồng ý với Mark Mullin, bạn có thể tranh luận ở hai bên. Tôi nghĩ rằng đó là một cách sử dụng khá thông minh các phương pháp mở rộng và nó phù hợp với câu hỏi nên tôi đã chia sẻ nó.
Jalayn

5

Tôi có thể sai về điều này, nhưng tôi luôn nghĩ rằng điểm chính của vòng lặp foreach là đơn giản hóa việc lặp lại từ những ngày STL của C ++ tức là

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

so sánh điều này với việc sử dụng IEnumerable của .NET trong foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

cái sau đơn giản hơn nhiều và tránh được nhiều cạm bẫy cũ. Ngoài ra, họ dường như tuân theo các quy tắc gần như giống hệt nhau. Chẳng hạn, bạn không thể thay đổi nội dung IEnumerable trong khi bên trong vòng lặp (điều này có ý nghĩa hơn nhiều nếu bạn nghĩ về nó theo cách STL). Tuy nhiên, vòng lặp for thường có mục đích logic khác với vòng lặp.


4
+1: ít người nhớ rằng foreach về cơ bản là đường cú pháp trên mẫu lặp.
Steven Evers

1
Vâng, có một số khác biệt; Thao tác con trỏ hiện diện trong .NET được ẩn khá sâu khỏi lập trình viên, nhưng bạn có thể viết một vòng lặp for trông rất giống với ví dụ C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS, không phải nếu bạn nhận ra rằng mọi thứ bạn chạm vào .NET không phải là kiểu có cấu trúc LÀ MỘT ĐIỂM, chỉ với cú pháp khác nhau (. Thay vì ->). Trong thực tế, việc truyền một kiểu tham chiếu cho một phương thức cũng giống như truyền nó như một con trỏ và truyền nó bằng tham chiếu là truyền nó như một con trỏ kép. Điều tương tự. Ít nhất, đó là cách mà một người có ngôn ngữ mẹ đẻ là C ++ nghĩ về nó. Giống như, bạn học tiếng Tây Ban Nha ở trường bằng cách suy nghĩ về cách ngôn ngữ có liên quan về mặt cú pháp với ngôn ngữ mẹ đẻ của bạn.
Jonathan Henson

* loại giá trị không có kiểu cấu trúc. lấy làm tiếc.
Jonathan Henson

2

Nếu bộ sưu tập trong câu hỏi là một IList<T>, bạn chỉ cần sử dụng forvới lập chỉ mục:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Nếu không, sau đó bạn có thể sử dụng Select(), nó có một quá tải cung cấp cho bạn chỉ mục.

Nếu điều đó cũng không phù hợp, tôi nghĩ việc duy trì chỉ số đó theo cách thủ công là đủ đơn giản, đó chỉ là hai dòng mã rất ngắn. Tạo một từ khóa đặc biệt cho điều này sẽ là một quá mức cần thiết.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

+1, ngoài ra, nếu bạn đang sử dụng một chỉ mục chính xác một lần trong vòng lặp, bạn có thể làm điều gì đó như foo(++i)hoặc foo(i++)tùy thuộc vào chỉ mục nào là cần thiết.
Công việc

2

Nếu tất cả những gì bạn biết là bộ sưu tập là vô số IE, nhưng cần theo dõi xem bạn đã xử lý bao nhiêu phần tử (và do đó tổng số khi bạn hoàn thành), bạn có thể thêm một vài dòng vào vòng lặp cơ bản:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Bạn cũng có thể thêm một biến chỉ mục tăng dần vào một foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Nếu bạn muốn làm cho nó trông thanh lịch hơn, bạn có thể xác định một phương thức mở rộng hoặc hai để "ẩn" các chi tiết này:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

Phạm vi cũng hoạt động nếu bạn muốn giữ cho khối sạch khỏi va chạm với các tên khác ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

Đối với điều này, tôi sử dụng một whilevòng lặp. Vòng lặp forcũng hoạt động, và nhiều người dường như thích chúng, nhưng tôi chỉ thích sử dụng forvới thứ gì đó sẽ có số lần lặp cố định. IEnumerables có thể được tạo ra trong thời gian chạy, vì vậy, theo tôi, whilecó ý nghĩa hơn. Gọi nó là một hướng dẫn mã hóa không chính thức, nếu bạn muốn.

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.