CodeGym /Các khóa học /C# SELF /Khối finally và toán tử throw

Khối finally và toán tử throw

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

1. Làm quen với khối finally

Dọn dẹp và giải phóng tài nguyên

Hãy tưởng tượng: bạn đang làm thí nghiệm trong phòng lab hóa học và khi xong việc (hoặc thậm chí làm nổ vài ống nghiệm), bạn vẫn phải dọn dẹp chỗ làm, rửa hóa chất và tắt đèn. Đó chính là lý do tồn tại của khối finally — nó luôn chạy sau trycatch, bất kể có exception hay không.


try
{
    // Ở đây viết code "nguy hiểm" có thể gây ra exception
}
catch
{
    // Ở đây bắt và xử lý exception
}
finally
{
    // Đoạn code này luôn chạy, dù có exception hay không
}
Cấu trúc try-catch-finally trong C#

Dùng để làm gì? Chủ yếu là để đảm bảo giải phóng tài nguyên: đóng file, ngắt kết nối database, mở khóa cửa server... Nếu không có finally, sau lỗi một số tài nguyên có thể bị "treo" — và đó là vấn đề thực sự. Ví dụ, file bị khóa, admin cũng không mở được.

Tại sao không viết hết vào catch?

Có thể chỉ giải phóng tài nguyên trong catch không? Lý thuyết thì được. Nhưng nếu mọi thứ ổn, catch sẽ không chạy. Nếu muốn chắc chắn tài nguyên luôn được giải phóng, bạn cần finally.

Trong thực tế, nhiều tình huống không quan trọng thành công hay thất bại — dọn dẹp là bắt buộc. Đó là lý do finally ra đời.

2. Đặc điểm của khối finally

Khi nào finally KHÔNG chạy?

Câu hỏi mẹo! finally chạy luôn luôn. Dù trong tryreturn (thoát sớm khỏi method), hoặc ném exception mới, finally vẫn sẽ được thực thi.


static void Test()
{
    try
    {
        Console.WriteLine("Trước return");
        return;
    }
    finally
    {
        Console.WriteLine("finally vẫn sẽ chạy!");
    }
}

//Gọi Test()
Test();
Sẽ in ra:

// Trước return
// finally vẫn sẽ chạy!
    

Tuy nhiên nếu app của bạn bị "sập cứng" (ví dụ, tắt máy, process bị kill hoặc CLR chết), khối finally sẽ không chạy. Trường hợp này thì bó tay thôi.

Toán tử finally và call stack

Call stack sẽ học kỹ hơn ở bài sau, giờ chỉ cần hiểu đơn giản: nó như chồng các method được gọi, chương trình sẽ "đi xuống" nếu không tìm thấy catch phù hợp.

Một điểm quan trọng nữa: nếu trong try có exception, còn catch không bắt được (ví dụ không có handler phù hợp), chương trình sẽ rời khỏi method hiện tại và tiếp tục đi xuống call stack cho đến khi gặp catch phù hợp. Nhưng trước đó, finally sẽ luôn chạy ở mỗi level của stack.

Điều này đảm bảo tài nguyên luôn được giải phóng đúng, kể cả khi có lỗi lạ.

3. Toán tử throw: tự ném exception

throw là gì và dùng để làm gì

Đôi khi chỉ bắt exception là chưa đủ — bạn cần tự tạo lỗi và "ném" nó ra ngoài. Đó là lý do có toán tử throw.


throw new Exception("Đây là lỗi đặc biệt của tôi!");
Tạo và ném exception của riêng bạn

throw nói với CLR: "Tôi vừa phát hiện điều gì đó rất tệ, ném Exception này ra, ai gọi code này thì xử lý tiếp đi".

throw mà không tạo exception mới

Có thể dùng throw; trong khối catch để ném lại exception vừa bắt — ví dụ bạn xử lý một phần lỗi, còn lại muốn chuyển trách nhiệm cho code bên trên.


try
{
    DangerousOperation();
}
catch (Exception ex)
{
    LogError(ex);
    throw; // ném lại exception hiện tại, giữ nguyên call stack
}
Ném lại exception hiện tại và giữ nguyên call stack

Nếu bạn viết throw ex;, thông tin về call stack sẽ bị mất — đây là thói quen xấu.

4. finally hoạt động thế nào với throw?

finally vẫn chạy kể cả khi ném exception

Thử xem điều gì xảy ra nếu trong trythrow, nhưng vẫn có finally:


try
{
    Console.WriteLine("Trước lỗi...");
    throw new Exception("Lỗi trong try!");
}
catch
{
    Console.WriteLine("Catch bắt lỗi.");
}
finally
{
    Console.WriteLine("Finally đã chạy.");
}
finally vẫn chạy kể cả khi ném exception

Kết quả:


Trước lỗi...
Catch bắt lỗi.
Finally đã chạy.

Và nếu không có catch phù hợp, finally vẫn chạy trước khi chương trình crash.

Lưu ý: nếu trong finally cũng throw thì sao?

Nếu trong finallythrow, exception này sẽ ghi đè exception trước đó. Nghĩa là thông tin về lỗi trong try/catch sẽ bị mất. Vì vậy không nên throw trong finally nếu bên trong đã có exception.


try
{
    throw new Exception("Lỗi trong try");
}
finally
{
    throw new Exception("Lỗi trong finally");
}
// Kết quả: chỉ thấy "Lỗi trong finally"
Exception trong finally sẽ ghi đè exception trước đó

5. Lời khuyên thực tế và lỗi phổ biến

Những gì newbie hay quên

  • Không dùng khối finally để giải phóng tài nguyên, chỉ dựa vào catch.
  • Đặt code có thể gây exception mới bên trong finally — dễ gây lỗi bất ngờ.
  • Quên rằng return trong try không "né" được finally — nó luôn chạy.

Thay thế finally: chọn cái nào?

Từ khi có using (sẽ học kỹ sau), việc giải phóng tài nguyên tiện hơn, nhưng thực chất using bên trong vẫn dùng finally. Với các tình huống đặc biệt (ví dụ mở khóa hoặc gửi thông báo lỗi) — vẫn phải dùng finally.

Tại sao finally hay bị hỏi khi phỏng vấn

Bất kỳ recruiter nào từng viết server chịu tải cao đều thích hỏi về finally. Thường hỏi: "Nếu trong tryreturn, còn trong finallythrow thì sao?" hoặc "Có đảm bảo giải phóng tài nguyên khi có exception không?". Giờ bạn không chỉ có câu trả lời mà còn hiểu bản chất. Bạn sẽ giải thích được finally hoạt động thế nào, dùng khi nào, tại sao không thể thiếu nó trong code nghiêm túc.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION