Những chủ đề tiến bộ trong C#

Lỗi và xử lí biệt lệ - Phần 1

Không gì quan trọng bằng một đoạn mã tốt, chương trình của bạn phải luôn

có khả năng xử lí những lỗi có thể xảy ra.Ví dụ, giữa một quy trình xử lí

phức tạp , đoạn mã của bạn nhận ra rằng nó không được phép đọc một file,

hoặc trong khi nó đang gửi yêu cầu đến mạng thì mạng rớt.trong những tính

huống ngoại lệ ( exception) như vậy, không có đủ phương thức dù chỉ đơn

giản là trả về một mã lỗi tương đương- có thể có khoảng 15 đến 20 lần gọi

những phương thức lồng nhau,vì thế những gì bạn thật sự cần chương trình

làm là nhảy ngược trở lại xuyên suốt 15 đến 20 lần gọi để thoát nhiệm vụ

một cách hoàn chỉnh và xắp sếp lại những thứ bừa bộn. C# có những cách

tốt để xử lí những loại tình huống này,bằng cơ chế xử lí biệt lệ ( exception

handling).

Cách thức xử lí lỗi trong VB rất hạn chế, bị giới hạn trong câu lệnh On Error

Goto. Nếu bạn đã học VB, bạn sẽ thấy những biệt lệ trong C# mở ra một thế

giới mới cho việc xữ lí lỗi trong chương trình của bạn. Mặt khác,những nhà

phát triển Java và C++ sẽ quen với những nguyên tắc biệt lệ bởi những ngôn

ngữ này cũng xử lí lỗi theo cùng cách mà C# sử dụng. Những nhà phát triển

sử dụng C++ thỉnh thoảng cảnh giác với những biệt lệ bởi việc thực thi ẩn

trong C++ có thể xảy ra, nhưng điều này không cần quan tâm trong C#.Sử

dụng biệt lệ trong mã C# không gây bất kì ảnh hưởng bất lợi nào trong thực

thi.

Những lớp biệt lệ của lớp cơ sở

Trong C#, một biệt lệ là một đối tượng được tạo ra ( hoặc được ném ) khi

một trạng thái lỗi biệt lệ cụ thể xuất hiện. những đối tượng này chứa đựng

những thông tin mà giúp ích cho việc truy ngược lại vấn đề. Mặc dù chúng

ta có thể tự tạo ra những lớp biệt lệ riêng ( chúng ta sẽ làm sau này), .NET

cũng cung cấp cho chúng ta nhiều lớp biệt lệ được định nghĩa trước.

Những lớp biệt lệ cơ bản

Trong phần này chúng ta sẽ xem xét một cách tổng quát một vài biệt lệ mà

có giá trị trong những lớp cơ bản. Có một số lượng lớn những lớp biệt lệ mà

Microsoft đã định nghĩa, và ta không thể xem xét toàn bộ chúng như một

danh sách toàn diện ở đây.tuy nhiên biểu đồ cây lớp dưới đây biểu diễn một

vài trong số chúng.

Tất cả những lớp trong biểu đồ này nằm trong namspace system, một phần

trong IOexception và những lớp dẫn xuất từ IOException. một vài phần nằm

trong namspace System.IO, mà có liên quan đến việc đọc và viết dữ liệu lên

tập tin.Nói chung, không có namspace cụ thể cho biệt lệ;những lớp biệt lệ

nên đươc đặt trong bất kì namspace nào tương đuơng những lớp mà chúng

có thể được sinh ra - vì lí do đó những biệt lệ có liên quan đến IO thì nằm

trong namspace System.IO, bạn sẽ tìm thấy những lớp biệt lệ nằm trong một

vài namspace lớp cơ sở.

Những lớp biệt lệ có đặc điểm chung ,System.Exception được dẫn xuất từ

System.Object. Nói chung bạn không nên sinh ra một đối tượng

System.Exception từ mã của bạn, bởi vì nó không đưa ra một thông tin hữu

ích nào cho trạng thái lỗi.

Có 2 lớp quan trọng trong hệ thống các lớp được dẫn xuất từ

System.Exception là :

System.SystemException - sử dụng cho những biệt lệ thường xuyên được

sinh ra trong thời gian chạy của .NET,hoặc là những lỗi chung thường được

sinh ra bởi hầu hết những ứng dụng , ví dụ như là StackOverflowException

