Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
80
11.2 Đối tượng Exception
Đối tượng
System.Exception
cung cấp nhiều phương thức và property hữu ích
cho việc bẫy lỗi. Chẳng hạn property
Message
cung cấp thông tin tại sao nó được
ném.
Message
là thuộc tính chỉ đọc, nó được thiết đặt vào lúc khởi tạo biệt lệ.
Property
HelpLink
cung cấp một kết nối đến tập tin giúp đỡ. Property này có thể
đọc và thiết đặt. Property
StackTrace
chỉ đọcvà được thiết lập vào lúc chạy.
Trong ví dụ 11-6, property
Exception.HelpLink
được thiết đặt và nhận về để
thông tin thêm cho người dùng về biệt lệ
DivideByZeroException
. Property
StackTrace
được dùng để cung cấp các vết của vùng nhớ
stack
. Nó hiển thị
hàng loạt các phương thức đã gọi dẫn đến phương thức mà biệt lệ được ném ra.
Ví dụ 11-6. Làm việc với đối tượng Exception
using System;
namespace Programming_CSharp
{
public class Test
{
public static void Main( )
{
Test t = new Test( );
t.TestFunc( );
}
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 may not print");
}
catch (System.DivideByZeroException e)
{
Console.WriteLine(
"\nDivideByZeroException! Msg: {0}", e.Message);
Console.WriteLine("\nHelpLink: {0}", e.HelpLink);
Console.WriteLine(
"\nHere's a stack trace: {0}\n", e.StackTrace);
}
catch
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
81
{
Console.WriteLine("Unknown exception caught");
}
finally
{
Console.WriteLine ("Close file here.");
}
}
public double DoDivide(double a, double b)
{
if (b == 0)
{
DivideByZeroException e = new DivideByZeroException();
e.HelpLink = "http://www.libertyassociates.com";
throw e;
}
if (a == 0)
throw new ArithmeticException( );
return a / b;
}
}
}
Kết quả:
Open file here
DivideByZeroException! Msg: Attempted to divide by zero.
HelpLink: http://www.libertyassociates.com
Here's a stack trace:
at Programming_CSharp.Test.DoDivide(Double a, Double b)
in c:\ exception06.cs:line 56
at Programming_CSharp.Test.TestFunc( )
in exception06.cs:line 22
Close file here.
Kết quả liệt kê các phương thức theo trình tự ngược với trình tự chúng được gọi.
Đọc kết quả trên như sau: Có một biệt lệ xảy ra tại hàm
DoDivide()
, hàm
DoDivide
này được gọi bởi hàm
TestFunc().
Trong ví dụ này ta tạo một thể hiện của
DivideByZeroException
DivideByZeroException e = new DivideByZeroException();
Do không truyền tham số, thông báo mặc định được dùng:
DivideByZeroException! Msg: Attempted to divide by zero.
Ta có thể thay thông báo mặc định này bằng cách truyền tham số khi khởi tạo:
new DivideByZeroException(
"You tried to divide by zero which is not meaningful");
Trong trường hợp này kết quả sẽ là:
DivideByZeroException! Msg:You tried to divide by zero which is not
meaningful
Trước khi ném biệt lệ này, ta thiết đặt thuộc tính
HelpLink
e.HelpLink = "http://www.libertyassociates.com";
Khi biệt lệ được bắt, chương trình in thông báo và cả đường dẫn đến kết nối giúp đỡ
catch (System.DivideByZeroException e)
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
82
{
Console.WriteLine("\nDivideByZeroException! Msg: {0}",
e.Message);
Console.WriteLine("\nHelpLink: {0}", e.HelpLink);
Nhờ vậy ta có thể cung cấp các thông tin cần thiết cho người dùng. Sau đó là in
StackTrace
Console.WriteLine("\nHere's a stack trace:{0}", e.StackTrace);
Ta có kết quả cuối cùng.
11.3 Các biệt lệ tự tạo
Với các biệt lệ có thể tùy biến thông báo do CLR cung cấp, thường đủ cho hầu hết
các ứng dụng. Tuy nhiên sẽ có lúc ta muốn thêm nhiều dạng thông tin hơn cho đối
tượng biệt lệ, khi đó ta phải tự tạo lấy các biệt lệ mong muốn. Biệt lệ tự tạo bắt buộc
thừa kế từ lớp
System.Exception. Ví dụ 11-7 mô tả cách tạo một biệt lệ mới.
Ví dụ 11-7. Tự tạo biệt lệ
using System;
namespace Programming_CSharp
{
public class MyCustomException : System.ApplicationException
{
public MyCustomException(string message) : base(message)
{
}
}
public class Test
{
public static void Main( )
{
Test t = new Test( );
t.TestFunc( );
}
public void TestFunc( )
{
try
{
Console.WriteLine("Open file here");
double a = 0;
double b = 5;
Console.WriteLine("{0}/{1}={2}", a, b, DoDivide(a,b));
Console.WriteLine("This line may or may not print");
}
catch (System.DivideByZeroException e)
{
Console.WriteLine("\nDivideByZeroException! Msg: {0}",
e.Message);
Console.WriteLine("\nHelpLink: {0}\n", e.HelpLink);
}
catch (MyCustomException e)
{
Console.WriteLine("\nMyCustomException! Msg: {0}",
e.Message);
Console.WriteLine("\nHelpLink: {0}\n", e.HelpLink);
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
83
}
catch
{
Console.WriteLine("Unknown exception caught");
}
finally
{
Console.WriteLine ("Close file here.");
}
}
// do the division if legal
public double DoDivide(double a, double b)
{
if (b == 0)
{
DivideByZeroException e = new DivideByZeroException();
e.HelpLink = "http://www.libertyassociates.com";
throw e;
}
if (a == 0)
{
MyCustomException e = new MyCustomException(
"Can't have zero divisor");
e.HelpLink =
"http://www.libertyassociates.com/NoZeroDivisor.htm";
throw e;
}
return a / b;
}
}
}
MyCustomException
thừa kế từ
System.ApplicationException
và nó
không có gì khác hơn là một hàm dựng nhận tham số là một thông báo. Câu thông
báo này sẽ được chuyển tới lớp cha. Biệt lệ
MyCustomException
được thiết kế
cho chính lớp
Test
, không cho phép chia cho 0 và không chia 0 cho số khác. Sử
dụng
ArithmeticException
cũng cho kết quả tương tự nhưng có thể gây nhầm
lẫn cho lập trình viên khác do phép chia 0 cho một số không phải là một lỗi toán
học.
11.4 Ném biệt lệ lần nữa.
Sẽ có trường hợp ta muốn rằng trong khối lệnh
catch
ta sẽ khởi động một hành
động sửa lỗi, và sau đó ném biệt lệ cho khối
try
khác (khối
try
của hàm gọi). Biệt
lệ này có thể cùng loại hay khác loại với biệt lệ khối
catch
bắt được. Nếu là cùng
loại, khối
catch
sẽ ném biệt lệ này một lần nữa; còn nếu khác loại, ta sẽ nhúng biệt
lệ cũ vào biệt lệ mới để khối
try
hàm gọi biết được lịch sử của biệt lệ. Property
InnerException
được dủng để thực hiện việc này. Biệt lệ đem nhúng gọi là biệt
lệ nội.
Bởi vì
InnerException
cũng chính là một biệt lệ nên nó cũng có
InnerException
của nó. Cứ như vậy tạo nên một loạt các biệt lệ.
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
84
Ví dụ 11-8. Ném biệt lệ lần nữa và biệt lệ nội (inner exception)
using System;
namespace Programming_CSharp
{
public class MyCustomException : System.Exception
{
public MyCustomException(string message,Exception inner):
base(message,inner)
{
}
}
public class Test
{
public static void Main()
{
Test t = new Test();
t.TestFunc();
}
public void TestFunc()
{
try
{
DangerousFunc1();
}
// khi bắt được biệt lệ tự tạo
// in lịch sử các biệt lệ
catch (MyCustomException e)
{
Console.WriteLine("\n{0}", e.Message);
Console.WriteLine("Retrieving exception history ");
Exception inner = e.InnerException;
while (inner != null)
{
Console.WriteLine("{0}",inner.Message);
inner = inner.InnerException;
}
}
}
public void DangerousFunc1( )
{
try
{
DangerousFunc2( );
}
// nếu bắt được một biệt lệ
// ném một biệt lệ tự tạo
catch(System.Exception e)
{
MyCustomException ex = new MyCustomException(
"E3 - Custom Exception Situation!",e);
throw ex;
}
}
public void DangerousFunc2( )
{
try
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
85
{
DangerousFunc3( );
}
// nếu bắt được biệt lệ DivideByZeroException thực hiện
// vài công việc sữa lỗi và ném ra biệt lệ tổng quát
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)
{
Console.WriteLine("Exception handled here.");
}
}
public void DangerousFunc4( )
{
throw new DivideByZeroException(
"E1 - DivideByZero Exception");
}
}
}
Kết quả:
E3 - Custom Exception Situation!
Retrieving exception history
E2 - Func2 caught divide by zero
E1 - DivideByZeroException
Ghi chú: Kết quả xuất hiện trên màn hình không đủ để thể hiện hết ý,
cách tốt nhất là nên chạy chương trình ở chế độ từng dòng lệnh để hiểu
rõ vấn đề hơn.
Chúng ta bắt đầu bằng lời gọi hàm
DangerousFunc1()
trong khối
try
try
{
DangerousFunc1( );
}
DangerousFunc1()
gọi
DangerousFunc2()
,
DangerousFunc2()
gọi
DangerousFunc3(), DangerousFunc3()
gọi
DangerousFunc4()
. Tất cả
các lời gọi này đều có khối
try
của riêng nó. Cuối cùng
DangerousFunc4()
ném
một biệt lệ
DivideByZeroException
với câu thông báo
E1 - DivideByZero
Exception
.
Quản lý lỗi Gvhd: Nguyễn Tấn Trần Minh Khang
86
Khối lệnh catch trong hàm DangerousFunc3() sẽ bắt biệt lệ này. Theo logic, tất
cả các lỗi toán học đều được bắt bởi biệt lệ
ArithmeticException
(vì vậy cả
DivideByZeroException
). Nó chẳng làm gì, chỉ ném biệt lệ này lần nữa.
catch (System.ArithmeticException)
{
throw;
}
Cú pháp trên ném cùng một loại biệt lệ cho khối
try
bên ngoài (chỉ cần từ khóa
throw)
DangerousFunc2()
sẽ bắt được biệt lệ này, nó sẽ ném ra một biệt lệ mới thuộc
kiểu
Exception
. Khi khởi tạo biệt lệ này, ta truyền cho nó hai tham số: thông báo
E2 - Func2 caught divide by zero, và biệt lệ cũ để làm biệt lệ nội.
DangerousFunc1()
bắt biệt lệ này, làm vài công việc nào đó, sau đó tạo một biệt
lệ có kiểu
MyCustomException
. Tương tự như trên khi khởi tạo biệt lệ ta truyền
cho nó hai tham số: thông báo
E3 - Custom Exception Situation!
, và biệt
lệ vừa bắt được làm biệt lệ nội. Đến thời điểm này biệt lệ đã có hai mức biệt lệ nội.
Cuối cùng, khối catch sẽ bắt biệt lệ này và in thông báo
E3 - Custom Exception Situation!
Sau đó sẽ tiếp tục in các thông báo của các biệt lệ nội
while (inner != null)
{
Console.WriteLine("{0}",inner.Message);
inner = inner.InnerException;
}
Và ta có kết quả
Retrieving exception history
E2 - Func2 caught divide by zero
E1 - DivideByZero Exception
. thiết đặt vào lúc khởi tạo biệt lệ.
Property
HelpLink
cung c p một kết nối đến t p tin gi p đỡ. Property này có thể
đọc và thiết đặt. Property
StackTrace
. tượng Exception
Đối tượng
System.Exception
cung c p nhiều phương thức và property hữu ích
cho việc bẫy lỗi. Chẳng hạn property
Message
cung c p thông