CodeGym /Các khóa học /C# SELF /Cú pháp try-catch: xử lý lỗi

Cú pháp try-catch: xử lý lỗi

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Trong thế giới lý tưởng thì code phải hoàn hảo. Nhưng tiếc là, kể cả coder cẩn thận nhất cũng sẽ gặp những tình huống bất ngờ: không tìm thấy file, user nhập chuỗi thay vì số, mạng tự dưng die, hoặc database trả về thứ gì đó không như mong đợi.

Nếu không bắt những vấn đề như vậy — chương trình sẽ "sập", đôi khi kèm theo thông báo lỗi khó hiểu và stack trace (ngăn xếp gọi hàm). Đó không phải là thứ user hay team support mong muốn. Bạn cần học cách "bắt" lỗi và phản ứng lại: kết thúc chương trình một cách nhẹ nhàng, in ra thông báo dễ hiểu hoặc thậm chí sửa lỗi ngay lập tức.

try-catch — là cách để nói với compiler và runtime: "Thử chạy đoạn code này đi. Nếu có vấn đề gì, đừng hoảng, cho mình xử lý!"

2. Cú pháp cơ bản của try-catch

Cấu trúc tổng thể rất đơn giản:


try
{
    // Ở đây mình viết code nguy hiểm (hoặc có thể nguy hiểm)
}
catch (ExceptionType variableName)
{
    // Ở đây mình viết phải làm gì nếu có exception kiểu ExceptionType
}
Cú pháp cơ bản try-catch
  • Block try — là "vùng nguy hiểm". Cho vào đó những thao tác có thể "nổ".
  • Block catch — bắt exception kiểu xác định. Nếu trong try có exception, chương trình sẽ nhảy ngay vào catch đầu tiên phù hợp, và code sau chỗ "có vấn đề" trong try sẽ không chạy nữa.

Ví dụ: xử lý exception đơn giản

Giả sử cái mini-calculator của mình giờ biết chia số. Nhưng user có thể nhập số 0! Xem thử nếu không xử lý lỗi thì sao:

int a = 10;
int b = 0;
int result = a / b; // BÙM! System.DivideByZeroException
Console.WriteLine(result);

Chương trình sẽ crash với lỗi DivideByZeroException:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

Giờ "sửa" lại bằng try-catch:

int a = 10;
int b = 0;

try
{
    int result = a / b; // thao tác nguy hiểm
    Console.WriteLine("Kết quả chia: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Ôi! Không thể chia cho 0: " + ex.Message);
}

Giờ chương trình không "sập" nữa. Console sẽ hiện thông báo không thể chia cho 0:

Ôi! Không thể chia cho 0: Attempted to divide by zero.

3. try-catch hoạt động như thế nào: từng bước một

Thực ra trong try có thể có bao nhiêu dòng code cũng được. Nếu một thao tác nào đó trong block ném ra exception, chương trình sẽ nhảy ngay vào catch phù hợp gần nhất. Tất cả những gì viết sau chỗ lỗi trong block try sẽ không chạy nữa.

Xem ví dụ lớn hơn chút:

try
{
    Console.WriteLine("Bắt đầu...");
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[1]);    // ok
    Console.WriteLine(numbers[5]);    // lỗi: index vượt ngoài mảng
    Console.WriteLine("Dòng này sẽ không xuất hiện!");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Lỗi: Cố truy cập phần tử không tồn tại trong mảng");
}

Kết quả trên console sẽ là:

Bắt đầu...
2
Lỗi: Cố truy cập phần tử không tồn tại trong mảng

Process finished with exit code 0.

Chương trình này hoạt động thế nào?

  • In ra "Bắt đầu...".
  • Sau đó in phần tử numbers[1] — là 2.
  • Khi tới numbers[5], "nổ", ném ra IndexOutOfRangeException.
  • Chương trình nhảy ngay vào block catch, bỏ qua Console.WriteLine("Dòng này sẽ không xuất hiện!");
  • Console sẽ hiện thông báo từ catch.

4. Có thể xử lý các loại lỗi khác nhau theo cách khác nhau không?

Có chứ! Bạn có thể viết nhiều block catch để phản ứng khác nhau với từng loại exception. Rất tiện: ví dụ, một thao tác liên quan tới đọc file (có thể bị FileNotFoundException), thao tác khác là chia số (có thể bị DivideByZeroException).

