Namespaces và xử lý ngoại lệ
- 29-11-2023
- Toanngo92
- 0 Comments
Buổi học này sẽ cung cấp thông tin về các không gian tên tích hợp sẵn và tùy chỉnh trong C# cùng với tính năng và lợi ích của chúng. Nó cũng giới thiệu các cách khác nhau để xử lý các ngoại lệ trong ứng dụng C#.
Trong buổi học này, bạn sẽ học về:
- Định nghĩa và mô tả các không gian tên (namespaces).
- Định nghĩa và mô tả các ngoại lệ (exceptions).
- Giải thích quá trình ném và bắt các ngoại lệ.
- Giải thích nested try và nhiều khối catch.
- Định nghĩa và mô tả các ngoại lệ tùy chỉnh.
Đây là một giới thiệu về không gian tên trong C#:
Một không gian tên là một phần của chương trình C# giúp tổ chức các lớp, giao diện và cấu trúc. Các không gian tên tránh xung đột tên giữa các lớp, giao diện và cấu trúc trong C#. Bạn có thể tạo nhiều không gian tên trong một không gian tên lớn.
Hãy tưởng tượng Venice, một thành phố ở Hoa Kỳ cũng như ở Ý. Người ta có thể dễ dàng phân biệt hai thành phố này bằng cách liên kết chúng với các quốc gia tương ứng của họ.
Tương tự, khi làm việc trên một dự án lớn, có thể xảy ra tình huống các lớp có cùng tên. Điều này có thể dẫn đến xung đột tên. Vấn đề này có thể được giải quyết bằng cách có các modules cá nhân của dự án sử dụng các không gian tên riêng để lưu trữ các lớp của họ. Bằng cách này, các lớp có thể có cùng tên mà không gây ra xung đột tên. Đoạn mã bên dưới đổi tên các lớp giống nhau bằng cách chèn một tiền tố mô tả.
class SamsungTelevision {
...
}
class SamsungWalkMan {
...
}
class SonyTelevision {
...
}
class SonyWalkMan {
...
}
Trong Đoạn mã trên, các lớp giống nhau Television và WalkMan được đổi tên bằng cách thêm tên công ty để tránh xung đột. Tuy nhiên, khi làm điều này, có thể dẫn đến việc tên của các lớp trở nên dài và khó quản lý.
Đoạn mã bên dưới thể hiện một giải pháp để vượt qua điều này, bằng cách sử dụng các không gian tên.
namespace Samsung {
class Television {
// ... Implementation for Samsung Television
}
class Walkman {
// ... Implementation for Samsung Walkman
}
}
namespace Sony {
class Television {
// ... Implementation for Sony Television
}
class Walkman {
// ... Implementation for Sony Walkman
}
}
Trong Đoạn mã trên, mỗi lớp tương đồng được đặt trong không gian tên tương ứng, thể hiện tên của các công ty tương ứng. Có thể thấy rằng đây là một cách sắp xếp gọn gàng, có tổ chức hơn và có cấu trúc hơn để xử lý xung đột tên.
Mục lục
Sử dụng Namespaces
Trong C#, cho phép xác định một định danh duy nhất cho mỗi không gian tên. Định danh này giúp truy cập vào các lớp trong không gian tên đó. Ngoài các lớp, các cấu trúc dữ liệu sau có thể được khai báo trong một không gian tên:
- Interface: Một loại tham chiếu chứa các khai báo về sự kiện, trình index, phương thức và thuộc tính.
- Structure: Một cấu trúc là một loại giá trị có thể chứa các giá trị của các loại dữ liệu khác nhau.
- Enumeration: Một enumeration là một loại giá trị gồm danh sách các hằng số có tên.
- Delegate: Một delegate là một loại tham chiếu được người dùng định nghĩa, chỉ đến một hoặc nhiều phương thức. Nó có thể được sử dụng để truyền dữ liệu như tham số vào các phương thức.
Các không gian tên tích hợp sẵn
Khung.NET bao gồm một số không gian tên tích hợp sẵn chứa các lớp, giao diện, cấu trúc, delegate và enumeration. Các không gian tên này được gọi là các không gian tên được xác định bởi hệ thống. Các không gian tên tích hợp sẵn phổ biến nhất trong Khung.NET là System. Không gian tên System chứa các lớp định nghĩa các loại dữ liệu giá trị và tham chiếu, các giao diện và các không gian tên khác. Ngoài ra, nó cũng chứa các lớp cho phép tương tác với hệ thống, bao gồm các thiết bị đầu vào và đầu ra tiêu chuẩn. Một số không gian tên phổ biến nhất trong không gian tên System là:
- System.Collections: Không gian tên System.Collections chứa các lớp và giao diện định nghĩa các cấu trúc dữ liệu phức tạp như danh sách, hàng đợi, bit arrays, bảng băm và từ điển.
- System.Data: Không gian tên System.Data chứa các lớp tạo nên kiến trúc ADO.NET. Kiến trúc ADO.NET cho phép xây dựng các thành phần có thể được sử dụng để chèn, sửa đổi và xóa dữ liệu từ nhiều nguồn dữ liệu khác nhau.
- System.IO: System.IO chứa các lớp cho phép nhà phát triển đọc và ghi từ các luồng dữ liệu và tệp tin.
- System.Net: System.Net chứa các lớp cho phép tạo ứng dụng dựa trên web.
Sử dụng không gian tên System:
Không gian tên System được nhập mặc định trong .NET Framework. Nó xuất hiện là dòng đầu tiên của chương trình cùng với từ khóa using. Để tham chiếu đến các lớp trong một không gian tên tích hợp sẵn, nhà phát triển phải rõ ràng tham chiếu đến các lớp cần thiết. Điều này được thực hiện bằng cách chỉ định tên không gian tên và tên lớp cách nhau bằng dấu chấm (.) sau từ khóa using ở đầu chương trình.
Nhà phát triển có thể tham chiếu đến các lớp trong không gian tên mà không sử dụng từ khóa using. Tuy nhiên, điều này dẫn đến sự lặp lại vì nhà phát triển phải đề cập đến toàn bộ khai báo mỗi khi tham chiếu đến lớp trong mã nguồn.
Cú pháp sau được sử dụng để truy cập vào một phương thức trong một không gian tên được xác định sẵn:
<NamespaceName>.<ClassName>.<MethodName>;
trong đó,
- NamespaceName: Là tên của không gian tên.
- ClassName: Là tên của lớp mà nhà phát triển muốn truy cập.
- MethodName: Là tên của phương thức trong lớp mà muốn gọi.
Cú pháp sau được sử dụng để truy cập vào các không gian tên được xác định sẵn với từ khóa using:
using <NamespaceName>
using <NamespaceName>.<ClassName>
trong đó,
- NamespaceName:Là tên của không gian tên và nó sẽ tham chiếu đến tất cả các lớp, giao diện, cấu trúc và enumeration.
- ClassName: Là tên của lớp cụ thể được định nghĩa trong không gian tên mà nhà phát triển muốn truy cập.
Đoạn mã bên dưới minh họa việc sử dụng từ khóa using khi sử dụng các không gian tên.
using System;
class World {
static void Main(string[] args) {
Console.WriteLine("Hello, World!");
}
}
Trong đoạn mã trên, không gian tên System được nhập vào chương trình bằng từ khóa using. Nếu điều này không được thực hiện, chương trình sẽ không thể biên dịch được vì lớp Console tồn tại trong không gian tên System.
Kết quả:
Hello, World!
Đoạn mã bên dưới tham chiếu đến lớp Console của không gian tên System nhiều lần. Ở đây, lớp này không được nhập nhưng các thành viên của không gian tên System được sử dụng cùng với các câu lệnh.
class World {
static void Main(string[] args) {
System.Console.WriteLine("Hello World");
System.Console.WriteLine("This is C# Programming");
System.Console.WriteLine("You have executed a simple program of C#");
}
}
Kết quả:
Hello World
This is C# Programming
You have executed a simple program of C#
Không gian tên tùy chỉnh
Trong C#, cho phép tạo các không gian tên có tên phù hợp để tổ chức cấu trúc, lớp, giao diện, delegate và enumeration có thể được sử dụng trong các ứng dụng C# khác nhau. Các không gian tên do người dùng xác định được gọi là không gian tên tùy chỉnh. Khi sử dụng một không gian tên tùy chỉnh, nhà phát triển không cần phải lo lắng về xung đột tên với các lớp, giao diện, và những thành phần khác trong các không gian tên khác.
Các không gian tên tùy chỉnh cho phép nhà phát triển kiểm soát phạm vi của một lớp bằng cách quyết định không gian tên phù hợp cho lớp đó. Một không gian tên tùy chỉnh được khai báo bằng từ khóa namespace và được truy cập với từ khóa using tương tự như bất kỳ không gian tên tích hợp sẵn nào.
Đoạn mã bên dưới tạo một không gian tên tùy chỉnh có tên là Department.
namespace Department {
class Sales {
// ...
}
}
Lưu ý: Nếu một không gian tên không được khai báo trong một tệp nguồn C#, trình biên dịch sẽ tạo ra một không gian tên mặc định trong mỗi tệp. Không gian tên không có tên này được gọi là không gian tên toàn cầu.
Một khi một không gian tên được tạo, C# cho phép bổ sung các lớp khác vào sau đó trong không gian tên đó. Do đó, các không gian tên có tính chất cộng hưởng (additive). C# cũng cho phép một không gian tên được khai báo nhiều hơn một lần. Những không gian tên này có thể được chia nhỏ và lưu trữ trong các tệp tin riêng biệt hoặc trong cùng một tệp. Trong quá trình biên dịch, các không gian tên này được kết hợp lại với nhau. Việc chia không gian tên qua nhiều tệp được minh họa trong các đoạn mã 1, 2 và 3.
Đoạn mã 1:
using System;
namespace Automotive {
public class SpareParts {
string _spareName;
public SpareParts() {
_spareName = "Gear Box";
}
public void Display() {
Console.WriteLine("Spare Part name: " + _spareName);
}
}
}
Đoạn mã 2:
using System;
namespace Automotive {
public class Category {
string _category;
public Category() {
_category = "Multi Utility Vehicle";
}
public void Display() {
Console.WriteLine("Category: " + _category);
}
}
}
Đoạn mã 3:
namespace Automotive {
class Toyota {
static void Main(string[] args) {
Category objCategory = new Category();
SpareParts objSpare = new SpareParts();
objCategory.Display();
objSpare.Display();
}
}
}
Vậy là ba lớp SpareParts, Category và Toyota được lưu trữ trong ba tệp tin khác nhau là SpareParts.cs, Category.cs và Toyota.cs tương ứng. Mặc dù các lớp này được lưu trữ trong các tệp tin khác nhau, chúng vẫn thuộc cùng một không gian tên, tức là Automotive. Do đó, không cần thiết phải tham chiếu đến không gian tên trong trường hợp này. Trong các ví dụ này, một không gian tên duy nhất Automotive được sử dụng để bao gồm ba lớp khác nhau. Mã cho mỗi lớp được lưu dưới dạng các tệp tin riêng biệt. Khi ba tệp tin này được biên dịch, kết quả sẽ vẫn là không gian tên Automotive. Hình bên dưới thể hiện kết quả của ứng dụng chứa ba tệp tin này.
Các bộ điều khiển truy cập cho Namespaces
Các không gian tên luôn luôn là public. Nhà phát triển không thể áp dụng các bộ điều khiển truy cập như public, protected, private hoặc internal cho các không gian tên. Nếu bất kỳ bộ điều khiển truy cập nào được chỉ định trong khai báo không gian tên, trình biên dịch sẽ tạo ra một lỗi.
Đặt tên được quyền chỉ định
C# cho phép sử dụng một lớp ngoài không gian tên của nó. Một lớp ngoài không gian tên của nó có thể được truy cập bằng cách chỉ định không gian tên của nó tiếp theo là toán tử dấu chấm và tên lớp. Hình thức này của việc chỉ định lớp được biết đến là Fully Qualified Naming (Đặt tên được quyền chỉ định). Việc sử dụng tên đầy đủ dẫn đến các tên dài và lặp đi lặp lại trong toàn bộ mã. Thay vào đó, một nhà phát triển có thể truy cập các lớp ngoài không gian tên của chúng bằng từ khóa using
. Điều này làm cho các tên ngắn gọn và ý nghĩa hơn. Đoạn mã bên dưới hiển thị tên của học sinh, ID, môn học và điểm số bằng cách sử dụng tên đầy đủ được quyền chỉ định.
using System;
namespace Students {
class StudentDetails {
string _studName = "Alexander";
int _studId = 30;
public StudentDetails() {
Console.WriteLine("Student Name: " + _studName);
Console.WriteLine("Student ID: " + _studId);
}
}
}
namespace Examination {
class ScoreReport {
public string Subject = "Science";
public int Marks = 60;
static void Main(string[] args) {
Students.StudentDetails objStudents = new Students.StudentDetails();
ScoreReport objReport = new ScoreReport();
Console.WriteLine("Subject: " + objReport.Subject);
Console.WriteLine("Marks: " + objReport.Marks);
}
}
}
Trong đoạn mã trên, lớp ScoreReport
sử dụng lớp StudentDetails
được định nghĩa trong không gian tên Examination
. Lớp này được truy cập bằng tên đầy đủ của nó.
Quy ước đặt tên cho Namespaces
Có một số quy ước đặt tên cần tuân theo khi tạo các namespace:
- Sử dụng Pascal case cho việc đặt tên các namespace.
- Sử dụng dấu chấm để phân tách các thành phần logic.
- Sử dụng tên số nhiều cho các namespace nếu có thể.
- Đảm bảo rằng một namespace và một lớp không có cùng tên.
- Đảm bảo rằng tên của một namespace không giống tên của assembly.
Bí danh cho Namespaces
Bí danh là những cái tên tạm thời thay thế đề cập đến cùng một thực thể. Không gian tên được đề cập đến bằng từ khóa using
liên kết đến tất cả các lớp trong không gian tên đó. Tuy nhiên, đôi khi một nhà phát triển có thể muốn truy cập chỉ một lớp từ một không gian tên cụ thể. Nhà phát triển có thể sử dụng tên bí danh để tham chiếu đến lớp cần thiết và ngăn việc sử dụng tên đầy đủ.
Bí danh cũng hữu ích khi một chương trình chứa nhiều khai báo không gian tên lồng nhau và nhà phát triển muốn phân biệt lớp nào thuộc không gian tên nào. Bí danh sẽ làm cho mã nguồn dễ đọc hơn đối với các lập trình viên khác và giúp dễ dàng bảo trì. Hình bên dưới hiển thị một ví dụ về việc sử dụng bí danh cho không gian tên.
Cú pháp sau được sử dụng để tạo bí danh cho không gian tên:
using <aliasName> = <NamespaceName>
trong đó,
aliasName
: là tên do người dùng định nghĩa được gán cho không gian tên.
Đoạn mã bên dưới tạo một không gian tên tùy chỉnh được gọi là Bank.Accounts.EmployeeDetails
:
namespace Bank.Accounts.EmployeeDetails {
public class Employees {
public string EmpName;
}
}
Đoạn mã bên dưới hiển thị tên của một nhân viên bằng cách sử dụng các bí danh của các không gian tên System.Console
và Bank.Accounts.EmployeeDetails
.
using IO = System.Console;
using Emp = Bank.Accounts.EmployeeDetails;
class AliasExample {
static void Main(string[] args) {
Emp.Employees objEmp = new Emp.Employees();
objEmp.EmpName = "Peter";
I0.WriteLine("Employee Name: " + objEmp.EmpName);
}
}
Kết quả:
Employee Name: Peter
Trong Đoạn mã trên, Bank.Accounts.EmployeeDetails
được đặt bí danh là “Emp
” và System.Console
được đặt bí danh là “IO
“. Những tên bí danh này được sử dụng trong lớp AliasExample
để truy cập các lớp Employees
và Console
được định nghĩa trong các không gian tên tương ứng.
Bí danh không gian tên có tính xác định
Trong một số trường hợp, khi bí danh được cung cấp cho một không gian tên trùng khớp với tên của một không gian tên hiện có. Khi đó, trình biên dịch sẽ tạo ra một lỗi khi thực thi chương trình tham chiếu đến không gian tên đó. Điều này được minh họa trong đoạn mã bên dưới.
using System;
using System.Collections.Generic;
using System.Text;
using System.Utility_Vehicle.Car;
using Utility_Vehicle = Automotive.Vehicle.Jeep;
namespace Automotive {
namespace Vehicle {
namespace Jeep {
class Category {
string _category;
public Category() {
_category = "Multi Utility Vehicle";
}
public void Display() {
Console.WriteLine("Jeep Category: " + _category);
}
}
}
}
class Automobile {
static void Main(string[] args) {
Category objCat = new Category();
objCat.Display();
Utility_Vehicle.Category objCategory = new Utility_Vehicle.Category();
objCategory.Display();
}
}
}
Một ví dụ khác được tạo trong dự án Automotive
để lưu trữ chương trình được hiển thị trong đoạn mã bên dưới.
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility_Vehicle {
namespace Car {
class Category {
string _category;
public Category() {
_category = "Luxury Vehicle";
}
public void Display() {
Console.WriteLine("Car Category: " + _category);
}
}
}
}
Cả hai tệp này sau đó được biên dịch như trong hình bên dưới. Kết quả của mã được biên dịch cũng có thể được thể hiện trong hình.
Trong 2 đoạn mã trên, các không gian tên Jeep
và Car
bao gồm lớp Category
. Một bí danh Utility_Vehicle
được cung cấp cho không gian tên Automotive.Jeep
Tuy nhiên, bí danh này trùng với tên của không gian tên khác mà không gian tên Car
được lồng trong đó. Để biên dịch cả hai chương trình lệnh csc
được sử dụng, sử dụng đường dẫn đầy đủ để tham chiếu đến tệp Category.cs
. Trong tệp Automobile.cs
, tên bí danh Utility_Vehicle
giống với không gian tên mà tệp đó đang tham chiếu tới. Do xung đột tên này, trình biên dịch tạo ra một lỗi. Vấn đề tham chiếu tên mơ hồ này có thể được giải quyết bằng cách sử dụng bí danh rõ ràng cho không gian tên.
Bí danh rõ ràng cho không gian tên là một tính năng mới của C# và có thể được sử dụng dưới dạng:
<LeftOperand>::<RightOperand>
trong đó,
LeftOperand
: là một bí danh không gian tên, một extern, hoặc một định danh toàn cục.RightOperand
: là một loại
Đoạn mã chính xác cho vấn đề này được minh họa trong đoạn mã bên dưới.
using System;
using System.Collections.Generic;
using System.Text;
using Utility_Vehicle.Car;
using Utility_Vehicle = Automotive.Vehicle.Jeep;
namespace Automotive {
namespace Vehicle {
namespace Jeep {
class Category {
string _category;
public Category() {
_category = "Multi Utility Vehicle";
}
public void Display() {
Console.WriteLine("Jeep Category: " + _category);
}
}
}
}
class Automobile {
static void Main(string[] args) {
Category objCat = new Category();
objCat.Display();
Utility_Vehicle::Category objCat = new Utility_Vehicle::Category();
objCategory.Display();
}
}
}
Kết quả của đoạn mã trên được hiển thị trong hình bên dưới.
Trong đoạn mã trên, trong lớp Automobile
, người viết đã sử dụng bộ điều khiển bộ danh tính của không gian tên. Bí danh Utility_Vehicle
được chỉ định trong toán hạng trái và lớp Category
trong toán hạng phải.
Các ngoại lệ trong C#
Về các ngoại lệ trong C#, chúng đại diện cho các sự kiện bất thường trong quá trình thực thi chương trình. Tương tự như một phương tiện giao thông đột ngột dừng lại do sự cố trong động cơ, các ngoại lệ làm gián đoạn quá trình thực thi bình thường của chương trình. Xử lý ngoại lệ trong C# là quá trình xử lý các lỗi thời gian chạy. Trong C#, các nhà phát triển có thể xử lý ngoại lệ bằng cách sử dụng các cấu trúc try-catch
hoặc try-catch-finally
. Ngoài ra, C# hỗ trợ tạo ra các ngoại lệ tùy chỉnh để điều chỉnh quá trình xử lý lỗi.
Hình bên dưới hiển thị các loại ngoại lệ trong C#.
Xử lý và Bắt ngoại lệ
Hãy xem xét một nhóm các bạn trai đang chơi bóng ném, trong đó một người bạn trai ném bóng và các bạn trai khác bắt bóng đó. Nếu một trong những bạn trai không bắt được quả bóng, trò chơi sẽ kết thúc. Do đó, trò chơi tiếp tục cho đến khi các bạn trai thành công trong việc bắt bóng đúng thời điểm.
Tương tự, trong lập trình C++, các ngoại lệ xảy ra khi làm việc trên một chương trình cụ thể phải được bắt bởi các xử lý ngoại lệ. Nếu chương trình không chứa xử lý ngoại lệ thích hợp, thì chương trình có thể bị kết thúc.
Bắt ngoại lệ
Xử lý ngoại lệ được thực hiện bằng cách sử dụng cấu trúc try-catch
trong C#. Cấu trúc này bao gồm hai khối, khối try
và khối catch
. Khối try
bao quanh các câu lệnh có thể tạo ra ngoại lệ. Khi những ngoại lệ này được bắt, các hành động cần thiết được thực hiện bằng cách sử dụng khối catch
. Do đó, khối catch
bao gồm các trình xử lý lỗi phù hợp để xử lý ngoại lệ.
Khối catch
đi sau khối try
và có thể hoặc không chứa tham số. Nếu khối catch
không chứa bất kỳ tham số nào, nó có thể bắt bất kỳ loại ngoại lệ nào. Nếu khối catch
chứa một tham số, nó sẽ bắt loại ngoại lệ được chỉ định bởi tham số.
Sau đây là cú pháp được sử dụng để xử lý lỗi bằng cách sử dụng các khối try
và catch
:
try {
// program code
} catch [(<ExceptionClass> <objException>)] {
// error handling code
}
trong đó,
try
: Xác định rằng khối bao quanh các câu lệnh có thể ném ra các ngoại lệ.program code
: Là các câu lệnh có thể tạo ra các ngoại lệ.catch
: Xác định rằng khối bao quanh các câu lệnh bắt các ngoại lệ và thực hiện các hành động thích hợp.ExceptionClass
: Là tên của lớp ngoại lệ. Nó là tùy chọn.objException
: Là một phiên bản của lớp ngoại lệ cụ thể. Nó có thể được bỏ qua nếuExceptionClass
được bỏ qua.
Cú pháp trình bày việc sử dụng khối try-catch
trong đoạn mã:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
class DivisionError
{
static void Main(string[] args)
{
int numOne = 133;
int numTwo = 0;
int result;
try
{
result = numOne / numTwo;
}
catch (DivideByZeroException objDivide)
{
Console.WriteLine("Exception caught: " + objDivide);
}
}
}
Trong đoạn mã trên, phương thức Main()
của lớp DivisionError
khai báo ba biến, trong đó có hai biến được khởi tạo giá trị. Khối try
chứa mã để chia numOne
cho numTwo
và lưu kết quả vào biến result
. Vì numTwo
có giá trị bằng không, khối try
ném ra một ngoại lệ. Ngoại lệ này được xử lý bởi khối catch
tương ứng, nhận objDivide
làm phiên bản của lớp DivideByZeroException
. Ngoại lệ được bắt bởi khối catch
này và thông báo phù hợp được hiển thị làm kết quả đầu ra.
Kết quả:
Exception caught: System.DivideByZeroException: Attempted to divide by zero.
at DivisionError.Main(String[] args)
Chú ý: Khối catch chỉ được thực thi nếu khối try ném ra một ngoại lệ.
Khối catch tổng quát
Khối catch
có thể xử lý tất cả các loại ngoại lệ. Tuy nhiên, loại ngoại lệ mà khối catch
xử lý phụ thuộc vào lớp ngoại lệ được chỉ định. Đôi khi, một nhà phát triển có thể không biết loại ngoại lệ mà chương trình có thể ném ra. Trong trường hợp đó, người phát triển có thể tạo một khối catch
với lớp cơ sở Exception. Các khối catch như vậy được gọi là khối catch tổng quát.
Một khối catch
tổng quát có thể xử lý tất cả các loại ngoại lệ. Tuy nhiên, nhược điểm của khối catch
tổng quát là không có phiên bản của ngoại lệ và do đó, người phát triển không thể biết được hành động phù hợp nào cần thực hiện để xử lý ngoại lệ.
Đoạn mã bên dưới minh họa cách khai báo một khối catch
tổng quát.
using System;
class Students {
string[] _names = { "James", "John", "Alexander" };
static void Main(string[] args) {
Students objStudents = new Students();
try {
objStudents._names[4] = "Michael";
} catch (Exception objException) {
Console.WriteLine("Error: " + objException);
}
}
}
Trong đoạn mã trên, một mảng chuỗi có tên là names
được khởi tạo để chứa ba giá trị. Trong khối try
, có một câu lệnh cố gắng tham chiếu đến phần tử thứ tư của mảng. Mảng chỉ có thể lưu trữ ba giá trị, vì vậy điều này sẽ gây ra một ngoại lệ. Lớp Students
bao gồm một khối catch tổng quát được khai báo với lớp cơ sở Exception
và khối catch
này có thể xử lý bất kỳ loại ngoại lệ nào.
Kết quả:
Error: System.IndexOutOfRangeException: Index was outside the bounds of the array at roject_New.ExceptionHandling.Students.Main(String[] args) in D:\Exception Handling\Students.cs:line 17
Các Thuộc tính và Phương thức của lớp Exception
Lớp System.Exception
là lớp cơ sở cho phép xử lý tất cả các ngoại lệ trong C#. Điều này có nghĩa là tất cả các ngoại lệ trong C# đều kế thừa lớp System.Exception
hoặc là trực tiếp hoặc gián tiếp. Lớp System.Exception
chứa các phương thức công cộng và bảo vệ có thể được kế thừa bởi các lớp ngoại lệ khác. Ngoài ra, lớp System.Exception
chứa các thuộc tính phổ biến cho tất cả các ngoại lệ. Bảng bên dưới mô tả một số thuộc tính của lớp System.Exception
.
Thuộc tính | Mô tả |
Message | Hiển thị thông điệp chỉ ra lý do của ngoại lệ. |
Source | Cung cấp tên của ứng dụng hoặc đối tượng gây ra ngoại lệ. |
StackTrace | Cung cấp chi tiết ngoại lệ trên ngăn xếp tại thời điểm ngoại lệ được bắt. |
InnerException | Trả về phiên bản Exception gây ra ngoại lệ hiện tại. |
Đoạn mã bên dưới thể hiện việc sử dụng một số thuộc tính công cộng của lớp System.Exception
.
using System;
class ExceptionProperties
{
static void Main(string[] args)
{
byte numOne = 200;
byte numTwo = 5;
byte result = 0;
try
{
result = checked((byte)(numOne * numTwo));
Console.WriteLine("Result = {0}", result);
}
catch (OverflowException objEx)
{
Console.WriteLine("Message: {0}", objEx.Message);
Console.WriteLine("Source: {0}", objEx.Source);
Console.WriteLine("TargetSite: {0}", objEx.TargetSite);
Console.WriteLine("StackTrace: {0}", objEx.StackTrace);
}
catch (Exception objEx)
{
Console.WriteLine("Error: {0}", objEx);
}
}
}
Trong đoạn mã trên, sự tràn số xảy ra vì kết quả của phép nhân hai số byte vượt quá phạm vi của kiểu biến đích, result
. Ngoại lệ tràn số được ném và nó được bắt bởi khối catch
. Khối này sử dụng các thuộc tính khác nhau của lớp System.Exception
để hiển thị nguồn và đích của lỗi.
Hình bên dưới trình bày việc sử dụng một số thuộc tính công cộng của lớp System.Exception
.
Bảng bên dưới liệt kê các phương thức công cộng của lớp System.Exception
cùng với mô tả tương ứng của chúng.
Phương thức | Mô Tả |
Equals | Xác định xem các đối tượng có bằng nhau không |
GetBaseException | Trả về một loại Exception khi được ghi đè trong một lớp dẫn xuất |
GetHashCode | Trả về một hàm băm cho một loại cụ thể |
GetObjectData | Lưu thông tin về việc tuần tự hoặc giải tuần tự một đối tượng cụ thể với thông tin về ngoại lệ khi được ghi đè trong lớp kế thừa |
GetType | Truy xuất loại của thể hiện hiện tại |
ToString | Trả về biểu diễn dạng chuỗi của ngoại lệ được ném |
System.Exception
Đoạn mã bên dưới trình bày cách sử dụng một số phương thức công khai của hệ thống lớp ngoại lệ
using System;
class ExceptionMethods
{
static void Main(string[] args)
{
byte numOne = 200;
byte numTwo = 5;
byte result = 0;
try
{
result = checked((byte)(numOne * numTwo));
Console.WriteLine("Result = {0}", result);
}
catch (Exception objEx)
{
Console.WriteLine("Error Description: {0}", objEx.ToString());
Console.WriteLine("Exception: {0}", objEx.GetType());
}
}
}
Đoạn mã trên trình bày một trường hợp tràn số xảy ra do kết quả của phép nhân hai số byte vượt quá phạm vi của kiểu dữ liệu của biến đích, result
. Ngoại lệ tràn số được ném và nó được bắt bởi khối catch
. Khối này sử dụng phương thức ToString()
để lấy biểu diễn dạng chuỗi của ngoại lệ. Phương thức GetType()
của lớp System.Exception
truy xuất loại của ngoại lệ, sau đó được hiển thị bằng cách sử dụng phương thức WriteLine()
.
Hình bên dưới trình bày một số phương thức công cộng của lớp System.Exception
.
Các lớp ngoại lệ thường được sử dụng
Lớp System.Exception
có một số lớp ngoại lệ được kế thừa để xử lý các loại ngoại lệ khác nhau. Bảng bên dưới liệt kê một số lớp ngoại lệ thông dụng.
Lớp Ngoại lệ | Mô Tả |
System.ArithmeticException | Ngoại lệ này được ném cho các vấn đề phát sinh do các phép toán hoặc chuyển đổi dữ liệu số học. |
System.ArgumentException | Ngoại lệ này được ném khi một trong các đối số không phù hợp với các thông số của phương thức được gọi. |
System.DivideByZeroException | Ngoại lệ này được ném khi có một nỗ lực chia một giá trị số cho số không. |
System.IndexOutOfRangeException | Ngoại lệ này được ném khi có một nỗ lực lưu trữ dữ liệu vào một mảng sử dụng một chỉ mục nhỏ hơn không hoặc vượt ra ngoài giới hạn trên của mảng. |
Lớp InvalidCastException
Lớp InvalidCastException
được ném khi việc chuyển đổi tường minh từ một kiểu cơ bản sang kiểu khác thất bại. Đoạn mã bên dưới mô tả ngoại lệ InvalidCastException
.
using System;
class InvalidCastError
{
static void Main(string[] args)
{
try
{
float numOne = 3.14F;
object obj = numOne;
int result = (int)obj;
Console.WriteLine("Value of numOne = {0}", result);
}
catch (InvalidCastException objEx)
{
Console.WriteLine("Message: {0}", objEx.Message);
Console.WriteLine("Error: {0}", objEx);
}
catch (Exception objEx)
{
Console.WriteLine("Error: {0}", objEx);
}
}
}
Trong đoạn mã bên trên, một biến numOne
được định nghĩa là float
. Khi biến này được đóng gói (boxed), nó được chuyển đổi sang kiểu object
. Tuy nhiên, khi nó được mở gói (unboxed), nó gây ra một InvalidCastException
và hiển thị một tin nhắn tương tự. Điều này xảy ra vì một giá trị kiểu float
được mở gói sang kiểu int
, điều này không được phép trong C#. Hình bên dưới hiển thị ngoại lệ được tạo ra khi chương trình được thực thi.
Lớp NullReferenceException
Lớp NullReferenceException
được ném khi có một nỗ lực thao tác trên một tham chiếu null. Đoạn mã bên dưới mô tả ngoại lệ NullReferenceException
.
using System;
class Employee {
private string _empName;
private int _empID;
public Employee() {
_empName = "David";
_empID = 101;
}
static void Main(string[] args) {
Employee objEmployee = new Employee();
Employee objEmp = objEmployee;
objEmployee = null;
try {
Console.WriteLine("Employee Name: " + objEmp._empName);
Console.WriteLine("Employee ID: " + objEmp._empID);
}
catch (NullReferenceException objNull) {
Console.WriteLine("Error: " + objNull.Message);
}
catch (Exception objEx) {
Console.WriteLine("Error: " + objEx.Message);
}
}
}
Được định nghĩa lớp Employee
chứa thông tin của một nhân viên. Hai thể hiện của lớp Employee
được tạo ra với thể hiện thứ hai tham chiếu đến thể hiện đầu tiên. Thể hiện đầu tiên được gỡ bỏ tham chiếu bằng cách sử dụng null. Nếu thể hiện đầu tiên được sử dụng để in ra các giá trị của lớp Employee
, một ngoại lệ NullReferenceException
sẽ được ném, và ngoại lệ này được bắt bởi khối catch
. Hình bên dưới hiển thị ngoại lệ được tạo ra khi chương trình được thực thi.
Phương thức throw
Lệnh throw
trong C# cho phép một nhà phát triển ném ngoại lệ theo cách lập trình bằng từ khóa throw
. Lệnh throw
nhận một phiên bản của lớp ngoại lệ cụ thể như một tham số. Tuy nhiên, nếu phiên bản không tham chiếu đến lớp ngoại lệ hợp lệ, trình biên dịch C# sẽ sinh ra một lỗi.
Khi ném một ngoại lệ bằng từ khóa throw
, ngoại lệ được xử lý bởi khối catch
.
Đoạn mã bên dưới mô tả việc sử dụng lệnh throw
.
using System;
class Employee {
static void ThrowException(string name) {
if (name == null) {
throw new ArgumentNullException();
}
}
static void Main(string[] args) {
Console.WriteLine("Throw Example");
try {
string empName = null;
ThrowException(empName);
}
catch (ArgumentNullException objNull) {
Console.WriteLine("Exception caught: " + objNull);
}
}
}
Trong đoạn mã bên trên, lớp Employee
khai báo một phương thức tĩnh có tên là ThrowException
, nhận một tham số string
có tên là name. Nếu giá trị của name
là null
, trình biên dịch C# sẽ ném ra ngoại lệ, được bắt bởi một phiên bản của lớp ArgumentException
. Trong khối try
, giá trị của name
là null
và quyền điều khiển của chương trình trở lại phương thức ThrowException
, nơi ngoại lệ được ném do tham chiếu đến một giá trị null
. Khối catch
chứa xử lý lỗi, được thực thi khi ngoại lệ được ném.
Kết quả:
Ví dụ về Throw
Exception caught: System.ArgumentNullException: Value cannot be null.
at Exception_Handling.Employee.ThrowException(String name) in D:\ExceptionHandling\Employee.cs:line 13
at Exception_Handling.Employee.Main(String[] args) in D:\ExceptionHandling\Employee.cs:line 24
Biểu thức throw
Từ C# 7.0 trở đi, C# có một cải tiến mới liên quan đến ngoại lệ, đó là biểu thức throw. Trong các phiên bản trước đó, throw là một lệnh và do đó, các cấu trúc mã như biểu thức điều kiện, biểu thức null coalescing và một số biểu thức lambda không được phép ném ngoại lệ. Tuy nhiên, hạn chế này không còn áp dụng từ C# 7.0. Nhà phát triển có thể ném ngoại lệ trong tất cả các cấu trúc mã như vậy.
Ví dụ, đoạn mã bên dưới ném một ngoại lệ trong một biểu thức null coalescing.
using System;
class Program
{
static void Evaluate(string arg)
{
var val = arg ?? throw new ArgumentException("Invalid argument");
Console.WriteLine("Reached this point");
}
static void Main()
{
Evaluate("numbers");
Evaluate(null);
}
}
Phương thức finally
Nói chung, nếu bất kỳ câu lệnh nào trong khối try
ném ra một ngoại lệ, khối catch
sẽ được thực thi và các câu lệnh còn lại trong khối try
sẽ bị bỏ qua. Đôi khi, việc thực hiện một số câu lệnh mặc dù có ngoại lệ xảy ra hay không là bắt buộc. Trong những trường hợp như vậy, một khối finally
được sử dụng. Các hành động mà có thể được đặt trong khối finally
là: đóng một tập tin, gán đối tượng vào null
, đóng kết nối cơ sở dữ liệu, và vân vân.
Khối finally
là một khối tùy chọn và nó xuất hiện sau khối catch
. Nó bao quanh các câu lệnh được thực thi mặc kể có ngoại lệ xảy ra hay không.
Đoạn mã bên dưới trình bày cách sử dụng cấu trúc try-catch-finally
.
using System;
class DivisionError
{
static void Main(string[] args)
{
int numOne = 133;
int numTwo = 0;
int result;
try
{
result = numOne / numTwo;
}
catch (DivideByZeroException objDivide)
{
Console.WriteLine("Exception caught: " + objDivide);
}
finally
{
Console.WriteLine("This finally block will always be executed");
}
}
}
Trong đoạn mã trên, phương thức Main()
của lớp DivisionError
khai báo và khởi tạo hai biến. Một nỗ lực được thực hiện để chia một trong những biến cho số không và một ngoại lệ được ném ra. Ngoại lệ này được bắt bằng cách sử dụng cấu trúc try-catch-finally
. Khối finally
được thực thi vào cuối ngay cả khi một ngoại lệ được ném ra bởi khối try
.
Kết quả:
Exception caught: System.DivideByZeroException: Attempted to divide by zero. at DivisionError.Main(String[] args)
This finally block will always be executed
Khối try lồng nhau và nhiều khối catch
Mã xử lý ngoại lệ có thể được lồng trong một chương trình. Trong xử lý ngoại lệ lồng nhau, một khối try
có thể bao gồm một khối try-catch
khác. Ngoài ra, một khối try
duy nhất có thể có nhiều khối catch
được sắp xếp để bắt và xử lý các loại ngoại lệ khác nhau được ném ra trong khối try
đó.
Các khối try lồng nhau
Khối try
lồng nhau bao gồm nhiều cấu trúc try-catch
. Một khối try
lồng nhau bắt đầu bằng một khối try
, được gọi là khối try
bên ngoài. Khối try
bên ngoài này chứa nhiều khối try
trong đó, được gọi là các khối try
bên trong. Nếu một ngoại lệ được ném ra bởi một khối try
lồng nhau, quyền điều khiển được chuyển đến khối catch
lồng nhau tương ứng.
Xem xét một khối try
bên ngoài chứa một khối try-catch
trong khối finally
. Nếu khối try bên trong ném ra một ngoại lệ, quyền điều khiển được chuyển đến khối catch
bên trong. Tuy nhiên, nếu khối catch
bên trong không chứa bộ xử lý lỗi thích hợp, quyền điều khiển sẽ được chuyển đến khối catch
bên ngoài.
Đoạn mã bên dưới mô tả việc sử dụng các khối try
lồng nhau.
using System;
class Program
{
static void Main(string[] args)
{
string[] names = { "John", "James" };
int numOne = 0;
int result;
try
{
Console.WriteLine("This is the outer try block");
try
{
result = 133 / numOne;
}
catch (ArithmeticException objMaths)
{
Console.WriteLine("Divide by 0: " + objMaths);
}
names[2] = "Smith";
}
catch (IndexOutOfRangeException objIndex)
{
Console.WriteLine("Wrong number of arguments supplied: " + objIndex);
}
}
}
Trong đoạn mã trên, biến mảng được gọi là “names
” kiểu string
được khởi tạo với hai giá trị. Khối try bên ngoài bao gồm một cấu trúc try-catch
khác. Trong khối try
bên trong, có một phép chia giữa hai số.
Khi cố gắng chia một số cho số không, khối try
bên trong gây ra một ngoại lệ, được xử lý bởi khối catch
bên trong. Ngoài ra, trong khối try
bên ngoài, có một câu lệnh tham chiếu đến một phần tử mảng thứ ba trong khi mảng chỉ có thể lưu trữ hai giá trị. Vì vậy, khối try
bên ngoài cũng gây ra một ngoại lệ, được xử lý bởi khối catch
bên ngoài.
Kết quả:
This is the outer try block
Divide by 0 System.DivideByZeroException: Attempted to divide by zero
at Product.Main (String[] args) in c:\ConsoleApplication1\Program.cs:line 52
Wrong number of arguments supplited System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Product.Mian(String[] args) in c:\ConsoleApplication1\Program.cs:line 58
Nhiều khối catch
Một khối try
có thể ném ra nhiều loại ngoại lệ, những ngoại lệ này phải được xử lý bởi khối catch
. Trong C#, ta có thể định nghĩa nhiều khối catch
để xử lý các loại ngoại lệ khác nhau có thể được ném ra bởi khối try
. Tùy thuộc vào loại ngoại lệ được ném ra bởi khối try
, khối catch
thích hợp (nếu có) sẽ được thực thi. Tuy nhiên, nếu trình biên dịch không tìm thấy khối catch
phù hợp, thì khối catch
tổng quát sẽ được thực thi.
Sau khi khối catch
được thực thi, quyền điều khiển của chương trình được chuyển đến khối finally
(nếu có) và sau đó quyền điều khiển kết thúc cấu trúc try-catch-finally
. Đoạn mã bên dưới mô tả việc sử dụng nhiều khối catch
.
using System;
class Program
{
static void Main(string[] args)
{
string[] names = { "John", "James" };
int numOne = 10;
int result = 0;
int index = 0;
try
{
index = 3;
names[index] = "Smith";
result = 130 / numOne;
}
catch (DivideByZeroException objDivide)
{
Console.WriteLine("Divide by 0: " + objDivide);
}
catch (IndexOutOfRangeException objIndex)
{
Console.WriteLine("Wrong number of arguments supplied: " + objIndex);
}
catch (Exception objException)
{
Console.WriteLine("Error: " + objException);
}
Console.WriteLine(result);
}
}
Trong đoạn mã trên, mảng names
được khởi tạo với hai giá trị phần tử và hai biến số nguyên được khai báo và khởi tạo. Do có tham chiếu đến phần tử thứ ba trong mảng, một ngoại lệ loại IndexOutOfRangeException
được ném ra và khối catch
thứ hai được thực thi. Thay vì cứ cố định giá trị chỉ mục, có thể xảy ra là một phép toán toán học nào đó dẫn đến giá trị chỉ mục vượt quá 2. Khi khối try
gặp phải một ngoại lệ trong câu lệnh đầu tiên, câu lệnh tiếp theo trong khối try
sẽ không được thực thi và quyền điều khiển kết thúc cấu trúc try-catch
. Do đó, trình biên dịch C# in ra giá trị khởi tạo của biến result
chứ không phải là giá trị được thu được từ việc chia hai số. Tuy nhiên, nếu một ngoại lệ xảy ra mà không thể được bắt bởi bất kỳ khối catch
nào trong hai khối catch
, thì khối catch
cuối cùng với lớp Exception
chung sẽ được thực thi.
Trợ lý Ngoại lệ (Exception Assistant)
Trợ lý ngoại lệ là một tính năng mới được thêm vào để gỡ lỗi các ứng dụng C#. Trợ lý này xuất hiện mỗi khi một ngoại lệ chạy xảy ra. Nó hiển thị loại ngoại lệ đã xảy ra, các gợi ý sửa lỗi và hành động sửa chữa cần thực hiện, cũng có thể được sử dụng để xem chi tiết của một đối tượng ngoại lệ. Trợ lý ngoại lệ cung cấp thông tin chi tiết hơn về một ngoại lệ so với hộp thoại Exception. Trợ lý này giúp dễ dàng xác định nguyên nhân của ngoại lệ và giải quyết vấn đề.