Thứ Ba, ngày 13 tháng 4 năm 2010

// // 1 comment

Xử lý ngoại lệ C#

1.      Định nghĩa ngoại lệ và trình xử lý ngoại lệ
Ngoại lệ: Là một đối tượng đóng gói những thông tin về sự cố của một chương trình không bình thường.



Một trình xử lý ngoại lệ:
Là một khối lệnh chương trình được thiết kế xử lý các ngoại lệ mà chương trình phát sinh.


Xử lý ngoại lệ được thực thi trong trong câu lệnh catch. Một cách lý tưởng thì nếu một ngoại lệ được bắt và được xử lý, thì chương trình có thể sửa chữa được vấn đề và tiếp tục thực hiện hoạt động. Thậm chí nếu chương trình không tiếp tục, bằng việc bắt giữ ngoại lệ chúng ta có cơ hội để in ra những thông điệp có ý nghĩa và kết thúc chương trình một cách rõ ràng. Nếu đoạn chương trình của chúng ta thực hiện mà không quan tâm đến bất cứ ngoại lệ nào mà chúng ta có thể gặp (như khi giải phóng tài nguyên mà chương trình được cấp phát), chúng ta có thể đặt đoạn mã này trong khối finally, khi đó nó sẽ chắc chắn sẽ được thực hiện thậm chí ngay cả khi có một ngoại lệ xuất hiện.


Phát sinh và bắt giữ ngoại lệ

Trong ngôn ngữ C#, chúng ta chỉ có thể phát sinh (throw) những đối tượng các kiểu dữ
liệu   là  System.Exception,   hay   những   đối   tượng   được   dẫn   xuất   từ   kiểu   dữ   liệu   này.
Namespace System của CLR chứa một số các kiểu dữ liệu xử lý ngoại lệ mà chúng ta có thể sử dụng  trong  chương  trình.  Những kiểu dữ  liệu ngoại   lệ  này bao gồm  ArgumentNull-Exception, InValidCastException, và OverflowException, cũng như nhiều lớp khác nữa.
2.      Lệnh Throw

Cú pháp: throw  new System.Exception();
Khi phát sinh ngoại lệ thì ngay tức khắc sẽ làm ngừng việc thực thi trong khi CLR sẽ tìm
kiếm một   trình xử  lý ngoại   lệ.  Nếu một   trình xử  lý ngoại   lệ không được  tìm  thấy  trong phương thức hiện thời, thì CLR tiếp tục tìm trong phương thức gọi cho đến khi nào tìm thấy. Nếu CLR trả về lớp Main() mà không tìm thấy bất cứ trình xử lý ngoại lệ nào, thì nó sẽ kết thúc chương trình.

Ví dụ:
using System;
using System.Collections.Generic;
using System.Text;

namespace  Programming_CSharp
{
    public  class Test
    {
        public static void Main()
        {
            Console.WriteLine("hàm Main....");
            Test  t = new Test();
            t.Func1();
            Console.WriteLine("Kết thúc hàm Main...");
        }
        public void Func1()
        {
            Console.WriteLine("Bắt đầu hàm Func1...");
            Func2();
            Console.WriteLine("Kết thúc hàm  Func1...");
        }
        public void Func2()
        {
            Console.WriteLine("Bắt đầu hàm  Func2...");
            throw  new  System.Exception();
            Console.WriteLine("Kết thúc hàm  Func2...");
        }
    }
}


Giải thích ví dụ trên như sau: Hàm Main() gọi hàm Func1(). Hàm Func1() thực hiện lệnh in ra màn hình dòng “bắt đầu hàm Func1” sau đó nó gọi tới hàm Func2(). Hàm Func2() lại in ra dòng “bắt đầu hàm Func2” sau đó nó sẽ phát sinh ra một ngoại lệ dùng câu lệnh throw  new  System.Exception(). Tại đây chương trình bị ngừng thực thi, CLR sẽ tìm kiếm trình xử lý ngoại lệ cho ngoại lệ  hàm Func2() phát sinh. CLR sẽ lần lượt tìm kiếm trong stack , ở hàm Func1() nhưng không có trình xử lý ngoại lệ nào, nó sẽ tiếp tục tìm đến hàm main nhưng ở hàm này cũng không có nên CLR sẽ gọi trình xử lý ngoại lệ mặc định, nó sẽ xuất ra một thông điệp lỗi như các bạn thấy khi thực thi chương trình.

3.      Lệnh Try Catch

Trong C#, một trình xử lý ngoại lệ hay một đoạn chương trình xử lý các ngoại lệ được gọi là một khối catch và được tạo ra với từ khóa catch.

