Lỗi và sửa lý ngoại lệ trong C# – Exceptions and Exception Handling in C#

Lỗi và sửa lý ngoại lệ trong C# – Exceptions and Exception Handling in C#

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ệ (exceptions) 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í ngoại 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 ngoại 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 ngoại 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 ngoại 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 ngoại lệ trong mã C# không gây bất kì ảnh hưởng bất lợi nào trong thực thi.

I.  Những lớp ngoại lệ của lớp cơ sở

Trong C#, một ngoại 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 ngoại 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 ngoại 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 ngoại lệ được định nghĩa trước.

Những lớp ngoại 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 ngoại 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 ngoại 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.

Những lớp ngoại lệ cơ bản

-  Tất cả những lớp trong biểu đồ này nằm trong namspace system, một phần trongIOexception 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 ngoại lệ; những lớp ngoại 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 ngoại 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 ngoại lệ nằm trong một vài namspace lớp cơ sở.

-  Những lớp ngoại 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.Exceptiontừ 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 ngoại 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 ngoại 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ì ngoại 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 ngoại 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í ngoại 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.

II. Đón bắt ngoại lệ

- Cho rằng bạn có những đối tượng ngoại 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ì ngoại 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ốitry, 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 ngoại 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 ngoại lệ, nói cách khác, nó chỉ ra một lớp đối tượng ngoại lệ và ném nó:

throw new OverflowException();

