Có thể lặp lại thông qua một foreach?


129

Tôi biết tôi có thể sử dụng một forcâu lệnh và đạt được hiệu quả tương tự, nhưng tôi có thể lặp lại thông qua một foreachvòng lặp trong C # không?


6
Bạn có thể đảo ngược phần tử (list.Reverse ()) trong danh sách trước khi bạn thực hiện cho từng phần tử.
Akanthan


Bạn sẽ phải khởi tạo tất cả các yếu tố trong bảng liệt kê để bạn có thể đảo ngược thứ tự của chúng. Nó có thể không thể. Hãy xem xét trình tự: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Làm thế nào bạn sẽ đảo ngược điều đó?
Suncat2000

Câu trả lời:


84

Khi làm việc với một danh sách (lập chỉ mục trực tiếp), bạn không thể thực hiện nó hiệu quả như sử dụng for vòng lặp.

Chỉnh sửa: Điều đó có nghĩa là, khi bạn có thể sử dụng một forvòng lặp, đó có thể là phương pháp chính xác cho nhiệm vụ này. Ngoài ra, đối với nhiều thứ foreachđược thực hiện theo thứ tự, bản thân cấu trúc được xây dựng để thể hiện các vòng lặp độc lập với các chỉ số phần tử và thứ tự lặp, đặc biệt quan trọng trong lập trình song song . Theo ý kiến ​​của tôi , việc lặp lại dựa trên thứ tự không nên sử dụng foreachđể lặp.


3
Tôi thấy tuyên bố cuối cùng của bạn một chút quá chung chung. Chắc chắn có những trường hợp, ví dụ, một số IE cần phải được lặp đi lặp lại theo thứ tự? Bạn sẽ không sử dụng foreach trong trường hợp đó? Bạn sẽ sử dụng cái gì?
avl_sweden

