Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 19 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
19
Dung lượng
121,66 KB
Nội dung
Những đối tượng ngoại lệ Những đối tượng ngoại lệ Bởi: Khuyet Danh Đối tượng System.Exception cung cấp số phương thức thuộc tính hữu dụng Thuộc tính Message cung cấp thông tin ngoại lệ, lý ngoại lệ phát sinh Thuộc tính Message thuộc tính đọc, đoạn chương trình phát sinh ngoại lệ thiết lập thuộc tính Message đối mục cho khởi dựng ngoại lệ Thuộc tính HelpLink cung cấp liên kết để trợ giúp cho tập tin liên quan đến ngoại lệ Đây thuộc tính đọc Thuộc tính StackTrace thuộc tính đọc thiết lập CLR Trong ví dụ 13.6 thuộc tính Exception.HelpLink thiết lập truy cập để cung cấp thông tin cho người sử dụng ngoại lệ DivideByZeroException Thuộc tính StackTrace ngoại lệ sử dụng để cung cấp thông tin stack cho câu lệnh lỗi Một thông tin stack cung cấp hàng loạt gọi stack phương thức gọi mà dẫn đến ngoại lệ phát sinh Làm việc với đối tượng ngoại lệ namespace Programming_CSharp { using System; public class Test { public static void Main() { Test t = new Test(); t.TestFunc(); 1/19 Những đối tượng ngoại lệ } // chia hai số xử lý ngoại lệ public void TestFunc() { try { Console.WriteLine("Open file here"); double a = 12; double b = 0; Console.WriteLine("{0} /{1} = {2}", a, b, DoDivide(a,b)); Console.WriteLine("This line may or not print"); } catch (System.DivideByZeroException e) { Console.WriteLine("\nDivideByZeroException! Msg: e.Message); Console.WriteLine("\nHelpLink: e.HelpLink); Console.WriteLine("\nHere’s e.StackTrace); a stack trace: {0}", {0}", {0}\n", } catch { Console.WriteLine("Unknown exception caught"); 2/19 Những đối tượng ngoại lệ } } // thực phép chia hợp lệ public double DoDivide( double a, double b) { if ( b == 0) { DivideByZeroException e = new DivideByZeroException(); e.HelpLink = "http://www.hcmunc.edu.v n"; throw e; } if ( a == 0) { throw new ArithmeticException(); } return a/b; } } } Kết quả: Open file here 3/19 Những đối tượng ngoại lệ DivideByZeroExceptión Msg: Attempted to divide by zero HelpLink: http://www.hcmuns.edu.vn Here’s a stack trace: at Programming_CSharp.Test.DoDivide(Double c, Double b) in c:\ exception06.cs: line 56 at Programming_CSharp.Test.TestFunc() in exception06.cs: line 22 Close file here Trong đoạn kết trên, danh sách trace stack hiển thị theo thứ tự ngược lại thứ tự gọi Nó hiển thị lỗi phương thức DoDivde(), phương thức gọi từ phương thức TestFunc() Khi phương thức gọi lồng nhiều cấp, thông tin stack giúp hiểu thứ tự phương thức gọi Trong ví dụ này, việc đơn giản phát sinh DidiveByZeroException, tạo thể hện ngoại lệ: DivideByZeroException e = new DivideByZeroException(); Chúng ta không truyền vào thông điệp chúng ta, nên thông điệp mặc định in ra: DivideByZeroException! Msg: Attemped to divide by zero Ở bổ sung dòng lệnh bên để truyền vào thông điệp tùy chọn sau: new DivideByZeroException(“You tried to divide by zero which is not meaningful”); Trước phát sinh ngoại lệ, thiết lập thuộc tính HelpLink sau: e.HelpLink = “http://www.hcmunc.edu.vn”; Khi ngoại lệ bắt giữ, chương trình in thông điệp HelpLink hình: catch (System.DivideByZeroException e) { 4/19 Những đối tượng ngoại lệ Console.WriteLine("\nDivideByZeroException! Msg: e.Message); Console.WriteLine("\nHelpLink: e.HelpLink); {0}", {0}", } Việc làm cho phép cung cấp thông tin hữu ích cho người sử dụng Thêm vào thông tin stack đưa cách sử dụng thuộc tính StackTrace đối tượng ngoại lệ: Console.WriteLine("\n e.StackTrace); Here’s a stack trace: {0}\n", Kết vết stack xuất ra: Here’s a stack trace: at Programming_CSharp.Test.DoDivide(Double c, Double b) in c:\ exception06.cs: line 56 at Programming_CSharp.Test.TestFunc() in exception06.cs: line 22 Lưu ý rằng, phần đường dẫn viết tắt, kết bạn khác tí Bảng sau mô tả số lớp ngoại lệ chung khai báo bên namespace System Các ngoại lệ thường xuất CÁC LỚP NGOẠI LỆ Tên ngoại lệ Mô tả MethodAccessException Lỗi truy cập, truy cập đến thành viên hayphương thức không truy cập ArgumentException Lỗi tham số đối mục ArgumentNullException Đối mục Null, phương thức truyền đối mụcnull không chấp nhận ArithmeticException Lỗi liên quan đến phép toán ArrayTypeMismatchException Kiểu mảng không hợp, cố lưu trữ kiểu khôngthích hợp vào mảng 5/19 Những đối tượng ngoại lệ DivideByZeroException Lỗi chia zero FormatException Định dạng không xác đối mục IndexOutOfRangeException Chỉ số truy cập mảng không hợp lệ, dùng nhỏ số nhỏ hay lớn số lớn củamảng InvalidCastException Phép gán không hợp lệ MulticastNotSupportedException Multicast không hỗ trợ, việc kết hợp haidelegate không NotFiniteNumberException Không phải số hữu hạn, số không hợp lệ NotSupportedException Phương thức không hỗ trợ, gọi phươngthức không tồn bên lớp NullReferenceException Tham chiếu null không hợp lệ OutOfMemoryException Out of memory OverflowException Lỗi tràn phép toán StackOverflowException Tràn stack TypeInitializationException Kiểu khởi tạo sai, khởi dựng tĩnh có lỗi Tạo riêng ngoại lệ CLR cung cấp kiểu liệu ngoại lệ bản, ví dụ trước tạo vài kiểu ngoại lệ riêng Thông thường cần thiết phải cung cấp thông tin mở rộng cho khối catchkhi ngoại lệ phát sinh Tuy nhiên, có lúc muốn cung cấp nhiều thông tin mở rộng khả đặc biệt cần thiết ngoại lệ mà tạo Chúng ta dễ dàng tạo ngoại lệ riêng, hay gọi ngoại lệ tùy chọn (custom exception), điều bắt buộc với ngoại lệ chúng phải dẫn xuất từ System.ApplicationException Ví dụ sau minh họa việc tạo ngoại lệ riêng Tạo ngoại lệ riêng namespace Programming_CSharp { 6/19 Những đối tượng ngoại lệ using System; // tạo ngoại lệ riêng public class System.ApplicationException MyCustomException : { public MyCustomException( string message): base(message) { } } public class Test { public static void Main() { Test t = new Test(); t.TestFunc(); } // chia hai số xử lý ngoại lệ public void TestFunc() { try { Console.WriteLine("Open file here"); 7/19 Những đối tượng ngoại lệ double a = 0; double b = 5; Console.WriteLine("{0} /{1} = {2}", a, b, DoDivide(a,b)); Console.WriteLine("This line may or not print"); } catch (System.DivideByZeroException e) { Console.WriteLine("\nDivideByZeroException! e.Message); Msg: {0}", Console.WriteLine("\nHelpLink: {0}", e.HelpLink); } catch (MyCustomException e) { Console.WriteLine("\nMyCustomException! Msg: e.Message); Console.WriteLine("\nHelpLink: e.HelpLink); {0}", {0}", } catch { Console.WriteLine("Unknown excepiton caught"); } finally { 8/19 Những đối tượng ngoại lệ Console.WriteLine("Close file here."); } } // thực phép chia hợp lệ public double DoDivide( double a, double b) { if ( b == 0) { DivideByZeroException e = new DivideByZeroException(); e.HelpLink = "http://www.hcmunc.edu." n"; throw e; } if ( a == 0) { MyCustomException zero divisor"); e = new MyCustomException("Can’t have e.HelpLink = "http://www.hcmuns.edu.v n" ; throw e; } return a/b; } } 9/19 Những đối tượng ngoại lệ } Lớp MyCustomException dẫn xuất từ System.ApplicationException lớp thực thi hay khai báo hàm khởi dựng Hàm khởi dựng lấy tham số chuỗi truyền cho lớp sở Trong trường hợp này, lợi ích việc tạo ngoại lệ làm bật điều mà chuơng trình muốn minh họa, tức không cho phép số chia zero Sử dụng ngoại lệ ArithmeticException tốt ngoại lệ tạo Nhưng làm nhầm lẫn cho người lập trình khác phép chia với số chia zero lỗi số học Phát sinh lại ngoại lệ Giả sử muốn khối catch thực vài hành động sau phát sinh lại ngoại lệ bên khối catch (trong hàm gọi) Chúng ta phép phát sinh lại ngoại lệ hay phát sinh lại ngoại lệ khác Nếu phát sinh ngoại lệ khác, phải nhúng ngoại lệ ban đầu vào bên ngoại lệ để phương thức gọi hiểu lai lịch nguồn gốc ngoại lệ Thuộc tính InnerException ngoại lệ cho phép truy cập ngoại lệ ban đầu Bởi InnerException ngoại lệ, nên có ngoại lệ bên Do vậy, toàn dây chuyền ngoại lệ đóng tổ (nest) ngoại lệ với ngoại lệ khác Giống lật đật, chứa đến lượt bên lại chứa Phát sinh lại ngoại lệ & ngoại lệ inner namespace Programming_CSharp { using System; // tạo ngoại lệ riêng public class MyCustomException : System.Exception { public MyCustomException( string message, Exception inner): 10/19 Những đối tượng ngoại lệ base(message, inner) { } } public class Test { public static void Main() { Test t = new Test(); t.TestFunc(); } // chia hai số xử lý ngoại lệ public void TestFunc() { try { DangerousFunc1(); } catch (MyCustomException e) { Console.WriteLine("\n{0}", e.Message); 11/19 Những đối tượng ngoại lệ Console.WriteLine("Retrieving exception Exception inner =e.InnerException; history "); while ( inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; } } } { try { DangerousFunc2(); } MyCustomException ex = new MyCustomException("E3 - Custom Exception Situation", e); throw ex; } } public void DangerousFunc2() { try 12/19 Những đối tượng ngoại lệ { DangerousFunc3(); } catch (System.DivideByZeroException e) { Exception ex = new Exception("E2 - Func2 caught divide by zero", e); throw ex; } } public void DangerousFunc3() { try { DangerousFunc4(); } catch (System.ArithmeticException) { throw; } catch (System.Exception) { 13/19 Những đối tượng ngoại lệ Console.WriteLine("Exception handled here."); } } public void DangerousFunc4() { throw new Exception"); DivideByZeroException("E1 - DivideByZero } } } Kết quả: E3 – Custom Exception Situation! Retrieving exception history E2 - Func2 caught divide by zero E1 – DivideByZeroException Để hiểu rõ ta dùng trình debugger để chạy bước chương trình ta hiểu rõ bước thực thi việc phát sinh ngoại lệ Chương trình bắt đầu với việc gọi hàm DangerousFunc1() khối try: try { DangerousFunc1(); } DangerousFunc1() gọi DangerousFunc2(), DangerousFunc2() lại gọi DangerousFunc3(), cuối DangerousFunc3() gọi DangerousFunc4() Tất việc gọi điều nằm khối try Cuối cùng, DangerousFunc4() phát sinh ngoại lệ DivideByzeroException Ngoại lệ bình thường có chứa thông điệp bên nó, 14/19 Những đối tượng ngoại lệ tự dùng thông điệp Để dễ theo dõi đưa vào chuỗi xác nhận kiện diễn Ngoại lệ phát sinh DangerousFunc4() bắt khối catch hàm DangerousFunc3() Khối catch DangerousFunc3() bắt ngoại lệ Arithmetic- Exception ( DivideByZeroException), không thực hành động mà đơn giản phát sinh lại ngoại lệ: catch ( System.ArithmeticException) { throw; } Cú pháp để thực phát sinh lại ngoại lệ mà bổ sung hay hiệu chỉnh : throw Do ngoại lệ phát sinh cho DangerousFunc2(), khối catch DangerousFunc2() thực vài hành động tiếp tục phát sinh ngoại lệ có kiểu Trong hàm khởi dựng ngoại lệ mới, DangerousFunc2() truyền chuỗi thông điệp (“E2 - Func2 caught divide by zero”) ngoại lệ ban đầu Do ngoại lệ ban đầu (E1) trở thành ngoại lệ bên ngoại lệ (E2) Sau hàm DangerousFunc2() phát sinh ngoại lệ (E2) cho hàm DangerousFunc1() DangerousFunc1() bắt giữ ngoại lệ này, làm số công việc tạo ngoại lệ có kiểu MyCustomException, truyền vào hàm khởi dựng ngoại lệ chuỗi (“E3 – Custom Exception Situation!”) ngoại lệ bắt giữ (E2) Chúng ta nên nhớ ngoại lệ bắt giữ ngoại lệ có chứa ngoại lệ DivideByZeroException (E1) bên Tại thời điểm này, có ngoại lệ kiểu MyCustomException (E3), ngoại lệ chứa bên ngoại lệ kiểu Exception (E2), đến lượt chứa ngoại lệ kiểu DivideByZeroException (E1) bên Sau ngoại lệ phát sinh cho hàm TestFunc; Khi khối catch TestFunc thực in thông điệp ngoại lệ : E3 – Custom Exception Situation! sau ngoại lệ bên lấy thông qua vòng lặp while: while ( inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; } Kết chuỗi ngoại lệ phát sinh bắt giữ: Retrieving exception history E2 - Func2 caught divide by zero E1 – DivideByZero Exception 15/19 Những đối tượng ngoại lệ Câu hỏi câu trả lời Câu hỏi trả lời Việc sử dụng catch tham số có nhiều sức mạnh chúng bắt tất ngoại lệ Tại không luôn sử dụng câu lệnh catch tham số để bắt lỗi? Mặc dù sử dụng catch có nhiều sức mạnh, làm nhiều thông tin quan trọng ngoại lệ phát sinh Khi xác loại ngoại lệ xảy khó bảo trì khắc phục ngoại lệ sau Về phía người dùng Nếu chương trình gặp ngoại lệ mà thông báo rõ ràng cho nguời dùng làm cho họ hoang mang, đổ lỗi cho chương trình không tốt lỗi ta Ví dụ lỗi hết tài nguyên nhớ người dùng sử dụng nhiều chương trình hoạt động lúc Tóm lại nên sử dụng catch với tham số chi tiết để thực tốt việc quản lý ngoại lệ phát sinh Có phải tất ngoại lệ đối xử cách bình đẳng? Không phải, có hai loại ngoại lệ, ngoại lệ hệ thống ngoại lệ chương trình ứng dụng Ngoại lệ chương trình ứng dụng không kết thúc chương trình Còn ngoại lệ hệ thống kết thúc chương trình Nói chung ngoại lệ xuất trước Hiện người ta chia nhiều mức độ ngoại lệ tùy theo mức độ ngoại lệ mà chương trình nhận ứng xử khác Để biết thêm chi tiết đọc thêm tài liệu NET Framework xử lý ngoại lệ Như câu trả lời bên phải tìm hiểu nhiều ngoại lệ cách thức xử lý ngoại lệ chúng phát sinh? Việc xây dựng chương trình ứng dụng phức tạp, chương trình tiếm ẩn yếu tố không ổn định phát sinh ngoại lệ dẫn đến lỗi không mong muốn Việc thực bắt giữ ngoại lệ cần thiết chương trình, cho phép xây dựng chương trình hoàn thiện xử lý thông điệp ngoại lệ tốt Tìm hiểu ngoại lệ đem đến cho nhiều kinh nghiệm việc xây dựng chương trình phức tạp Câu hỏi thêm Hãy cho biết từ khóa sử dụng để xử lý ngoại lệ? Phân biệt lỗi ngoại lệ? 16/19 Những đối tượng ngoại lệ Khi thực việc bắt giữ ngoại lệ Nếu có nhiều mức bắt giữ ngoại lệ thực mức Từ chi tiết đến tổng quát, hay từ tổng quát đến chi tiết? Ý nghĩa từ khóa finally việc xử lý ngoại lệ? Câu lệnh dùng để phát sinh ngoại lệ? Loại sau nên xử lý theo ngoại lệ loại nên xử lý mã lệnh thông thường? a Giá trị nhập vào người dùng không nằm mức cho phép b Tập tin không viết mà thực viết c Đối mục truyền vào cho phương thức chứa giá trị không hợp lệ d Đối mục truyền vào cho phương thức chứa kiểu không hợp lệ Câuhỏi 7: Nguyên nhân dẫn đến phát sinh ngoại lệ? Khi ngoại lệ xuất hiện? a Trong tạo mã nguồn b Trong biên dịch c Trong thực thi chương trình d Khi yêu cầu đựơc đưa ta người dùng cuối Khi khối lệnh finally thực hiện? Trong namespace chức lớp liên quan đến việc xử lý ngoại lệ? Hãy cho biết số lớp xử lý ngoại lệ quan trọng namespace này? Bài tập Hãy viết đoạn lệnh để thực việc bắt ngoại lệ liên quan đến câu lệnh sau đây: Ketqua = Sothu1 / Sothu2; Chương trình sau có vấn đề Hãy xác định vấn đề phát sinh ngoại lệ chạy chương trình Và viết lại chương trình hoàn chỉnh gồm lệnh xử lý ngoại lệ: - 17/19 Những đối tượng ngoại lệ using System; public class Tester { public static void Main() { uint so1=0; int so2, so3; so2 = -10; so3 = 0; // tính giá trị lại so1 -= 5; so2 = 5/so3; // xuất kết Console.WriteLine("So 1: {0}, So 2:{1}", so1, so2); } } Chương trình sau dẫn đến ngoại lệ hay không? Nếu có cho biết ngoại lệ phát sinh Hãy viết lại chương trình hoàn chỉnh có xử lý ngoại lệ cách đưa thông điệp ngoại lệ phát sinh using System; using System.IO; 18/19 Những đối tượng ngoại lệ public class Tester { public static void Main() { string fname = "test3.txt"; string buffer; StreamReader sReader = File.OpenText(fname); while ( (buffer = sReader.ReadLine()) !=null) { Console.WriteLine(buffer); } } } Hãy xem lại ví dụ chương trước, ví dụ phái sinh ngoại lệ thêm đoạn xử lý ngoại lệ cho ví dụ 19/19 [...]... cả những ngoại lệ được đối xử một cách bình đẳng? Không phải, có hai loại ngoại lệ, ngoại lệ hệ thống và ngoại lệ của chương trình ứng dụng Ngoại lệ của chương trình ứng dụng thì sẽ không kết thúc chương trình Còn ngoại lệ hệ thống thì sẽ kết thúc chương trình Nói chung đó là những ngoại lệ xuất hiện trước đây Hiện nay thì người ta chia ra nhiều mức độ ngoại lệ và tùy theo từng mức độ của ngoại lệ. .. và ngoại lệ? 16/19 Những đối tượng ngoại lệ Khi thực hiện việc bắt giữ các ngoại lệ Nếu có nhiều mức bắt giữ ngoại lệ thì chúng ta sẽ thực hiện mức nào Từ chi tiết đến tổng quát, hay từ tổng quát đến chi tiết? Ý nghĩa của từ khóa finally trong việc xử lý ngoại lệ? Câu lệnh nào được dùng để phát sinh ngoại lệ? Loại nào sau đây nên được xử lý theo ngoại lệ và loại nào thì nên được xử lý bởi các mã lệnh... và ngoại lệ ban đầu Do vậy ngoại lệ ban đầu (E1) trở thành ngoại lệ bên trong của ngoại lệ mới (E2) Sau đó hàm DangerousFunc2() phát sinh ngoại lệ này (E2) cho hàm DangerousFunc1() DangerousFunc1() bắt giữ ngoại lệ này, làm một số công việc và tạo ra một ngoại lệ mới có kiểu là MyCustomException, truyền vào hàm khởi dựng của ngoại lệ mới một chuỗi mới (“E3 – Custom Exception Situation!”) và ngoại lệ. .. lệ được bắt giữ (E2) Chúng ta nên nhớ rằng ngoại lệ được bắt giữ là ngoại lệ có chứa ngoại lệ DivideByZeroException (E1) bên trong nó Tại thời điểm này, chúng ta có một ngoại lệ kiểu MyCustomException (E3), ngoại lệ này chứa bên trong một ngoại lệ kiểu Exception (E2), và đến lượt nó chứa một ngoại lệ kiểu DivideByZeroException (E1) bên trong Sau cùng ngoại lệ được phát sinh cho hàm TestFunc; Khi khối... đến việc xử lý các ngoại lệ? Hãy cho biết một số lớp xử lý ngoại lệ quan trọng trong namespace này? Bài tập Hãy viết đoạn lệnh để thực hiện việc bắt giữa ngoại lệ liên quan đến câu lệnh sau đây: Ketqua = Sothu1 / Sothu2; Chương trình sau đây có vấn đề Hãy xác định vấn đề có thể phát sinh ngoại lệ khi chạy chương trình Và viết lại chương trình hoàn chỉnh gồm các lệnh xử lý ngoại lệ: .. .Những đối tượng ngoại lệ base(message, inner) { } } public class Test { public static void Main() { Test t = new Test(); t.TestFunc(); } // chia hai số và xử lý ngoại lệ public void TestFunc() { try { DangerousFunc1(); } catch (MyCustomException e) { Console.WriteLine("\n{0}", e.Message); 11/19 Những đối tượng ngoại lệ Console.WriteLine("Retrieving exception... của ngoại lệ : E3 – Custom Exception Situation! sau đó từng ngoại lệ bên trong sẽ được lấy ra thông qua vòng lặp while: while ( inner != null) { Console.WriteLine("{0}", inner.Message); inner = inner.InnerException; } Kết quả là chuỗi các ngoại lệ được phát sinh và được bắt giữ: Retrieving exception history E2 - Func2 caught divide by zero E1 – DivideByZero Exception 15/19 Những đối tượng ngoại lệ. .. giản là phát sinh lại ngoại lệ: catch ( System.ArithmeticException) { throw; } Cú pháp để thực hiện phát sinh lại cùng một ngoại lệ mà không có bất cứ bổ sung hay hiệu chỉnh nào là : throw Do vậy ngoại lệ được phát sinh cho DangerousFunc2(), khối catch trong DangerousFunc2() thực hiện một vài hành động và tiếp tục phát sinh một ngoại lệ có kiểu mới Trong hàm khởi dựng của ngoại lệ mới, DangerousFunc2()... nhận những ứng xử khác nhau Để biết thêm chi tiết chúng ta có thể đọc thêm trong tài liệu NET Framework về xử lý ngoại lệ Như câu trả lời bên trên tại sao tôi phải tìm hiểu nhiều về các ngoại lệ và cách thức xử lý các ngoại lệ khi chúng được phát sinh? Việc xây dựng một chương trình ứng dụng là hết sức phức tạp, chương trình luôn tiếm ẩn những yếu tố không ổn định và có thể phát sinh các ngoại lệ dẫn... dẫn đến những lỗi không mong muốn Việc thực hiện bắt giữ các ngoại lệ là hết sức cần thiết trong chương trình, nó cho phép chúng ta xây dựng được chương trình hoàn thiện hơn và xử lý các thông điệp ngoại lệ tốt hơn Tìm hiểu những ngoại lệ đem đến cho chúng ta nhiều kinh nghiệm trong việc xây dựng các chương trình phức tạp hơn Câu hỏi thêm Hãy cho biết các từ khóa được sử dụng để xử lý ngoại lệ? Phân ... InnerException ngoại lệ, nên có ngoại lệ bên Do vậy, toàn dây chuyền ngoại lệ đóng tổ (nest) ngoại lệ với ngoại lệ khác Giống lật đật, chứa đến lượt bên lại chứa Phát sinh lại ngoại lệ & ngoại lệ inner... thực tốt việc quản lý ngoại lệ phát sinh Có phải tất ngoại lệ đối xử cách bình đẳng? Không phải, có hai loại ngoại lệ, ngoại lệ hệ thống ngoại lệ chương trình ứng dụng Ngoại lệ chương trình ứng... cho biết từ khóa sử dụng để xử lý ngoại lệ? Phân biệt lỗi ngoại lệ? 16/19 Những đối tượng ngoại lệ Khi thực việc bắt giữ ngoại lệ Nếu có nhiều mức bắt giữ ngoại lệ thực mức Từ chi tiết đến tổng