try
{
    // Code nguy hiểm của bạn
}
catch (DivideByZeroException)
{
    Console.WriteLine("Lỗi chia cho 0");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Lỗi: index vượt ngoài mảng");
}
catch (Exception ex)
{
    Console.WriteLine("Có lỗi bất ngờ nào đó: " + ex.Message);
}

Ở đây block catch cuối cùng với kiểu Exception — là "catch-all". Nó sẽ bắt mọi exception khác mà các block trước không bắt được. Nhưng nhớ: nếu bạn viết nó đầu tiên, các block sau sẽ không bao giờ tới! Luôn để catch "rộng" ở cuối.

5. Object exception trông như thế nào?

Trong block catch bạn có thể (không bắt buộc) khai báo biến — ví dụ, catch (Exception ex). Biến này chứa toàn bộ thông tin về lỗi: message, loại lỗi, stack trace, exception lồng nhau.

Ví dụ nhỏ:

try
{
    string? text = null;
    Console.WriteLine(text.Length); // Ối! NullReferenceException
}
catch (NullReferenceException ex)
{
    Console.WriteLine("Bắt được exception: " + ex.Message);
    Console.WriteLine("Stack trace: " + ex.StackTrace);
}

Cách này cực kỳ hữu ích khi debug lỗi khó: bạn luôn biết chương trình "vấp" ở đâu và vì sao.

6. Bắt lỗi và tiếp tục chạy tiếp

Trong thực tế, nhất là app cho user, lỗi không phải là tận thế. User nhập sai đường dẫn file, chuyện thường thôi. Không cần phải cho chương trình crash. Thay vào đó, mình nhẹ nhàng bảo user thử lại.

Ví dụ dưới đây là cách đơn giản mà hiệu quả: mình bọc việc đọc file trong vòng lặp, nếu có lỗi thì báo cho user và cho nhập lại. Không hoảng, không màn hình đỏ.

bool success = false;
while (!success)
{
    Console.Write("Nhập tên file: ");
    string fileName = Console.ReadLine() ?? "";

    try
    {
        string content = File.ReadAllText(fileName);
        Console.WriteLine("Đọc file thành công!");
        success = true; // YEAH!
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("Lỗi: không tìm thấy file. Thử lại nhé.");
    }
}

Ý chính ở đây — không chỉ bắt exception mà còn xử lý hợp lý: không cho chương trình sập, mà chuyển sang chế độ "thử lại cho đúng". User nhận được gợi ý rõ ràng, chương trình có cơ hội thứ hai, còn bạn thì được cảm ơn và tôn trọng.

7. Những lỗi phổ biến khi dùng try-catch

Lỗi số 1: Bọc toàn bộ code vào một try-catch duy nhất.
Nhìn thì hấp dẫn — một try, một catch (Exception), tưởng như "an toàn tuyệt đối". Nhưng thực tế bạn mất kiểm soát. Không biết lỗi ở đâu, phản ứng với lỗi gì. Kết quả là mất hết khả năng chẩn đoán.

Lỗi số 2: Bắt exception nhưng không làm gì cả.
Ví dụ thực tế:

try
{
    // cái gì đó có thể nguy hiểm
}
catch
{
    // im lặng...
}
Kiểu code này như nói: "Có gì đó sai... kệ nó". Rất nguy hiểm: chương trình có thể tiếp tục ở trạng thái sai, bạn còn không biết là có lỗi. Ít nhất hãy in ra thông báo hoặc log lại. Đừng bao giờ để catch rỗng!

Lỗi số 3: Bắt exception quá chung chung.
Nếu bạn bắt Exception, dù biết có thể gặp FormatException hoặc FileNotFoundException, — bạn tự tước đi khả năng phản ứng đúng với từng vấn đề cụ thể. catch càng hẹp, càng chính xác thì chương trình càng dễ kiểm soát khi có sự cố.

8. Khi nào KHÔNG nên dùng exception

Đôi khi newbie dùng exception... như cơ chế điều khiển logic bình thường, ví dụ:

try
{
    if (x < 0) throw new Exception("x phải >= 0");
}
catch (Exception)
{
    // rollback trạng thái, tiếp tục...
}

Đừng làm vậy! Exception là công cụ cho tình huống bất ngờ, hiếm gặp (lỗi đọc file, mất mạng, v.v.), không phải để kiểm tra dữ liệu user. Với trường hợp này, hãy dùng lệnh điều kiện (if, else) và trả về giá trị đặc biệt nếu có gì đó sai.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Chia với xử lý lỗi
Chia với xử lý lỗi
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Xử lý nhập số không hợp lệ
Xử lý nhập số không hợp lệ
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION