CodeGym /Các khóa học /C# SELF /Nhiều catch và bộ lọc trong C#

Nhiều catch và bộ lọc trong C#

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

1. Giới thiệu

Khi chương trình phức tạp hơn một chút so với Hello, world!, bạn sẽ nhanh chóng gặp bài toán: xử lý các lỗi khác nhau theo cách khác nhau. Hãy tưởng tượng: bạn làm việc với file, mạng, database — và với mỗi trường hợp lỗi lại cần phản ứng khác nhau. Ví dụ, nếu thiếu file thì có thể gợi ý người dùng chọn file khác, còn nếu lỗi mạng — thử lại hoặc hiện thông báo vui vẻ "Kiểm tra dây mạng đi, biết đâu con mèo lại cắn đứt rồi".

Trong C#, để làm việc này thì dùng nhiều khối catch liên tiếp. Mỗi khối sẽ chuyên xử lý một loại exception (và các class con của nó).

Cấu trúc nhiều catch


try
{
    // Ở đây — code có thể ném ra nhiều exception khác nhau
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"Không tìm thấy file: {ex.Message}");
}
catch (IOException ex) // bắt mọi lỗi I/O, trừ FileNotFoundException
{
    Console.WriteLine($"Lỗi nhập/xuất: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Có gì đó sai sai: {ex.Message}");
}
Nhiều khối catch cho các loại lỗi khác nhau

Lưu ý! Compiler sẽ đi từ trên xuống dưới và chọn khối đầu tiên phù hợp với loại exception. Nếu bắt được FileNotFoundException, nó sẽ không đi tiếp xuống dưới nữa.

Minh họa "chuỗi"

Loại exception Bị bắt bởi khối nào?
FileNotFoundException Khối catch đầu tiên
IOException (khác) Khối catch thứ hai
ArgumentException Khối catch thứ ba (chung)

Tại sao không nên trộn lẫn?
Cái "bẫy" rộng nhất — ví dụ catch (Exception) — luôn đặt cuối cùng, nếu không nó sẽ "nuốt" hết mọi exception trước khi các khối catch chuyên biệt được thực thi.

Nó giống như tắt hết cảm biến báo cháy trong tòa nhà chỉ vì một cái kêu do toaster cháy: các vụ cháy sau đó hệ thống sẽ không phát hiện ra nữa.

Ví dụ thực tế

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            double result = CalculateAverageAgeFromFile("users.txt");
            Console.WriteLine($"Tuổi trung bình — {result}");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("Lỗi: không tìm thấy file. Kiểm tra lại đường dẫn.");
        }
        catch (FormatException ex)
        {
            Console.WriteLine("Lỗi dữ liệu: không đọc được tuổi.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Lỗi khác: {ex.Message}");
        }
    }

    static double CalculateAverageAgeFromFile(string filePath)
    {
        // (Triển khai: đọc file, parse tuổi, tính trung bình)
        // ...
        throw new NotImplementedException();
    }
}

Ở đây ta tách rõ: làm gì nếu không có file (FileNotFoundException), và làm gì nếu dữ liệu trong file bị "lỗi" (FormatException). Các trường hợp còn lại sẽ do khối thứ ba "dự phòng" xử lý.

2. Bộ lọc catch: bắt lỗi tinh tế

Nhiều khối catch rất tiện, nhưng đôi khi vẫn chưa đủ. Có lúc, trong cùng một loại exception, bạn lại muốn phản ứng khác nhau tùy hoàn cảnh.

Ví dụ, nếu mạng không hoạt động vì hết internet — ta có thể thử kết nối lại. Còn nếu server không trả lời — có thể nên hiện thông báo khác.

Đây là lúc bộ lọc catch xuất hiện — một siêu tính năng của C#, cho phép bắt exception không chỉ theo loại mà còn theo điều kiện bổ sung.

Cú pháp bộ lọc when


catch (IOException ex) when (ex.Message.Contains("không có đĩa"))
{
    Console.WriteLine("Oops! Có vẻ bạn vừa rút USB ra.");
}
catch (IOException ex)
{
    Console.WriteLine("Lỗi nhập/xuất khác: " + ex.Message);
}
Bộ lọc catch when để làm rõ điều kiện bắt lỗi

Ở đây khối đầu chỉ bắt IOException mà message chứa cụm "không có đĩa" — còn lại sẽ vào khối thứ hai.