đuợc sinh ra bởi thời gian chạy .NET nếu nó thăm dò thấy Stack đầy. Mặt

khác, bạn có thể chọn để sinh ra ArgumentException,hoặc là một lớp con

của nó trong đoạn mã của bạn.nếu bạn thấy rằng một phương thức vừa mới

đưọc gọi với một đối số không tương thích.những lớp con của

System.SystemException bao gồm việc trình bày những lỗi nghiêm trọng và

không nghiêm trọng.

System.ApplicationException - đây là một lớp quan trọng. bởi vì nó được

dùng cho bất kì lớp biệt lệ được định nghĩa bởi những hãng thứ ba ( hay còn

gọi là những tổ chức khác với Microsoft), Vì lí do đó, nếu bạn định nghĩa

bất kì biệt lệ nào bao phủ trạng thái lỗi duy nhất đối với ứng dụng của bạn

,bạn nên dẫn xuất một cách trực tiếp hay gián tiếp từ

System.ApplicationException

Chúng ta sẽ không thảo luận về tất cả những lớp biệt lệ đuợc biểu diễn trong

biểu đồ. bởi vì , hầu hết các mục đích sử dụng đều thể hiện rỏ ràng qua tên

của chúng.

Như đã đề cập, StackOverflowException xuất hiện nếu một vùng bộ nhớ

đươc dùng cho Stack bị đầy.tràn Stack có thể xuất hiện, nếu một phương

thức gọi đệ quy liên tiếp. Đây là một lỗi nghiêm trọng.bởi vì nó ngăn ứng

dụng bạn làm bất cứ điều gì ngoại trừ việc tắt .

Một OverflowException là những gì sẽ xảy ra nếu bạn cố gắng ép một số int

-40 vào trong một số uint trong ngữ cảnh Checked.

Tuy nhiên trong trường hợp xử lí biệt lệ, những lí do thường dùng trong việc

thêm vào những lớp thừa kế đơn giản là dùng để chỉ rõ những trạng thái lỗi

cụ thể hơn và thường không cần để nạp chồng phương thức hoặc thêm bất kì

cái mới nào ( mặc dù người ta cũng thường thêm vào những thuộc tính mà

mang những thông tin thêm về trạng thái lỗi.) ví dụ bạn có thể có một lớp

ArgumentException cơ sở chỉ định phương thức, gọi một giá trị không

tương thích, được truyền vào, và một lớp ArgumentNullException dẫn xuất

từ lớp này, mà được chỉ định đặc biệt khi một đối số NULL được truyền

vào.

Đón bắt biệt lệ

Cho rằng bạn có những đối tượng biệt lệ có giá trị , vậy làm thế nào chúng ta

có thể sử dụng chúng trong đoạn mã để bẫy những trạng thái lỗi? Để có thể

giải quyết điều này trong C# bạn thường là phải chia chương trình của bạn

thành những khối thuộc 3 kiểu khác nhau :

Khối try chứa đựng đoạn mã mà có dạng là một phần thao tác bình thường

trong chương trình của bạn, nhưng đoạn mã này có thể gặp phải một vài

trạng thái lỗi nghiêm trọng.

Khối catch chứa đựng đoạn mã mà giải quyết những trạng thái lỗi nghiêm

trọng trong đoạn try

Khối finally chứa đựng đoạn mã mà dọn dẹp tài nguyên hoặc làm bất kì

hành động nào mà bạn thường muốn làm xong vào cuối khối try hay catch..

điều quan trọng phải hiểu rằng khối finally được thực thi dù có hay không có

bất kì biệt lệ nào được ném ra.bởi vì mục tiêu chính của khối finally là chứa

đựng đoạn mã rõ ràng mà luôn được thực thi. trình biên dịch sẽ báo lỗi nếu

bạn đặt một lệnh return bên trong khối finally.

Vậy làm thế nào những khối này gắn kết lại với nhau để bẫy lỗi ? cách mà

nó làm như sau :

1. Dòng thực thi bước vào khối try.

2. Nếu không có lỗi xuất hiện, việc thực thi tiến hành một cách bình

thường xuyên suốt khối try, và khi đến cuối khối try, dòng thực thi sẽ nhảy

đến khối finally ( bước 5), tuy nhiên, nếu một lỗi xuất hiện trong khối

