Nếu bạn không định nghĩa một constructor nào trong lớp của bạn thì trình biên dịch tạo một constructor mặc định để khởi tạo một số giá trị mặc định như: gán chuỗi rỗng cho chuỗi, gán 0 c
Trang 1
Construction and Disposal
Constructor :
Cú pháp khai báo một Constructor là : chúng ta khai báo một phương thức mà cùng tên với lớp và không có kiểu trả về
public class MyClass
{
public MyClass()
{
}
// rest of class definition
Như trong c++ và java, bạn có thể không cần định nghĩa constructor trong lớp của bạn nếu không cần thiết Nếu bạn không định nghĩa một constructor nào trong lớp của bạn thì trình biên dịch tạo một constructor mặc định để khởi tạo một số giá trị mặc định như: gán chuỗi rỗng cho chuỗi, gán 0 cho kiểu số, false cho kiểu bool
Các contructor theo cùng luật overloading như các phương thức khác Bạn cũng có thể tạo nhiều constructor cùng tên và khác tham số giống như các phương thức nạp chồng :
public MyClass() // zero-parameter constructor
{
// construction code
}
public MyClass(int number) // another overload
{
// construction code
}
Chú ý : khi bạn đã định nghĩa một constructor trong lớp của bạn thì trình biên dịch sẽ không tự động tạo ra constructor mặc định
Chúng ta có thể định nghĩa các constructor với các bổ từ private và protected để chúng không thể được nhìn thấy trong các lớp không có quan hệ:
Trang 2public class MyNumber
{
private int number;
private MyNumber(int number) // another overload
{
this.number = number;
}
}
Chú ý: Nếu bạn định nghĩa một hay nhiều constructor private thì những lớp thừa kế lớp của bạn sẽ không thể khởi tạo được Do đó chúng ta phải cân nhắc kỹ lưỡng khi định nghĩa bổ từ của một constructor
Constructor tĩnh(static):
Chúng ta định nghĩa một constructor tĩnh để khởi tạo giá trị cho các biến tĩnh
class MyClass
{
static MyClass()
{
// initialization code
}
// rest of class definition
}
Một nguyên nhân để viết một constructor tĩnh là: Nếu lớp của bạn có một số trường hay thuộc tính cần được khởi tạo từ bên ngoài trước khi lớp được sử dụng lần đầu tiên
Chúng ta không thể biết chắc được khi nào một constructor tĩnh sẽ được thực hiện Và chúng ta cũng không biết trước các constructor tĩnh của các lớp khác nhau sẽ thực hiện những gì Nhưng chúng ta có thể chắc chắn rằng constructor tĩnh chỉ chạy một lần và nó
sẽ được gọi trước khi đoạn mã của bạn tham khảo đến lớp đó Trong C#, Constructor tĩnh thường được thực hiện ngay trước lần gọi đầu tiên của một thành viên trong lớp đó Constructor tĩnh không có bổ từ truy cập, không có bất kỳ một tham số nào và chỉ có duy nhất một constructor tĩnh trong một lớp
Chúng ta có thể định nghĩa một constructor tĩnh và một constructor thực thể không có tham số trong cùng một lớp Nó không gây ra bất kỳ một sự xung đột nào bởi vì
constructor tĩnh được thực hiện khi lớp được khởi tạo còn constructor thực thể được thực hiện khi một thực thể được tạo ra
Trang 3Gọi các constructor từ những constructor khác:
Xét ví dụ như sau:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model)
{
this.description = description;
this.nWheels = 4;
}
// etc
Ta thấy cả hai constructor đều khởi tạo cùng các trường, và nó sẽ ngắn gọn hơn nếu ta chỉ cần viết đoạn mã ở một constructor C# cho phép ta làm đều đó như sau:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model) : this(model, 4)
{
}
// etc
Khi ta khởi tạo một biến như sau:
Trang 4Car myCar = new Car("Proton Persona");
Thì constructor 2 tham số sẽ được thực thi trước bất kỳ đoạn mã nào trong constructor 1 biến
Constructor của các lớp thừa hưởng:
Khi chúng ta tạo ra một thể hiện của một lớp thừa hưởng thì không phải chỉ những
constructor của lớp thừa hưởng đó được thực hiện mà cả những constructor của lớp cơ sở cũng được gọi Và các constructor của lớp cơ sở sẽ được thực hiện trước khi các
constructor của lớp thừa hưởng
Chúng ta xét ví dụ sau:
abstract class GenericCustomer
{
private string name;
// lots of other methods etc
}
class Nevermore60Customer : GenericCustomer
{
private uint highCostMinutesUsed;
// other methods etc
}
Đều chúng ta cần ở ví dụ trên là khi một thể hiện của lớp Nevermore60Customer được tạo ra thì thuộc tính name phải được khởi tạo giá trị null và thuộc tính
highCostMinutesUsed được khởi tạo là 0
GenericCustomer arabel = new Nevermore60Customer();
Đối với thuộc tính highCostMinutesUsed thì không có vấn đề gì, nó sẽ được constructor
mặc định khởi tạo giá trị 0 Còn thuộc tính name thì sao? Lớp con không thể truy cập vào
thuộc tính này bởi vì nó được khai báo private Nhưng trên thực tế thì thuộc tính này luôn
được khởi tạo giá trị null vì khi này constructor của lớp cơ sở cũng được gọi và nó thực
hiện trước khởi tạo giá trị null cho thuộc tính name
Thêm một constructor không tham số trong một quan hệ thừa kế:
Chúng ta sẽ xem xét chuyện gì sẽ xảy ra nếu ta thay thế constructor mặc định bằng một
constructor khác không có tham số Xét ví dụ ở trên, bây giờ ta muốn khởi tạo name bằng
giá trị <noname> ta làm như sau:
Trang 5public abstract class GenericCustomer
{
private string name;
public GenericCustomer()
: base() // chúng ta có thể xoá bỏ dòng này mà không có ảnh hưởng gì khi biên dịch {
name = "<no name>";
}
Điểm chú ý ở đây là chúng ta thêm lời gọi tường minh đến constructor của lớp cơ sở
trước khi constructor của lớp GenericCustomer được thực hiện và chúng ta sử dụng từ khoá base để gọi các constructor ở lớp cơ sở
Trên thực tế chúng ta có thể viết như sau:
public GenericCustomer()
{
name = "<no name>";
}
Nếu trình biên dịch không thấy bất kỳ một sự tham khảo nào đến các constructor khác thì
nó sẽ nghĩ là chúng ta muốn gọi constructor mặc định của lớp cơ sở
Chú ý: Từ khoá base và this chỉ cho phép dùng để gọi một constructor khác, nếu không
nó sẽ báo lỗi
Nếu chúng ta khai báo như sau:
private GenericCustomer()
{
name = "<no name>";
}
Thì khi khởi tạo một thể hiện của lớp thừa hưởng neverMore60Customer trình biên dịch
sẽ báo lỗi :
'Wrox.ProCSharp.OOCSharp.GenericCustomer.GenericCustomer()' is inaccessible due
to its protection level
Bởi vì bạn đã khai báo private nên lớp con sẽ không nhìn thấy constructor này nên sẽ báo
lỗi
Trang 6Thêm các constructor có tham số trong một quan hệ thừa kế
Cũng ví dụ như ở trên nhưng bây giờ chúng ta yêu cầu thuộc tính name phải được khởi
tạo một giá trị xát định Tức là ta phải tạo một constructor một tham số ở lớp
GenericCustomer:
abstract class GenericCustomer
{
private string name;
public GenericCustomer(string name)
{
this.name = name;
}
Khi đó nếu ta không sửa constructor ở lớp thừa hưởng thì trình biên dịch sẽ báo lỗi vì nó không tìm thấy một constructor không tham số nào trong lớp cơ sở Vì thế ta phải sửa như sau:
class Nevermore60Customer : GenericCustomer
{
private uint highCostMinutesUsed;
public Nevermore60Customer(string name)
: base(name)
{
}
Xét constructor một tham số ta thấy mặc dù không có quyền truy cập đến thuộc tính
name của lớp cơ sở nhưng nó vẫn khởi tạo được thuộc tính name bởi vì constructor của lớp cơ sở đã được gọi thông qua từ khóa base
Bây giờ ta xét một trường hợp phức tạp hơn:
The Nevermore60Customer definition will look like this at this stage:
class Nevermore60Customer : GenericCustomer
{
public Nevermore60Customer(string name, string referrerName)
: base(name)
{
this.referrerName = referrerName;
}
Trang 7
private string referrerName;
private uint highCostMinutesUsed;
public Nevermore60Customer(string name)
: this(name, "<None>")
{
}
Bây giờ ta khởi tạo một thể hiện như sau:
GenericCustomer arabel = new Nevermore60Customer("Arabel Jones");
Ta thấy trình biên dịch sẽ cần một constructor một tham số để lấy một chuỗi và nó sẽ nhận ra constructor:
public Nevermore60Customer(string name)
: this(name, "<None>")
{
}
Khi ta khởi tạo thể hiện arabel thì constructor của nó sẽ được gọi Ngay lập tức nó
chuyến quyền điều khiển cho constructor 2 tham số của lớp Nervemore60customer sẽ gán hai giá trị Arabel Jone và <none> Sau đó chuyển quyền điều khiển cho constructor 1 tham số của lớp GenericCustomer với chuỗi "Arabel Jone" Và tiếp tục chuyển quyền điều khiển cho constructor system.object thực hiện gán chuỗi "Arable Jone" cho thuộc tính name Sau đó constructor 2 tham số của lớp Nervemore60customer lấy lại quyền điều khiển và khởi tạo referrerName bằng <none> Và cuối cùng constructor 1 tham số của lớp Nervemore60customer lấy lại quyền điều khiển và nó không làm gì hết
Như vậy ta đã hiểu rõ về constructor và cách thức mà chúng hoạt động để biết cách sử dụng đúng trong thực tiễn
Destructors và phương thức Dispose()
C# cũng hỗ trợ Destructor, nhưng chúng không được dùng thường xuyên như trong C++
và cách chúng hoạt động rất khác nhau Bởi vì các đối tượng trong NET và C# thì bị xoá bởi bộ thu gom rác (garbage collection) Trong C#, mẫu destruction làm việc theo hai giai đoạn:
1.Lớp sẽ thực thi giao diện System.IDisposable, tức là thực thi phương thức
IDisposable.Dispose() Phương thức này được gọi tường minh khi trong đoạn mã khi một
đối tượng không cần nữa
Trang 82 Một Destructor có thể được định nghĩa và nó được tự động gọi khi đối tượng bị thu gom rác Destructor chỉ đóng vai trò như một máy rà soát lại trong một số trường hợp xấu
client không gọi phương thức Dispose()
Nhìn chung các đối tượng của một vài lớp có thể chứa sự tham khảo đến các đối tượng quản lý khác.Các đối tượng này rất lớn và nên được xoá càng sớm càng tốt, sau đó lớp đó
nên thực thi phương thức Dispose() Nếu một lớp nắm những tài nguyên không quản lý thì nó nên thực thi cả hai phương thức Dispose() và một Destructor
Cú pháp để định nghĩa Destructor và Dispose():
class MyClass : IDisposable
{
public void Dispose()
{
// implementation
}
~MyClass() // destructor Only implement if MyClass directly holds
// unmanaged resources
{
// implementation
}
// etc
Phương thức Dispose() giống như một phương thức bình thường, nó không có kiểu trả về
và không có tham số truyền
Cú pháp của một Destructor giống như một phương thức nhưng có cùng tên với lớp, có tiền tố là một dấu sóng(~), không có kiểu trả về, không có tham số truyền và không có bổ
từ
Thực thi phương thức Dispose() và một Destructor:
Destrutor được gọi khi một đối tượng bị huỹ Có một vài điểm ta phải nhớ như sau:
1 Chúng ta không thể biết trước khi nào một thể hiện bị huỹ, tức là ta không biết trước khi nào một Destructor được gọi
2 Bạn có thể tác động đến bộ thu gom rác để chạy tại một thời điểm trong đoạn mã của
bạn bằng cách gọi phương thức System.GC.Collect() System.GC là một lớp cơ sở NET
mô tả bộ thu gom rác và phương thức Collect() dùng để gọi bộ thu gom rác
Trang 93 Có một lời khuyên là chúng ta không nên thực thi một Destructor nếu như lớp của bạn không thực sự cần đến nó Nếu một đối tượng thực thi một Destructor, thì nó đặt một đặc tính quan trọng vào quá trình thu gom rác của đối tượng đó Nó sẽ trì hoãn việc di chuyển cuối cùng của đối tượng từ bộ nhớ Những đối tượng không có một Destructor thì bị xoá khỏi bộ nhớ trong một lần hoạt động của bộ thu gom rác Còn đối tượng có Destructor thì
nó sẽ qua hai bước : lần đầu nó gọi Destructor mà không xoá đối tượng, lần thứ hai mới thực sự xoá đối tượng
Chú ý : Không có bất kỳ một tham số nào trong một Destructor, không kiểu trả về và không có bổ từ Không cần thiết phải gọi tường minh một Destructor của lớp cơ sở mà trình biên dịch sẽ tự động sắp xếp tất cả Destructor được định nghĩa trong các lớp thừa kế đều được gọi
Close() vs Dispose()
Sự khác nhau giữa hai phương thức Close() và Dispose() là rất lớn Phương thức Close()
sẽ đóng tài nguyên và có thể được gọi lại sau này Còn khi gọi Dispose() tức là client đã
kết thúc Bạn có thể thực thi một hoặc cả hai phương thức trên, tuy nhiên để tránh một số
rắc rối bạn nên căn nhắc trước khi thực thi chúng và bạn nên sử dụng Dispose() nếu bạn
muốn lấy một số lợi ích từ cấu trúc của giao diện IDisposable
Sử dụng giao diện IDisposable:
C# đưa ra cú pháp để chắc chắn rằng phương thức Dispose() phải tự động được gọi khi một đối tượng tham khảo ra ngoài phạm vi Ví dụ ta có một lớp ResourceGobbler sử
dụng một số tài nguyên bên ngoài và chúng ta cần khởi tạo một thể hiện của lớp này: {
ResourceGobbler theInstance = new ResourceGobbler();
// do your processing
theInstance.Dispose();
}
Theo đoạn mã trên thì phương thức Dispose() sẽ được gọi vào cuối khối mã khi đó thể hiện theInstance sẽ bị huỹ Và chúng ta còn một cách khác như sau:
class ResourceGobbler : IDisposable
{
// etc
public void Dispose()
Trang 10{
// etc
}
}
Ta thấy đoạn mã trên lớp ResourceGobbler thừa kế giao diện IDisposable, việc thừa kế một giao diện khác với việc thừa kế một lớp Nó sẽ bắt buột lớp ResourceGobbler phải thực thi phương thức Dispose() Bạn sẽ bị báo lỗi nếu bạn thừa kế từ giao diện
IDisposable và không thực thi phương thức Dispose() Chính vì thế trình biên dịch có thể kiểm tra xem một đối tượng được định nghĩa có phương thức Dispose() thì nó phải tự
động được gọi
Sự thực thi của các Destructor và phương thức Dispose():
Xét ví dụ một lớp chứa cả tài nguyên không quản lý và tài nguyên quản lý:
public class ResourceGobbler : IDisposable
{
private StreamReader sr;
private int connection;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (sr != null)
{
sr.Close();
sr = null;
}
}
CloseConnection();
}
~ResourceGobbler()
{
Dispose (false);
Trang 11}
void CloseConnection()
{
// code here will close connection
}
}
Lớp ResourceGobbler có một sự tham khảo đến một đối tượng StreamReader Đây là một lớp được định nghĩa trong System.IO để đọc dữ liệu như tập tin.Tuy nhiên có một sự
kết nối gọi bên trong để mô tả một số đối tượng không quản lý Tức là chúng ta cần thực
thi một Destructor Trong đoạn mã trên thì phương thức CloseConnection() dùng để đóng
tài nguyên ngoài