Menu

break, continue và goto trong C#: thoát vòng lặp lồng nhau

Cách break, continue và goto hoạt động trong C#: kết thúc vòng lặp sớm, bỏ qua một lần lặp, break bên trong switch, ba cách thoát khỏi vòng lặp lồng nhau (biến cờ, goto, hoặc phương thức với return), và bug vòng lặp vô hạn do continue trong while.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

break kết thúc vòng lặp sớm. continue bỏ qua phần còn lại của lần lặp hiện tại và chuyển sang lần lặp tiếp theo. Cả hai đều áp dụng cho vòng lặp (loop) gần nhất bao quanh chúng, và hoạt động giống nhau trong for, foreach, while và do while.

Đầu ra:

process order of 45
skip invalid total 0
process order of 120
skip invalid total -5
process order of 80
test order found, stopping
after the loop

continue đã nhảy qua dòng xử lý với 0 và -5. break dừng vòng lặp tại 999, nên 60 không bao giờ được duyệt tới, và chương trình chạy tiếp ở câu lệnh đầu tiên sau vòng lặp.

break: dừng khi đã có câu trả lời

Cách dùng điển hình của break là tìm kiếm: khi đã tìm thấy thứ cần tìm, không có lý do gì để xem phần còn lại.

Đầu ra:

first invalid email at index 1

Phần cập nhật (i++) không chạy sau break, nên nếu i được khai báo bên ngoài vòng lặp, nó vẫn giữ chỉ số vừa khớp. Với những lần tìm kiếm đơn giản, một lời gọi thư viện cũng làm được điều tương tự: Array.FindIndex(emails, e => !e.Contains("@")) trả về chỉ số (hoặc -1), còn emails.Any(e => !e.Contains("@")) của LINQ cho biết có email nào không hợp lệ hay không.

continue: nhảy sang lần lặp tiếp theo

continue là một guard clause cho vòng lặp. Thay vì bọc phần việc chính trong một câu lệnh if, hãy loại bỏ những trường hợp không mong muốn ngay ở đầu:

Đầu ra:

pen costs 1.20
mug costs 8.00

continue nhảy đến đâu tiếp theo phụ thuộc vào loại vòng lặp. Trong vòng lặp for, nó chạy phần cập nhật rồi kiểm tra điều kiện. Trong vòng lặp while, nó nhảy thẳng tới điều kiện, và đây là nguồn gốc của một bug kinh điển:

int i = 0;
while (i < 10)
{
    if (i % 2 == 0)
        continue;        // i++ below is skipped: i stays 0 forever
    Console.WriteLine(i);
    i++;
}

Hãy cập nhật biến đếm trước mọi lệnh continue, hoặc dùng vòng lặp for, nơi phần cập nhật nằm ở phần đầu và luôn được chạy.

break bên trong switch bên trong vòng lặp

Trong switch, break kết thúc phần case. Khi switch đó nằm trong một vòng lặp, lệnh break thuộc về switch, không phải vòng lặp:

Đầu ra:

quit received
count = 2

Một lệnh break bình thường ở case "quit" sẽ chỉ kết thúc switch, vòng lặp sẽ chạy tiếp, và count sẽ là 3. Lệnh goto nhảy tới nhãn nằm sau vòng lặp. Chuyển vòng lặp vào một phương thức và viết return ở case đó cũng được, mà không cần nhãn.

Thoát khỏi vòng lặp lồng nhau

C# không có break kèm nhãn như break outer; của Java. Khi việc tìm kiếm chạy qua hai vòng lặp lồng nhau, break ở vòng lặp trong chỉ kết thúc vòng lặp trong; vòng lặp ngoài chuyển sang hàng tiếp theo. Có ba cách xử lý chuẩn.

1. Một biến cờ được vòng lặp ngoài kiểm tra:

Đầu ra:

first free seat: row 1, seat 2

2. goto tới một nhãn sau cả hai vòng lặp. Đây là nơi duy nhất mà nhiều bộ quy tắc viết code C# chấp nhận goto, vì nó diễn đạt chính xác ý "thoát khỏi cả hai vòng lặp":

for (int row = 0; row < rows; row++)
{
    for (int col = 0; col < cols; col++)
    {
        if (seats[row, col] == 0)
        {
            Console.WriteLine($"first free seat: row {row}, seat {col}");
            goto done;
        }
    }
}
done:
Console.WriteLine("search finished");