try,thực thi sẽ nhảy đến khối catch ( bước tiếp theo)

3. trạng thái lỗi được xử lí trong khối catch

4. vào cuối của khối catch , việc thực thi được chuyển một cách tự động

đến khối finally

5. khối finally được thực thi

Cú pháp C# được sử dụng để thể hiện tất cả điều này cụ thể như sau:

try

{

// mã cho việc thực thi bình thường

}

catch

{

// xử lí lỗi

}

finally

{

// dọn dẹp

}

thực sự , có một vài điều có thể thay đổi trong cú pháp trên.

- Ta có thể bỏ qua khối finally.

- Ta có thể cung cấp nhiếu khối catch mà ta muốn xử lí những kiểu lỗi

khác nhau.

- Ta có thể bỏ qua khối catch, trong trường hợp cú pháp phục vụ không

xác định biệt lệ , nhưng phải đảm bảo rằng mã trong khối finally sẽ được

thực thi khi việc thực thi rời khỏi khối try .

Nhưng điều này đặt ra một câu hỏi : nếu đoạn mã đang chạy trong khối try,

làm thế nào nó biết chuyển đến khối catch nếu một lỗi xuất hiện? nếu một

lỗi được thăm dò, mã sẽ làm một việc gì đó được biết đến như là ném ra một

biệt lệ , nói cách khác , nó chỉ ra một lớp đối tượng biệt lệ và ném nó:

throw new OverflowException();

Ở đây chúng ta có một thể hiện của đối tượng biệt lệ của lớp

OverflowException. ngay khi máy tính gặp một câu lệnh throw bên trong

khối try, nó ngay lập tức tìm khối catch kết hợp với khối try, nó xác định

khối catch đúng bởi việc kiểm tra lớp biệt lệ nào mà khối catch kết hợp với.

ví dụ, khi đối tượng OverflowException đuợc ném ra việc thực thi sẽ nhảy

vào khối catch sau:

catch (OverflowException e)