Ứng dụng thực tế

Bộ lọc cực kỳ hữu ích khi xử lý lỗi mạng, khi cần quyết định dựa trên thuộc tính bên trong của exception: thử lại hay chỉ báo lỗi.

Ví dụ nữa: giả sử trong hàm parse file, ta muốn không chỉ bắt FormatException "chung chung", mà riêng trường hợp lỗi liên quan đến tuổi (ví dụ, tuổi ghi là "abc" thay vì số).

catch (FormatException ex) when (ex.Message.Contains("tuổi"))
{
    Console.WriteLine("Lỗi: không đọc được tuổi. Kiểm tra lại dữ liệu.");
}
catch (FormatException ex)
{
    Console.WriteLine("Lỗi định dạng dữ liệu: " + ex.Message);
}

Bộ lọc và hiệu năng

Bộ lọc rất tiện, nhưng nhớ rằng điều kiện trong bộ lọc được tính trước khi vào khối catch, nên nếu không thỏa mãn — code trong khối sẽ không chạy.

À, nếu bộ lọc tự ném exception (ví dụ, trong biểu thức when bị chia cho 0) — thì exception đó catch này không bao giờ bắt được. Cẩn thận nhé.

3. Kết hợp nhiều catch và bộ lọc

Hãy tưởng tượng trong dự án bạn cần xử lý không chỉ loại exception mà còn cả trạng thái bên trong của nó. Ví dụ, khi làm việc với IOException, bạn muốn phản ứng khác nhau nếu lỗi do quyền truy cập hoặc do hết dung lượng ổ đĩa.


try
{
    File.AppendAllText("log.txt", "Bản ghi mới\n");
}
catch (IOException ex) when (ex.Message.Contains("Hết chỗ"))
{
    Console.WriteLine("Lỗi: Ổ đĩa đã hết dung lượng!");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Lỗi: không có quyền ghi file. Hãy chạy bằng quyền admin.");
}
catch (IOException ex)
{
    Console.WriteLine("Lỗi đĩa khác: " + ex.Message);
}
Kết hợp bộ lọc và các loại catch khác nhau

Ở đây ta dùng cả bộ lọc lẫn phân loại lỗi. Sự linh hoạt này rất quý khi phát triển ứng dụng phức tạp, nơi cần phản hồi rõ ràng với các lỗi thường gặp.

4. Đặc điểm và "bẫy" của bộ lọc và nhiều catch

  • Bộ lọc catch có thể dùng biến exception (ex), nhưng không thể thay đổi nó.
  • Nếu bạn ném exception trong when, nó sẽ không được khối catch đó bắt — nó sẽ đi lên stack như thể không có bộ lọc.
  • Logic bộ lọc là một phần của "hợp đồng": đồng đội bạn cần biết không phải mọi IOException đều bị bắt — chỉ khi điều kiện đúng thôi.
  • Nếu bạn chỉ dùng một catch cho cả hàm, nhưng lại có bộ lọc, có thể bạn sẽ bỏ sót một số exception. Nếu không chắc — hãy dùng nhiều khối rõ ràng.
  • Trong app lớn, có thể dùng bộ lọc để xử lý tập trung, ví dụ chỉ log lỗi nghiêm trọng.

5. Kịch bản phỏng vấn và thực tế

Biết về bộ lọc và nhiều catch không chỉ để code đẹp hay đạt điểm clean code cao. Đây là kỹ năng thực tế, có thể bị hỏi khi phỏng vấn, nhất là nếu vị trí liên quan đến bảo trì app lớn, phức tạp, phân tán.

Ví dụ câu hỏi:

  • Làm sao trong C# xử lý các trường hợp lỗi đọc file khác nhau, để mỗi tình huống (không có file, ổ đĩa bận, dữ liệu hỏng) hiện thông báo riêng?
  • Làm sao không "nuốt" mất lỗi quan trọng nếu đặt khối catch (Exception) chung?
  • Bộ lọc catch dùng để làm gì? Có thể dùng throw trong đó không?
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Xử lý ngoại lệ với nhiều khối `catch`
Xử lý ngoại lệ với nhiều khối `catch`
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng bộ lọc `catch`
Sử dụng bộ lọc `catch`
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Làm quen với ngoại lệ
Stack trace và tự tạo ngoại lệ của riêng bạn
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION