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 try và catch, 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
}
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 try có return (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.
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!");
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ế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 try có throw, 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.");
}
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 finally có throw, 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"
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 try có return, còn trong finally có throw 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.
GO TO FULL VERSION