-  Ở đây chúng ta có một thể hiện của đối tượng ngoại 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ốicatch 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 ngoại 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 ngoại 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 ngoại 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 ngoại 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 Exam1
{
    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, 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ứcsystem.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ứcint.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 trycũ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ệnhConsole.Writeline() trong khối finally được thực thi. Mặc dù chúng ta chỉ trình bày một lời chào ở đâ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 bày. 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 ngoại lệ:

if (index < 0 || index > 5)
    throw new IndexOutOfRangeException(
       "You typed in " + userInput);

-   Khi ném một ngoại lệ, chúng ta cần chọn kiểu ngoại lệ để ném. Mặc dù lớpSystem.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 ngoại 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 ngoại 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 ngoại 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 này, bạn chọn System.IndexOutOfRangeException như là sự lưạ chọn tốt nhất trong tình huống này. 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 Exceptiontuỳ 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.

-  Giả sử rằng người sử dụng gõ 1 số mà không nằm giữa 0 và 5 . điều này được đón bởi câu lệnh if và 1 đối tượng IndexOutOfRangeException sẽ được khởi tạo và ném.vào lúc này máy tính sẽ ngay lập tức thoát khỏi khối try và tìm khối catch mà xử líIndexOutOfRangeException. Khối catch đầu tiên mà nó đến là khối này:

catch (IndexOutOfRangeException e)
{
    Console.WriteLine("Exception: " +
       "Number should be between 0 and 5. " + e.Message);
}

-  Bởi vì khối catch này lấy 1 thông số của một lớp tương đương, điều này sẽ được truyền qua 1 thể hiện của ngoại lệ và thực thi. Trong trường hợp này, chúng ta trình bày,1 thông báo lỗi và thuộc tính exception.message (đáp lại chuỗi mà chúng ta đã truyền đến hàm dựng của IndexOutOfRange). Sau khi thực thi khối catch này, điều khiển chuyển đến khốifinally, như là không có ngoại lệ nào xuất hiện.

-  Chú ý rằng chúng ta cũng cung cấp một khối catch khác :

catch (Exception e)
{
    Console.WriteLine(
       "An exception was thrown. Message was: " + e.Message);
}

-   Khối catch này cũng có khả năng xử lý IndexOutOfRangeException nếu không có sự kiện rằng ngoại lệ đã được đón bắt bởi khối catch trước – một sự tham chiếu đến 1 lớp cơ sở có thể cũng chuyển đến bất kỳ 1 thể hiện của mộ lớp dẫn xuất từ nó. Và tất cả ngoại lệ mà dẫn xuất từ system.exception . Vậy tại sao khối catch này không được thực thi? Câu trả lời là máy tính chỉ thực thi khối catch thích hợp đầu tiên mà nó tìm thấy. Vậy tại sao khốicatch thứ hai này lại nằm ở đây ? Thật ra thì không chỉ đoạn mã của ta mới được bao phủ bởi khối try, mà bên trong khối, chúng ta thực sự gọi những phương thức riêng biệt nhau trongnamspace system ( Console.ReadLine(), Console.Write(), and Convert.ToInt32()), và bất kỳ phương thức nào trong đây cũng có thể ném ra một ngoại lệ.

-   Nếu chúng ta gõ một thứ gì không phải là số – say hoặc hello ,sau đó phương thứcconvert.toInt32() sẽ ném ra một ngoại lệ của lớp System.FormatException, để chỉ định chuỗi được truyền vào toInt32() không nằm trong định dạng mà có thể chuyển thành kiểu Int. Khi điều này xảy ra, máy tính sẽ truy vết xuyên suốt phương thức gọi , tìm hàm xử lí mà có thể xử lí ngoại lệ này. Khối catch đầu tiên của chúng ta (cái mà bắtIndexOutOfRangeException) sẽ không thực hiện. Máy tính tìm đến cái thứ hai. Cái này sẽ thi hành bởi vì FormatException là một dẫn xuất từ exception, vì vậy một thực thể FormatException có thể được truyền như là một thông số ở đây.

-   Cấu trúc của ví dụ trên thực sự là kiểu tình huống đẹp với nhiều khối catch. Chúng ta sẽ bắt đầu với khối catch được thiết kế để bẫy trạng thái lỗi cụ thể. Sau đó, chúng ta hoàn thành với nhiều khối catch sẽ bao phủ bất kì lỗi nào mà chúng ta không viết những hàm xử lí lỗi cụ thể. Việc sắp xếp các khối catch là quan trọng. Nếu chúng ta viết tên 2 khối thứ tự ngược nhau, mã sẽ không phiên dịch bởi vì khối catch thứ hai sẽ không bao giờ có thể được tham chiếu đến ( khối catch exception có thể bắt tất cả các ngoại lệ).

-   Tuy nhiên chúng ta cũng nhìn vào khối catch thứ ba:

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

-   Đây là khối catch chung cho tất cả – nó không nhận bất kì thông số nào . Lý do của khối block này là bắt các ngoại lệ được ném bởi những đoạn mã không được viết trong C#, hoặc thậm chí không được quản lí trong C#. Như bạn thấy, đó là một sự đòi hỏi của ngôn ngữ C# mà chỉ thực thể của lớp được dẫn xuất từ system.exception mới có thể ném như một ngoại lệ. Nhưng những ngôn ngữ khác không có giới hạn này – ví dụ C++ cho phép bất kì một biến nào được ném như là một ngoại lệ. Nếu mã của ta gọi trong thư viện hay những tập hợp mà được viết trong những ngôn ngữ khác, sau đó nó có thể tìm một ngoại lệ được ném mà không dẫn xuất từ system.exception , mặc dù trong nhiều trường hợp , cơ chế của .NET sẽ bẫy những ngoại lệ này và chuyển chúng thành đối tượng ngoại lệ .NET. Tuy nhiên không có nhiều khối catch có thể làm điều này, bởi vì chúng ta không biết những lớp ngoại lệ nào có thể trình bày .

-  Bây giờ ta sẽ chạy đoạn mã ví dụ mà ta đả phân tích .minh họa cho những tình huống nhập khác nhau xảy ra, và cả việc IndexOutOfRangeException và FormatException được ném:

việc IndexOutOfRangeException và FormatException

Đón bắt những ngoại lệ từ những các đoạn mã khác không nằm trong chương trình

-   Ở ví dụ trước, ta đã minh họa hai ngoại lệ: IndexOutOfRangeException,được tung ra bởi mã của ta. Cái còn lại, FormatException được tung ra từ bên trong của 1 torng những lớp cơ sở. Ta thường gặp điều này, tuy nhiên các mã torng các thư viện hiếm khi bắt các ngoại lệ, xem đó như là trách nhiệm của các mã client ( mã mà ta viết).

-   Thông thường, ta sẽ tìm các ngoại lệ được ném từ các thư viện lớp cơ sở khi ta vá lỗi. Mục đích của ta là đảm bảo vào thời gian chạy của các chươn gtrình các ngoại lệ thực sự chỉ xuất hiện vào lúc nào đó và nếu có thể thì được xử lí theo một cách nào đó theo ý của ta .

Những thuộctính của system.exception

-   Trong ví dụ trên chúng ta chỉ xem xét 1 thuộc tính đó là message, của đối tượng exception. Tuy nhiên có 1 số những thuộc tính trong lớp system.exception:

  • helplink: 1 liên kết đến 1 tập tin trợ giúp mà cung cấp nhiều thông tin hơn về ngoại lệ.
  • message: chuỗi mô tả trạng thái lỗi.
  • source : tên của ứng dụng hoặc đối tượng mà gây ra lỗi.
  • Stacktrace: chi tiết về phương thức gọi stack ( để giúp đỡ truy dấu vết phương thức mà   ném ra ngoại lệ)
  • TargetSite: một đối tượng phản ánh của .NET mà mô tả phương thức ném ra ngoại lệ.
  • InnerException: nếu ngoại lệ này được ném từ bên trong một khối catch , nó chứa đựng đối tượng ngoại lệ mà gửi đoạn mã vào trong khối catch.

-   Trong những thuộc tính này , StackTrace và TargetSite được cung cấp tự động bởi thời gian chạy .NET nếu dấu vết Stack có giá trị. Source luôn luôn được điền bởi thời gian chạy .NET như là tên của tập hợp, mà ngoại lệ đuợc phát ra. Trong khi đó message, helplink,innerexception phải được điền bởi mã mà ném ra ngoại lệ, bằng việc thiết lập những thuộc tính này ngay trước Khi ném ra ngoại lệ, do đó trong ví dụ mã để ném ngoại lệ có thể là :

if (ErrorCondition == true)
{
    Exception myException = new ClassmyException("Help!!!!");
    myException.Source = "My Application Name";
    myException.HelpLink = "MyHelpFile.txt";
    throw myException;
}

Điều gì xảy ra nêu ngoại lệ không được xử lí

-  Thỉnh thoảng 1 ngoại lệ được tung ra, nhưng không có một khối catch nào trong mã có thể xử lí loại ngoại lệ này. Ví dụ 1 trên cho thấy điều này. Giả sử trong ví dụ chúng ta bỏ quaFormatException và những khối catch bắt tất cả và chỉ cung cấp một khối mà bẩy lỗiIndexOutOfRangeException. Trong tình huống này điều gì xảy ra khi 1FormatException được tung ra? Câu trả lời là thời gian chạy .NET sẽ bắt nó.

-   Sau đó trong phần này chúng ta sẽ xem làm thế nào nó có thể lồng những khối try , và quả thật trong ví dụ đã có sẵn khối try được lồng phiá sau chương trình. Thời gian chạy .NET đặ toàn bộ chương trình của bạn trong 1 khối try khổng lồ – điều này được làm trong mỗi chương trình .NET.khối try này có một hàm xử lí catch mà có thể bắt bất kỳ kiểu ngoại lệ nào. Nếu ngoại lệ trong mã không được xử l, thì nó sẽ truyền ra cho thời gian chạy .NET đón bắt. Tuy nhiên kết quả sẽ không như bạn muốn, nghĩa là chương trình của bạn có thể bị ngắt và người sử dụng sẽ nhìn thấy thông báo phàn nàn mã của bạn đã không xử lí ngoại lệ thêm vào đó là những thông tin mà .NET nhận đuợc. Tuy nhiên ít nhất thì ngoại lệ cũng đã được đón bắt.

Lồng những khối try

-  Một đặc tính tốt của ngoại lệ là có thể lồng những khối try trong những khối try khác ví dụ như:

try
{
    // Point A
    try
    {
        // Point B
    }
    catch
    {
        // Point C
    }
    finally
    {
        // Dọn dẹp

    }
    // Point D
}
catch
{
    // xử lí lỗi
}
finally
{
    // dọn dẹp
}

-   Nếu ngoại lệ được tung ra bên trong khối try ngoài nhưng bên ngoài khối try bên trong (điểm a và d), thì tình huống sẽ giống như bạn đã được học trước đây.

-   Nếu ngoại lệ đưọc ném bên trong khối try (điểm B) có một khối catch thích hợp để xử lí ngoại lệ này, thì ngoại lệ được xử lí ở đó và khối finally bên trong được thực thi trước khi việc thực thi tiếp tục bên trong của khối try nằm ngoài (điểm D). Bây giờ giả sử một ngoại lệ xuất hiện bên trong khối try trong và không có khối catch trong để xử lí. Lúc này khối finallytrong được thực thi, nhưng sau đó thời gian chạy .NET sẽ không có chọn lựa nào khác là rời khỏi toàn bộ khối try này và tìm kiếm hàm xử lí ngoại lệ thích hợp ở nơi tiếp theo là khốicatch ngoài. Nếu tìm được thì xử lí và nhảy dến khối finally ngoài. Nếu không thấy thì tìm tiếp tục, nếu vẫn không thấy thì khối finally ngoài sẽ thi hành sau đó sẽ chuyển cho thời gian chạy .NET.

-   Một điểm thú vị nữa nếu một ngoại lệ tung ra ở điểm C. nếu chương trình ở điểm C thì nó phải xử lí một ngoại lệ được ném ở điểm B. Điều này là hợp pháp khi ném một ngoại lệ khác từ bên trong của 1 khối catch, trong trường hợp này, ngoại lệ được đối xử như là nó được ném bởi khối try ngoài và thực thi khối finally trong, trước khi khối catch ngoài xử lí. Nếu 1 ngoại lệ được ném từ khối finally trong , điều khiển sẽ lập tức chuyển đến hàm xử lí tương đương tốt nhất, với việc tìm kiếm bắt đầu ở khối catch ngoài.

-   Quan trọng: việc ném ngoại lệ từ khối catch hay finally là hợp lệ. Chúng ta đã biết được làm thế nào các khối try lồng nhau làm việc. Sau đây chúng ta tìm hiểu xem tại sao chúng ta phải làm điều đó ? Có 2 lí do:

  • Để cập nhật kiểu ngoại lệ được ném.
  • Cho phép kiểu ngoại lệ khác nhau được xử lí ở nơi khác nhau trong mã của bạn.

-  Cập nhật kiểu ngoại lệ. Điều này hữu ích khi kiểu ngoại lệ được ném ra không mô tả đầy đủ vấn đề.

-  Xử lí những ngoại lệt khác nhau ở những nơi khác nhau
           Lý do thứ hai để lồng những khối try là để những kiểu ngoại lệ khác nhau có thể xử lí ở những vị trí khác nhau trong mã của bạn. Một ví dụ hay cho điều này là nếu bạn có một vòng lặp nơi mà các trạng thái ngoại lệ khác nhau có thể xuất hiện, một vài trong số chúng nghiêm trọng đủ để bạn cần phải bỏ ra khỏi vòng lặp. Trong khi những cái khác ít nghiêm trọng hơn đơn giản đòi hỏi bạn bỏ phần lặp này và di chuyển đến phần lặp khác trong vòng lặp của bạn. Bạn có thể làm điều này bằng cách cài khối try bên trong vòng lặp mà xử lí những lỗi không nghiêm trọng, và một khối try ở ngoài xử lí những lỗi nghiêm trọng.

III.  Những lớp ngoại lệ do người sử dụng định nghĩa.

-   Trong thứ 2 này, sẽ chứa đựng 2 khối try lồng nhau, và cũng minh họa thực tập việc định nghĩa lớp ngoại lệ tuỳ biến của riêng ta. Và tung một ngoại lệ khác từ bên trong một khối try
Trong ví dụ này, ta sẽ làm quen với công ty điện thoại di động mortimer phones giả sử rằng công ty muốn có thêm khách hàng. Đội kinh doanh của công ty sắp đưa ra danh sách của những người được mời trở thành khách hàng hoặc để dùng cho kinh doanh. Để làm điều này ta có một tập tin lưu tên của những người được gọi. Tập tin nên được định dạng chuẩn mà dòng đầu tiên chứa số người trong tập tin, và mỗi dòng tiếp theo chứa đựng tên của người kế tiếp. Ví dụ:

4
Avon from 'Blake's 7'
Zbigniew Harlequin
Simon Robinson
Christian Peak

-  Chương trình của ta sẽ hỏi người sử dụng tên tập tin : đơn giản đọc nó và trình bày tên trong tập tin. Nghe có vẻ đơn giản nhưng có một vài lỗi có thể xảy ra

-  Tên tập tin không tồn tại. Ngoại lệ FileNotFound sẽ tung ra tập tin có thể định dạng không đúng. Có 2 vấn đề có thể có:

  • Thứ nhất: dòng đầu tiên không phải là một số nguyên.
  • Thứ hai: số tên không khớp với số nguyên ở dòng đầu.để giải quyết vấn đề này chúng ta phải tự xây dựng lấy lớp ngoại lệ , ở đây ta đặt tên làColdCallFileFormatException

-   Cũng có những vấn đề khác tuy nhiên không nghiêm trọng, chúng ta có thể sử dụng các khối try bên trong. Ví dụ nếu là người gián điệp của công ty khác (chữ cái đầu là Z) thì chúng ta có thể bỏ qua người đó và xử lí ngưòi kế tiếp. Ngoại lệ này được gọi làLandLineSpyFoundException , tất nhiên đây là một đối tuợng ngoại lệ tuỳ biến khác.

Cuối cùng chúng ta sẽ thực thi ví dụ này bằng việc soạn 1 lớp, ColdCallFileReader, mà duy trì việc nối giữa tập tin goị-lạnh (cold-call ) và xuất dữ liệu từ nó. Chúng ta soạn mã này theo cách an toàn, nghĩa là những phương thức của nó sẽ ném các ngoại lệ nếu chúng được gọi không hợp lí, ví dụ nếu phương thức đọc tập tin đưọc gọi trước phương thức mở tập tin. Để xử lý điều này ta soạn ra một lớp ngoại lệ khác : UnexpectedException.

Đón bắt ngoại lệ do người sử dụng định nghĩa

-   Chúng ta bắt đầu với phương thức main() trong ví dụ MortimerColdCall mà bắt những ngoại lệ riêng của ta.

using System;
using System.IO;

namespace Exam2
{
    class MainEntryPoint
    {
        static void Main()
        {
            string fileName;
            Console.Write("Please type in the name of the file " +
               "containing the names of the people to be cold-called > ");
            fileName = Console.ReadLine();
            ColdCallFileReader peopleToRing = new ColdCallFileReader();

            try
            {
                peopleToRing.Open(fileName);
                for (int i = 0; i < peopleToRing.NPeopleToRing; i++)
                {
                    peopleToRing.ProcessNextPerson();
                }
                Console.WriteLine("All callees processed correctly");
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine("The file {0} does not exist", fileName);
            }
            catch (ColdCallFileFormatException e)
            {
                Console.WriteLine(
              "The file {0} appears to have been corrupted", fileName);
                Console.WriteLine("Details of problem are: {0}", e.Message);
                if (e.InnerException != null)
                    Console.WriteLine(
                       "Inner exception was: {0}", e.InnerException.Message);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception occurred:\n" + e.Message);
            }
            finally
            {
                peopleToRing.Dispose();
            }
            Console.ReadLine();
        }
    }
}

-   Trong khối try ta mở file (phương thức ColdCallFileReader.Open()) và lặp tất cả tên trong đó. Phương thức ColdCallFileReader.ProcessNextPerson() đọc và trình bày tên của người kế tiếp trong tập tin.trong khi thuộc tínhColdCallFileReader.NPeopleToRing bảo cho ta biết bao nhiêu người nên có trong tập tin (bằng cách đọc dòng đầu tiên ). Có 3 khối catch : FileNotFoundException andColdCallFileFormatException và 1 khối catch bẫy bất kì ngoại lệ .NET nào.

Ngoại lệ FileNotFoundException đơn giản trình bày một thông điệp của riêng ta ngoại lệ ColdCallFileFormatException trình bày chi tiết hơn bao gồm chi tiết về ngoại lệ bên trong nếu có,với đầy đủ thông tin kĩ thuật.

-   Cuối cùng, nếu có bất kì ngoại lệ khác 2 ngoại lệ trên ta sẽ đón bắt bằng khối catchexception. Thay vì để cho thời gian chạy .NET xử lý. Khối finally sẽ dọn dẹp tài nguyên, trong trường hợp này là đóng tập tin bằng phương thứcColdCallFileReader.Dispose()

Tung  ngoại lệ do người sử dụng định nghĩa

-   Bây giờ ta hãy nhìn vào định nghĩa của lớp xử lí việc đọc tập tin, và tung ra một ngoại lệ riêng ColdCallFileReader. Lớp này được dẫn xuất từ IDisposable

class ColdCallFileReader : IDisposable
{
    FileStream fs;
    StreamReader sr;
    uint nPeopleToRing;
    bool isDisposed = false;
    bool isOpen = false;

-   FileStream and StreamReader cả hai đều nằm trong namspace system.IO, là lớp cơ sở mà chúng ta sẽ dùng để đọc tập tin. filestream cho phép chúng ta nối đến tập tin ở nơi đầu tiên, trong khi StreamReader được đẩy mạnh để đọc tập tin kí tự, và thực thi phương thức,StreamReader(), đọc 1 dòng trong tập tin.

-   Trường IsDispose chỉ định liệu phương thức Dispose() có được gọi hay không.Isopen được dùng cho việc kiểm tra lỗi – trong trường hợp này, kiểm tra xem liệuStreamreader có thực sự nối đến một tập tin đang mở không. Quy trình mở và đọc dòng đầu tiên -cho biết có bao nhiêu người trong tập tin được xử lí bằng phưong thức open().

public void Open(string fileName)
{
    if (isDisposed)
        throw new ObjectDisposedException("peopleToRing");
    fs = new FileStream(fileName, FileMode.Open);
    sr = new StreamReader(fs);
    try
    {
        string firstLine = sr.ReadLine();
        nPeopleToRing = uint.Parse(firstLine);
        isOpen = true;
    }
    catch (FormatException e)
    {
        throw new ColdCallFileFormatException(
           "First line isn\'t an integer", e);
    }
}

-   Dòng đầu tiên kiểm tra xem liệu mã client có gọi không hợp lí nó sau khi đối tượng bị huỷ hay không . Ném ra một đối tượng ObjectDisposedException được định nghĩa trước nếu nó xuất hiện.

-   Kế tiếp phương thức chứa đựng khối try đầu tiên, mục đích của khối này là đón bắt bất kì lỗi trả về từ dòng đầu tiên không chứa đựng một số nguyên, nếu vấn đề phát ra, thời gian chạy .NET sẽ tung ra ngoại lệ FormatException mà chúng ta sẽ bắt và chuyển thành một ngoại lệ có nhiều ý nghĩa hơn. Ngoại lệ mới được tung ra sẽ bị bẫy bởi khối try ngoài nhất.

-   Nếu mọi thứ tốt đẹp, chúng ta sẽ thiết lập trường isopen là true để chỉ định việc kết nối đến tập tin đang có giá trị.và dữ liệu có thể được đọc. Phương thứcProcessNextPerson() chứa đựng khối try bên trong.

public void ProcessNextPerson()
{
    if (isDisposed)
        throw new ObjectDisposedException("peopleToRing");
    if (!isOpen)
        throw new UnexpectedException(
           "Attempt to access cold call file that is not open");
    try
    {
        string name;
        name = sr.ReadLine();
        if (name == null)
            throw new ColdCallFileFormatException("Not enough names");
        if (name[0] == 'Z')
        {
            throw new LandLineSpyFoundException(name);
        }
        Console.WriteLine(name);
    }
    catch (LandLineSpyFoundException e)
    {
        Console.WriteLine(e.Message);
    }

    finally
    {
    }
}

Có 2 vấn đề cần quan tâm trong công đoạn này :

  • Thứ nhất là đọc tên của người gián điệp khi đó khối catch trong phương thức này sẽ xử lí. bởi vì ngoại lệ bị bắt ở đây bên trong vòng lặp nghĩa là việc thực thi có thể tiếp tục trong phương thức main() của chương trình, và tên tiếp theo trong tập tin sẽ xử lí.
  • Thứ hai là khi đọc tên của người kế tiếp thì thấy rằng đã đến vị trí kết thúc tập tin, cách mà phương thức StreamReader’s ReadLine() làm việc là nếu chúng ta đã qua điểm kết thúc tập tin, thì nó không ném ra ngoại lệ mà chỉ trả về null, nếu ta tìm thấy chuỗinull ta biết rằng tập tin định dạng sai và ta ném ra ngoại lệColdCallFileFormatException mà sẽ đưọc bắt bởi hàm xử lí ngoại lệ bên ngoài (là nguyên nhân của việc thực thi bị ngắt).

-   Thuộc tính NPeopleToRing cho biết số người dự định sẽ có trong tập tin. Phương thứcDispose() mà đóng một tập tin. nó sẽ kiểm tra có thực là có một tập tin để đóng trước khi đóng.

public uint NPeopleToRing
{
    get
    {
        if (isDisposed)
            throw new ObjectDisposedException("peopleToRing");
        if (!isOpen)
            throw new UnexpectedException(
               "Attempt to access cold call file that is not open");
        return nPeopleToRing;
    }
}

public void Dispose()
{
    if (isDisposed)
        return;

    isDisposed = true;
    isOpen = false;
    if (fs != null)
    {
        fs.Close();
        fs = null;
    }
}

Định nghĩa một lớp ngoại lệ

-   Cuối cùng chúng ta cần định nghĩa 3 lớp ngoại lệ riêng . sau đây là sự thi hành đầy đủ của lớp LandLineSpyFoundException:

class LandLineSpyFoundException : ApplicationException
{
    public LandLineSpyFoundException(string spyName)
        : base("LandLine spy found, with name " + spyName)
    {
    }

    public LandLineSpyFoundException(
       string spyName, Exception innerException)
        : base(
           "LandLine spy found with name " + spyName, innerException)
    {
    }
}

-   Lưu ý rằng ta dẫn xuất nó từ ApplicationException, chúng ta giả sử rằng thông điệp được truyền vào hàm dựng chỉ là tên của người gián điệp được tìm thấy. Chúng ta cũng cung cấp 2 hàm dựng, một cái đơn giản lấy thông điệp, cái kia cũng lấy ngoại lệ bên trong như là 1 thông số. Khi định nghiã lớp ngoại lệ riêng biệt ta nên xây dựng ít nhất 2 hàm dựng (mặc dù trong thí dụ trên chúng ta không cần hàm dựng thứ 2).

-   Sau đây là lớp ColdCallFileFormatException:

class ColdCallFileFormatException : ApplicationException
{
    public ColdCallFileFormatException(string message)
        : base(message)
    {
    }

    public ColdCallFileFormatException(
       string message, Exception innerException)
        : base(message, innerException)
    {
    }
}

-   Và cuối cùng là lớp UnexpectedException mà cũng gần giống như lớpColdCallFieFormatException

class UnexpectedException : ApplicationException
{
    public UnexpectedException(string message)
        : base(message)
    {
    }

    public UnexpectedException(string message, Exception innerException)
        : base(message, innerException)
    {
    }
}

-   Bây giờ ta sẵn sàn để kiểm tra chương trình. tạo ra 2 tập tin một cái là tập tin hợp lệ people.txt và cái thứ 2 không hợp lệ people2.txt

49
Avon from 'Blake's 7'
Zbigniew Harlequin
Simon Robinson
Christian Peak

và cuối cùng thử một tập tin không tồn tại people3.txt. chạy 3 lần ta có các kết quả sau :

Chạy Chương trình Ví dụ 2:

Please type in the name of the file containing the names of the people to be cold-called > people.txt
Avon from 'Blake's 7'
LandLine spy found, with name Zbigniew Harlequin
Simon Robinson
Christian Peak
All callees processed correctly
Please type in the name of the file containing the names of the people to be cold-called > people2.txt
Avon from 'Blake's 7'
LandLine spy found, with name Zbigniew Harlequin
Simon Robinson
Christian Peak
The file people2.txt appears to have been corrupted
Details of the problem are: Not enough names
Please type in the name of the file containing the names of the people to be cold-called > people3.txt
The file people3.txt does not exist

IV.  Tài liệu tham khảo thêm

- Professional C# 3rd của Wrox

-  Exceptions and Exception Handling (C# programming guide) in MSDN

-  Exception Handling in C#

- Do Not Catch Exceptions That You Cannot Handle

Bạn thấy bài viết này như thế nào?: 
Average: 9 (1 vote)
Ảnh của Binh Tran Thanh

Drupal Consultant

Started my career as a drupal8 developer in EM Solutions . I love learning Web technologies like HTML, CSS, PHP, Jquery Ajax and Drupal backend . Currently working as a drupal backend developer.

Advertisement

 

jobsora

Dich vu khu trung tphcm

Dich vu diet chuot tphcm

Dich vu diet con trung

Quảng Cáo Bài Viết

 
Hướng dẫn viết Drupal 8 Image Effect Plugin cho Image Styles

Hướng dẫn viết Drupal 8 Image Effect Plugin cho Image Styles

Recently at Drupal South I had the opportunity to upgrade the Image Style Quality module to Drupal 8

Những điểm bất tiện trên giao diện mới của Gmail

Những điểm bất tiện trên giao diện mới của Gmail

Đối với người sử dụng là doanh nhân hay các doanh nghiệp, Gmail đang dần đánh mất lòng tin của đối tượng người dùng này. Có vẻ như Google đang muốn biến sản phẩm...

Bắt đầu làm quen với Big Data - How, When and Who

Bắt đầu làm quen với Big Data - How, When and Who

Big Data là nhu cầu đang tăng trưởng lớn đến nỗi Software AG, Oracle, IBM, Microsoft, SAP, EMC, HP và Dell đã chi hơn 15 tỉ USD cho các công ty 

Công ty diệt chuột T&C

 

Diet con trung