Chúng ta sẽ viết lại ví dụ trên nhưng đặt throw vào trong khối try và một khối catch sẽ dùng để xử lý ngoại lệ do lệnh throw phát sinh. Khối catch sẽ đưa ra thông báo là đã có một lỗi được xử lý.

using System;
using System.Collections.Generic;
using System.Text;

namespace  Programming_CSharp
{
    public  class Test
    {
        public static void Main()
        {
            Console.WriteLine("hàm Main....");
            Test  t = new Test();
            t.Func1();
            Console.WriteLine("Kết thúc hàm Main...");
            Console.ReadLine();
        }
        public void Func1()
        {
            Console.WriteLine("Bắt đầu hàm Func1...");
            Func2();
            Console.WriteLine("Kết thúc hàm  Func1...");
        }
        public void Func2()
        {
            Console.WriteLine("Bắt đầu hàm  Func2...");
            try
            {
                Console.WriteLine("Bắt đầu Khối try");
                    throw  new System.Exception();
                    Console.WriteLine("Kết thúc khối try");
            }           
            catch
            {
                Console.WriteLine("Ngoại lệ đã được xử lý");
            }
            Console.WriteLine("Kết thúc hàm Func2...");
        }     
    }
}

Tương tự như ví dụ tôi đã vừa trình bày, cho đến khi chương trình thực hiện hàm Func2() khi lệnh throw phát sinh ra ngoại lệ, chương trình sẽ bị ngừng thực hiện và CLR sẽ tìm phần xử lý ngoại lệ trong stack, đầu tiên nó sẽ gọi đến hàm Func1() tại đây hàm Func2() được gọi và nó sẽ tìm thấy phần xử lý ngoại lệ trong khối catch ,  nó sẽ in ra dòng “Ngoại lệ đã được xử lý”. Đó cũng là lý do mà chương trình sẽ không bao giờ in ra dòng “Kết thúc khối try”.

4.      Lệnh Finally
     
Trong một số tình huống chúng ta cần phải thực hiện bất cứ khi nào một ngoại lệ được phát sinh ra, ví dụ như việc đóng một tập tin. Để làm việc này chúng ta có thể đặt câu lệnh trong cả hai khối try và catch. Tuy nhiên có một cách giải quyết tốt hơn, đó là sử dụng câu lệnh Finnally.
Các hành động đặt trong khối finnally sẽ luôn được thực hiện mà không cần quan tâm tới việc có hay không một ngoại lệ phát sinh trong chương trình.

Chúng ta cùng xét ví dụ sau:

using System;
namespace  Programming_CSharp
{

    public class Test
    {
        public static void Main()
        {
            Test  t = new  Test();
            t.TestFunc();
            Console.ReadLine();
        }
        // chia hai số và xử lý ngoại lệ nếu có
        public void TestFunc()
        {
            try               
            {
                Console.WriteLine("mở file");
                double  a = 5;
                double  b = 0;
                Console.WriteLine("{0} /{1} = {2}", a, b, DoDivide(a,b));
                Console.WriteLine("dòng này có thể xuất hiện hoặc không");
            }
            catch (System.DivideByZeroException)
            {
                Console.WriteLine("lỗi chia cho 0!");
            }
            catch
            {
                Console.WriteLine("không có ngoại lệ");
            }
            finally
            {
                Console.WriteLine("Đóng tệp.");
            }
        }
        // thực hiện chia nếu hợp lệ
        public double DoDivide(double a, double b)
        {
            if ( b == 0)
            {
                throw new System.DivideByZeroException();
            }
            if ( a == 0)
            {
                throw new System.ArithmeticException();
            }
            return a/b;
        }
    }
}

Đầu tiên hãy gán a= 5 và b=0 chạy chương trình Bạn sẽ thấy lệnh Console.WriteLine("dòng này có thể xuất hiện hoặc không");
Sẽ không được thực hiện do xuất hiện một ngoại lệ là lỗi chia cho 0 và chương trình sẽ tìm tới phần xử lý ngoại lệ này
mà bỏ qua phần lệnh tiếp theo.



Sau đó bạn thay đổi giá trị b=12 và chạy chương trình thì lệnh

Console.WriteLine("dòng này có thể xuất hiện hoặc không"); được thực hiện.

Tuy nhiên ở cả 2 trường hợp bạn đề thấy thực hiện lệnh    Console.WriteLine("Đóng tệp.");

Đó là vì lệnh này đã được đặt trong khối Finally.

Nắm được cách xử lý ngoại lệ qua việc sử dụng các câu lệnh throw, catch và finally sẽ giúp bạn lập trình có hiệu quả hơn.