1
CẬP NHẬT: Xem [Câu trả lời của Bryan cho một câu hỏi tương tự [( stackoverflow.com/a/3320924/199364 ), để biết câu trả lời hiện đại hơn, sử dụng Linq và thảo luận về cả hai danh sách và các liệt kê khác.
ToolmakerSteve

CẬP NHẬT: Jon Skeet có một câu trả lời thậm chí thanh lịch hơn, hiện có thể sử dụng " yield return".
ToolmakerSteve

2
Tôi đã có phản ứng ban đầu giống như @avl_sweden - "lặp đi lặp lại dựa trên trật tự không nên sử dụng foreach" nghe có vẻ quá rộng. Có, foreach là tốt để thể hiện các nhiệm vụ song song độc lập trật tự. NHƯNG nó cũng là một phần thiết yếu của lập trình dựa trên iterator hiện đại . Có lẽ vấn đề là nó sẽ rõ ràng / an toàn hơn nếu có hai từ khóa riêng biệt , để làm rõ liệu người ta có khẳng định rằng các lần lặp là độc lập không? [Đưa ra một ngôn ngữ như Eiffel có thể truyền bá hợp đồng, các xác nhận đó có thể chứng minh đúng hoặc sai, đối với mã đã cho.]
ToolmakerSteve

2
Ý tưởng rằng foreach "được xây dựng để thể hiện các vòng lặp độc lập với các chỉ số phần tử và thứ tự lặp" là không chính xác. Đặc tả ngôn ngữ c # yêu cầu quy trình xử lý các phần tử theo thứ tự. Hoặc bằng cách sử dụng MoveNext trên các trình vòng lặp hoặc bằng cách xử lý các chỉ mục bắt đầu từ 0 và tăng thêm một lần trên mỗi lần lặp của mảng.
Donald Rich

145

Nếu bạn đang dùng .NET 3.5, bạn có thể làm điều này:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

Nó không hiệu quả lắm vì về cơ bản nó phải thông qua điều tra viên để chuyển mọi thứ lên một ngăn xếp rồi bật mọi thứ trở lại theo thứ tự ngược lại.

Nếu bạn có một bộ sưu tập có thể lập chỉ mục trực tiếp (ví dụ IList), bạn chắc chắn nên sử dụng một for vòng lặp thay thế.

Nếu bạn đang sử dụng .NET 2.0 và không thể sử dụng vòng lặp for (tức là bạn chỉ có IEnumerable) thì bạn sẽ phải viết hàm Reverse của riêng mình. Điều này sẽ làm việc:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

Điều này phụ thuộc vào một số hành vi có lẽ không rõ ràng. Khi bạn chuyển một IEn Countable cho hàm tạo stack, nó sẽ lặp qua nó và đẩy các mục lên stack. Khi bạn lặp lại qua ngăn xếp, nó sẽ bật ra mọi thứ theo thứ tự ngược lại.

Điều này và Reverse()phương thức mở rộng .NET 3.5 rõ ràng sẽ nổ tung nếu bạn cung cấp cho nó một IEnumerable không bao giờ ngừng trả lại các mục.


Ngoài ra tôi quên đề cập đến .net v2 chỉ xin vui lòng
JL.

4
Giải pháp .NET 2.0 thú vị.
RichardOD

11
Tôi có thiếu thứ gì không hoặc giải pháp .Net 3.5 của bạn không thực sự hoạt động? Reverse () đảo ngược danh sách tại chỗ và không trả lại nó. Quá tệ, như tôi đã hy vọng cho một giải pháp như vậy.
dùng12861

2
Một số quản trị viên đánh dấu câu trả lời này là SAI. Reverse () là một khoảng trống [1] và do đó, ví dụ trên dẫn đến một lỗi biên dịch. [1] msdn.microsoft.com/en-us/l
Library / b0axc2h2 (v = vs.110) .aspx

6
@ user12861, Micky Duncan: câu trả lời không sai, bạn đang thiếu một cái gì đó. Có một phương thức Reverse trên System.Collections.Generic.List <T> thực hiện đảo ngược tại chỗ. Trong .Net 3.5 có một phương thức mở rộng trên IEnumerable <T> có tên Reverse. Tôi đã thay đổi ví dụ từ var thành IEnumerable <int> để làm cho điều này rõ ràng hơn.
Matt Howells

55

Như 280Z28 nói, đối với một IList<T>bạn chỉ có thể sử dụng chỉ mục. Bạn có thể ẩn điều này trong một phương thức mở rộng:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

Điều này sẽ nhanh hơn so với Enumerable.Reverse()bộ đệm tất cả dữ liệu đầu tiên. (Tôi không tin Reversecó bất kỳ tối ưu hóa nào được áp dụng theo cách đó Count().) Lưu ý rằng bộ đệm này có nghĩa là dữ liệu được đọc hoàn toàn khi bạn bắt đầu lặp lại, trong khiFastReverse sẽ "nhìn thấy" bất kỳ thay đổi nào trong danh sách khi bạn lặp. (Nó cũng sẽ bị hỏng nếu bạn loại bỏ nhiều mục giữa các lần lặp.)

Đối với các chuỗi chung, không có cách lặp lại ngược lại - ví dụ: chuỗi có thể là vô hạn:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

Điều gì bạn sẽ xảy ra nếu bạn cố gắng lặp đi lặp lại điều đó?


Chỉ tò mò, tại sao sử dụng "> = 0" thay vì "> -1"?
Chris S

15
> tại sao sử dụng "> = 0" thay vì "> -1"? Bởi vì> = 0 truyền đạt tốt hơn ý định cho con người đọc mã. Trình biên dịch phải có khả năng tối ưu hóa điều đó thành tương đương> -1 nếu làm như vậy sẽ cải thiện hiệu suất.
Đánh dấu Maslar

1
FastReverse (vật phẩm <T> IList này) phải là FastReverse <T> (vật phẩm <T> IList này. :)
Rob

Tách gậy 0 <= i thậm chí còn tốt hơn. Sau khi dạy khá nhiều trẻ em toán học trong nhiều năm và giúp đỡ mọi người về mã hóa, tôi thấy rằng nó sẽ giảm được rất nhiều lỗi mà mọi người mắc phải nếu họ LUÔN trao đổi a> b thành b <a khi mọi thứ diễn ra theo thứ tự tự nhiên số (Hoặc trục X của hệ tọa độ) - nhược điểm duy nhất là nếu bạn cần dán phương trình vào XML, giả sử .config
Eske Rahn

13

Trước khi sử dụng foreachđể lặp lại, đảo ngược danh sách theo reversephương thức:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }


Một lời cảnh báo về những gì myListsẽ hữu ích. IEnumerable.Reverse sẽ không làm việc ở đây.
nawfal

2
@ MA-Maddin không có hai là khác nhau. Tôi đoán người trả lời đang dựa vào Danh sách <T>. Trả lời đúng chỗ.
nawfal

Thật ra, tôi không biết tại sao tôi lại nói thế. Tôi nên đã thêm chi tiết ... Dù sao đây là mã khó hiểu vì myListdường như là loại System.Collections.Generic.List<System.Windows.Documents.List>(hoặc bất kỳ loại tùy chỉnh nào khác List) nếu không mã này không hoạt động: P
Martin Schneider

5

Đôi khi bạn không có khả năng lập chỉ mục hoặc có lẽ bạn muốn đảo ngược kết quả của truy vấn Linq hoặc có thể bạn không muốn sửa đổi bộ sưu tập nguồn, nếu bất kỳ điều nào trong số này là đúng, Linq có thể giúp bạn.

Phương thức mở rộng Linq sử dụng các loại ẩn danh với Linq Chọn để cung cấp khóa sắp xếp cho Linq OrderByDesceinating;

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Sử dụng:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

Nó được đặt tên là "Đảo ngược" vì nó đồng nghĩa với "Đảo ngược" và cho phép định hướng với việc thực hiện Danh sách đảo ngược.

Cũng có thể đảo ngược một số phạm vi nhất định của bộ sưu tập, vì Int32.MinValue và Int32.MaxValue nằm ngoài phạm vi của bất kỳ loại chỉ mục bộ sưu tập nào, chúng tôi có thể tận dụng chúng cho quy trình đặt hàng; nếu một chỉ số phần tử nằm dưới phạm vi đã cho, nó được gán Int32.MaxValue để thứ tự của nó không thay đổi khi sử dụng OrderByDesceinating, tương tự, các phần tử tại một chỉ mục lớn hơn phạm vi đã cho, sẽ được gán Int32.MinValue, để chúng xuất hiện đến cuối quá trình đặt hàng. Tất cả các yếu tố trong phạm vi nhất định được gán chỉ số bình thường của chúng và được đảo ngược tương ứng.

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Sử dụng:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

Tôi không chắc chắn về lượt truy cập hiệu suất của các triển khai Linq này so với việc sử dụng Danh sách tạm thời để bọc bộ sưu tập để đảo ngược.


Tại thời điểm viết bài, tôi không biết về việc triển khai Reverse của chính Linq, tuy nhiên, thật vui khi làm việc này. https://msdn.microsoft.com/en-us/l Library / vudio / bb58497 (v = vs.100) .aspx


4

Nếu bạn sử dụng Danh sách <T>, bạn cũng có thể sử dụng mã này:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

Đây là một phương pháp viết danh sách ngược lại trong chính nó.

Bây giờ là lời khuyên:

foreach(string s in list)
{
    Console.WriteLine(s);
}

Đầu ra là:

3
2
1

3

thể nếu bạn có thể thay đổi mã bộ sưu tập thực hiện IEnumerable hoặc IEnumerable (ví dụ: triển khai IList của riêng bạn).

Tạo Trình lặp thực hiện công việc này cho bạn, ví dụ như triển khai sau thông qua giao diện IEnumerable (giả sử 'mục' là trường Danh sách trong mẫu này):

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Do đó, Danh sách của bạn sẽ lặp theo thứ tự ngược thông qua danh sách của bạn.

Chỉ là một gợi ý: Bạn nên nêu rõ hành vi đặc biệt này của danh sách của bạn trong tài liệu (thậm chí tốt hơn bằng cách chọn tên lớp tự giải thích như Stack hoặc Queue, quá).


2

Không. ForEach chỉ lặp qua bộ sưu tập cho từng mặt hàng và đơn hàng tùy thuộc vào việc nó sử dụng IEnumerable hay GetEnumerator ().


1
Cũng có những bảo đảm trật tự, tùy thuộc vào loại bộ sưu tập.
Jon Skeet

1

Elaborateling nhẹ nhàng về câu trả lời hay của Jon Skeet , điều này có thể là linh hoạt:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

Và sau đó sử dụng như

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}

-1

Tôi đã sử dụng mã này hoạt động

                if (element.HasAttributes) {

                    foreach(var attr in element.Attributes().Reverse())
                    {

                        if (depth > 1)
                        {
                            elements_upper_hierarchy_text = "";
                            foreach (var ancest  in element.Ancestors().Reverse())
                            {
                                elements_upper_hierarchy_text += ancest.Name + "_";
                            }// foreach(var ancest  in element.Ancestors())

                        }//if (depth > 1)
                        xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                    }// foreach(var attr in element.Attributes().Reverse())

                }// if (element.HasAttributes) {

2
Điều này là xấu bởi vì .Reverse()thực sự là sửa đổi danh sách.
Colin Basnett

-8

Điều này hoạt động khá tốt

List<string> list = new List<string>();

list.Add("Hello");
list.Add("Who");
list.Add("Are");
list.Add("You");

foreach (String s in list)
{
    Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
}

9
Điều này nghe có vẻ không hiệu quả đối với tôi.
Jean Azzopardi
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.