Trong bài này chúng ta sẽ tìm hiểu:

  • Xử lý ngoại lệ với try, catch, finally
  • Kiểm soát tràn số nguyên bằng từ khoá checkedunchecked
  • Phát sinh ngoại lệ với từ khoá throw
  • Đảm bảo đoạn mã luôn thực thi với khối lệnh finally

Các lỗi trong lập trình

Khi lập trình, chúng ta thường gặp một số kiểu lỗi sau:

  • Lỗi cú pháp (syntax errors): gõ sai chính tả, thiếu từ khoá, mã không chính xác. Thường có một số dấu hiệu trong VS để cảnh báo đoạn code của chúng ta bị lỗi cú pháp, thường là các đường gợn sóng xanh hay đỏ như hình dưới với biến i chưa được khai báo (gợn song đỏ):

  • Lỗi logic (logical errors): thường là do thuật toán sai; chương trình vẫn chạy bình thường nhưng tạo ra các kết quả không mong muốn hay mong đợi. Ví dụ đoạn code sau nhầm lẫn vị trí giữa hai biến toAddress và fromAddress, và lỗi này sẽ khó phát hiện trong chương trình có hàng trăm dòng mã:

string fromAddress = "you@example.com";

string toAddress = EmailAddress.Text;

myMessage.From = new MailAddress(toAddress);

myMessage.To.Add(new MailAddress(fromAddress));

  • Lỗi Runtime (Runtime errors): xuất hiện trong thời gian chạy chương trình. Lỗi nay thường khó phát hiện và cần một chiến lược để xử lí.

Lệnh try/catch

Một cách để xử lý các lỗi xuất hiện  là sử dụng lệnh try/catch. Các lệnh sẽ được viết trong khối lệnh try, nếu một lỗi xuất hiện chương trình sẽ thoát khỏi try để đến với các lệnh trong khối lệnh catch. Ví dụ đoạn mã sau:


try

{

   int a = 9;

   int b = 3;

   double c = a / b;

   MessageBox.Show(c.ToString("f2"));

}

catch

{

  MessageBox.Show("Lỗi");

}

Kết quả khi thực thi:

Giả sử b = 0, kết quả:

Nếu không dùng try/catch và b = 0, chương trình sẽ phát sinh ngoại lệ như sau:

Có thể dùng nhiều lệnh catch, mỗi catch tương ứng với một ngoại lệ, ví dụ đoạn mã:


try

{

  int a, b;

  a = int.Parse(txtnum1.Text);

  b = int.Parse(txtnum2.Text);

  double c = a / b;

  MessageBox.Show(c.ToString("f2"));

}

catch (OverflowException oe)

{

  MessageBox.Show("Lỗi tràn số!");

}

catch (FormatException fe)

{

  MessageBox.Show("Xin nhập số nguyên!");

}

OverflowException là ngoại lệ phát sinh nếu giá trị gán đến một biến vượt quá giá trị cho phép của kiểu dữ liệu của biến đó. FormatException là ngoại lệ xuất hiện nếu giá trị gán đến một biến không phù hợp với kiểu dữ liệu của biến đó. Xem kết quả của ví dụ trên khi:

a = 9

b = 3

a = 1234567899999

b = 3

a = abc

b = 3

OverflowExceptionFormatException là những ngoại lệ cụ thể; có một kiểu ngoại lệ phổ quát cho tất cả các ngoại lệ là Exception. Nếu chúng ta đặt cacth với kiểu ngoại lệ Exception trên tất cả các catch có kiểu ngoại lệ khác thì các lệnh trong catch với kiểu Exception sẽ luôn luôn thực thi với bất kỳ ngoại lệ nào phát sinh. Ví dụ sau sẽ phát sinh lỗi khi biên dịch:

Ngoại lệ phổ quát Exception luôn đặt sau các ngoại lệ khác:


try

{

  int a, b;

  a = int.Parse(txtnum1.Text);

  b = int.Parse(txtnum2.Text);

  double c = a / b;

  MessageBox.Show(c.ToString("f2"));

}

catch (OverflowException oe)

{

  MessageBox.Show("Lỗi tràn số!");

}

catch (FormatException fe)

{

  MessageBox.Show("Xin nhập số nguyên!");

}

catch (Exception ex)

{

  MessageBox.Show("Lỗi");

}

Nếu a = 123456789999999, b = 3

Nếu a = abc, b = 3

Nếu a = 9, b = 0

checked unchecked

Khi thực hiện tính toán số học với các giá trị kiểu int hay long có thể chúng ta sẽ gặp các trường hợp tràn số (overflow). Mặc định, trình biên dịch sẽ không phát hiện như ví dụ đoạn mã:


int i1 = 10;

int i2 = 2147483647 + i1;

MessageBox.Show(i2.ToString());

Sẽ cho kết quả không mong đợi:

Chúng ta có thể dùng từ khoá checked để phát sinh ngoại lệ khi xuất hiện tràn số:


int i1 = 10;

checked

{

  int i2 = 2147483647 + i1;

  MessageBox.Show(i2.ToString());

}

Kết quả:

Có thể dùng checked trong biểu thức:

Đối lập với checked, có thể dùng từ khoá unchecked để không phát sinh ngoại lệ tràn số:


int i1 = 10;

unchecked

{

  int i2 = 2147483647 + i1;

  MessageBox.Show(i2.ToString());

}

Hay


int i1 = 10;

int i2 = unchecked(2147483647 + i1);

MessageBox.Show(i2.ToString());

Lệnh throw

Chúng ta có thể dùng lệnh throw để phát sinh các ngoại lệ thông qua các đối tượng ngoại lệ. Các đối tượng này chứa thông tin chi tiết về ngoại lệ bao gồm các thông điệp lỗi.

Ví dụ đoạn mã:


int a, b;

double c;

a = int.Parse(txtnum1.Text);

b = int.Parse(txtnum2.Text);

if (b != 0)

   c = a / b;

else

   throw new DivideByZeroException("Lỗi chia 0");

Ví dụ sau throw sẽ phát sinh ngoại lệ nếu một số nguyên chia cho 0:

Lệnh finally

Chúng ta có thể dùng khối lệnh finally cùng với khối lệnh try hay try/catch. Với finally, chúng ta có thể giải phóng tất cả tài nguyên được cấp trong khối lệnh try và vẫn có thể chạy code ngay cả khi một ngoại lệ được phát sinh. Trong ví dụ sau chúng ta cần đảm bảo lệnh reader.Close () luôn thực thi để giải phóng tài nguyên bất chấp ngoại lệ có xuất hiện trong try hay không:


TextReader reader = null;

try

{

  reader = src.OpenText();

  string line;

  while ((line = reader.ReadLine()) != null)

  {

    source.Text += line + "\n";

  }

}

finally

{

  if (reader != null)

  {

    reader.Close();

  }

}

Khối lệnh finally đảm bảo lệnh reader.Close() luôn thực thi ngay cả có ngoại lệ xuất hiện trong try.

Chúng ta thường kết hợp các khối lệnh try – catch – finally cùng nhau.

Học C# và WPF >