{

Nói cách khác, máy tính sẽ tìm khối catch mà chỉ định một thể hiện lớp biệt

lệ phù hợp trong cùng một một lớp ( hoặc của lớp cơ sở)

Giả sử, lúc xem xét đối số, có 2 lỗi nghiêm trọng có thể xảy ra trong nó: lỗi

tràn và mảng ngoài biên.giả sử rằng đoạn mã của chúng ta chứa đựng hai

biến luậnlý. Overflow và OutOfBounds , mà chỉ định liệu trạng thái lỗi này

có tồn tại không .Chúng ta đã thấy lớp biệt lệ đã định nghĩa trước đây tồn tại

để chỉ định tràn (OverflowException);tương tự một lớp

IndexOutOfRangeException tồn tại để xử lí mảng ngoài biên.

Bây giờ hãy nhìn vào khối try sau:

try

{

// mã cho thực thi bình thường

if (Overflow == true)

throw new OverflowException();

// xử lý nhiều hơn

if (OutOfBounds == true)

throw new IndexOutOfRangeException();

// hoặc tiếp tục xử lí bình thường

}

catch (OverflowException e)

{

// xử lí lỗi cho trạng thái lỗi tràn

}

catch (IndexOutOfRangeException e)

{

// xử lí lỗi cho trạng thái lỗi chỉ mục nằm ngoài vùng

}

finally

{

// dọn dẹp

}

Thực thi nhiều khối catch

Cách dễ dàng nhất để xem làm thế nào khối try ... catch ... block làm việc là

thực tập với một vài ví dụ: ví dụ đầu tiên là SimpleExceptions.. nó lặp lại

việc hỏi người sử dụng gõ vào 1 số và trình bày nó tuy nhiên vì mục đích

của ví dụ , chúng ta sẽ giả sử rằng số cần gõ phải từ 0--> 5 hoặc là chương

trình sẽ không thể xử lý số một cách chính xác. do đó chúng ta sẽ ném ra

một biệt lệ nếu người sử dụng gõ mộ thử gì đó ngoài vùng này.

Chương trình sẽ tiếp tục hỏi số cho đến khi người sử dụng nhấn enter mà

không gõ bất kì phím gì khác.

using System;

namespace Wrox.ProCSharp.AdvancedCSharp

{

public class MainEntryPoint

{

public static void Main()

{

string userInput;

while ( true )

{

try

{

Console.Write("Input a number between 0 and 5 " +

"(or just hit return to exit)> ");

userInput = Console.ReadLine();

if (userInput == "")

break;

int index = Convert.ToInt32(userInput);

if (index < 0 || index > 5)

throw new IndexOutOfRangeException(

"You typed in " + userInput);

Console.WriteLine("Your number was " + index);

}

catch (IndexOutOfRangeException e)

{

Console.WriteLine("Exception: " +

"Number should be between 0 and 5. " + e.Message);

}

catch (Exception e)

{

Console.WriteLine(

"An exception was thrown. Message was: " + e.Message);

}

catch

{

Console.WriteLine("Some other exception has occurred");

}

finally

{

Console.WriteLine("Thank you");

}

}

}

}

}

Lõi của đoạn mã này là vòng lặp loop, mà tiếp tục sử dụng

console.readline() để hỏi người sử dụng cho việc nhập.readline() trả về một

chuổi, vì vậy điều đầu tiên chúng ta làm là chuyển nó thành một số kiểu int

sử dụng phương thức system.Convert.ToInt32() . Lớp System.Convert chứa

đựng những phương thức hữu ích khác để biểu diễn chuyển đổi dữ liệu, và

cung cấp một thay thế đến phương thức int.Parse(). Nói chung

System.Convert chứa đựng phương thức để thực thi nhiều kiểu chuyển đổi

khác nhau , nhắc lại rằng trình biên dịch C# xem int là một thể hiện của lớp

cơ sở System.int32 .

một điều cũng quan trọng nữa là thông số truyền đến khối catch chỉ có phạm

vi trong khối catch -chính vì vậy mà chúng ta mới có thể sử dụng cùng một

tên thông số , e, trong liên tiếp những khối block trong đoạn mã phía trên .

Trong đoạn mã trên chúng ta cũng kiểm tra chuỗi rỗng, bởi vì đây là điều

kiện của chúng ta để thoát khỏi vòng lặp while . chú ý cách câu lệnh break

thực sự ngắt ra khỏi khối try cũng như vòng lặp while. tất nhiên khi việc

thực thi ngắt ra khỏi khối try, câu lệnh Console.Writeline() trong khối finally

được thực thi..mặc dù chúng ta chỉ trình bày một lời chaò ở đây, nhưng nhìn

chung , chúng ta sẽ làm một số nhiệm vụ như là điều khiển việc đóng file và

gọi phương thức Dispose() của những đối tượng khác để thực hiện việc dọn

dẹp.mỗi lần máy tính rời khỏi khối finally, nó đơn giản chuyển việc thực thi

đến câu lệnh kế tiếp sẽ được thực thi, có khối finally không được tình

baỳ.trong trường hợp này chúng ta trở lại đầu vòng lặp while và bươc và

khối try lần nữa.

Kế tiếp , chúng ta kiểm tra trạng thái biệt lệ:

if (index < 0 || index > 5)

throw new IndexOutOfRangeException("You typed in " + userInput);

Khi ném một biệt lệ, chúng ta cần chọn kiểu biệt lệ để ném.mặc dù lớp

System.Exception là có giá trị, nó thật sự được dùng như là một lớp cơ sở và

là một cách lập trình không hay khi ném một thể hiện của lớp này như là

một biệt lệ, bởi vì nó không chuyển thông tin gì về bản chất của trạng thái

lỗi. thay vào đó microsoft định nghĩa nhiều lớp biệt lệ khác mà dẫn xuất từ

system.exception. mỗi lớp này phù hợp với một kiểu biệt lệ cụ thể và bạn có

quyền tự do định nghĩa một cái riêng của bạn.ý tưởng là bạn sẽ đưa ra càng

nhiều thông tin càng tốt về trạng thái lỗi cụ thể bằng cách ném 1 thể hiện của

lớp phù hợp với trạng thái lỗi cụ thể.trong lúc naỳ, bạn chọn

System.IndexOutOfRangeException như là sự lưạ chọn tốt nhất trong tình

huống naỳ. IndexOutOfRangeException có một vài hàm dựng.cái mà chúng

ta chọn lấy 1 chuỗi mô tả một lỗi, thay vào đó chúng ta có thể chọn để dẫn

xuất đối tượng Exception tuỳ biến của riêng ta mà mô tả trạng thái lỗi trong

ngữ cảnh của ứng dụng.