3. Một phương thức với return, thường là cách gọn nhất, vì việc tìm kiếm có tên riêng và có thể trả về kết quả:

Đầu ra:

first free seat: row 1, seat 2

C# 7.0 còn giới thiệu local function, cho phép đặt phương thức hỗ trợ đó ngay bên trong phương thức gọi nó. Cách dùng tham số out ở đây được giải thích trong trang về ref và out.

goto và nhãn

Một nhãn là một định danh theo sau bởi dấu hai chấm, và goto label; nhảy tới đó. Các quy tắc đủ chặt để ngăn những cách lạm dụng tệ nhất:

  • Nhãn phải nằm trong cùng phương thức, trong cùng khối lệnh với goto hoặc một khối bao quanh nó. Bạn có thể nhảy ra khỏi khối lệnh nhưng không bao giờ nhảy vào trong: nhảy từ bên ngoài tới một nhãn trong thân vòng lặp sẽ gây lỗi CS0159 (no such label within the scope of the goto statement).
  • Bạn không thể rời khỏi khối finally bằng goto, break hay continue.
  • Bên trong switch, goto case 3; và goto default; chuyển quyền điều khiển sang phần khác. Đây là cách switch trong C# thể hiện fall-through, vì tự động rơi xuống case kế tiếp là lỗi biên dịch.

goto cũng có thể tạo vòng lặp (nhảy ngược về một nhãn), nhưng vòng lặp while diễn đạt cùng điều đó theo cách ai đọc cũng nhận ra. Hãy chỉ dùng goto để thoát vòng lặp lồng nhau và cho các phần của switch.

return, throw và vòng lặp

return rời khỏi toàn bộ phương thức, nên cũng rời khỏi mọi vòng lặp bên trong nó. throw cũng vậy, bằng cách ném ra một ngoại lệ. Bên trong khối try có finally, cả break, continue, goto và return vẫn chạy khối finally trước khi chuyển quyền điều khiển, nhờ đó việc thoát khỏi một vòng lặp đang mở tệp trong khối using vẫn an toàn.

Câu lệnhRời khỏiChạy tiếp tại
continuelần lặp hiện tạilần lặp tiếp theo (phần cập nhật, rồi điều kiện)
breakvòng lặp hoặc switch trong cùngcâu lệnh ngay sau nó
goto labelmọi khối lệnh bao quanhnhãn
returnphương thứcbên gọi
throwmọi thứ cho đến catch phù hợpkhối catch

Câu hỏi thường gặp

break và continue trong C# khác nhau thế nào?

break kết thúc ngay vòng lặp (hoặc switch) trong cùng; chương trình chạy tiếp sau nó. continue chỉ kết thúc lần lặp hiện tại và nhảy sang lần lặp tiếp theo: trong vòng lặp for, phần cập nhật chạy tiếp theo; trong while và do while, điều kiện được kiểm tra tiếp theo; trong foreach, phần tử kế tiếp được lấy ra.

Làm sao thoát khỏi vòng lặp lồng nhau trong C#?

C# không có break kèm nhãn. Có ba lựa chọn: đặt một biến cờ bool và kiểm tra nó ở vòng lặp ngoài, dùng goto nhảy qua cả hai vòng lặp tới một nhãn, hoặc chuyển các vòng lặp vào một phương thức và return từ đó. Cách dùng phương thức thường gọn gàng nhất, vì việc tìm kiếm còn có thêm một cái tên và một giá trị trả về.

break bên trong switch có thoát khỏi vòng lặp trong C# không?

Không. Bên trong một switch nằm trong vòng lặp, break kết thúc phần switch và vòng lặp vẫn chạy tiếp. Để thoát vòng lặp từ bên trong một case, hãy dùng return, một biến cờ được kiểm tra sau switch, hoặc goto tới một nhãn sau vòng lặp. Còn continue bên trong case thì đúng là nhảy sang lần lặp tiếp theo của vòng lặp.

C# có cho phép dùng goto không?

Có. goto label; nhảy tới label: trong cùng phương thức, nhưng không thể nhảy vào bên trong một khối lệnh hay nhảy ra khỏi finally. Bên trong switch, goto case value; và goto default; nhảy giữa các phần. Ngoài việc thoát vòng lặp lồng nhau và fall-through tường minh trong switch, goto hiếm khi là lựa chọn rõ ràng nhất.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu