Trong bài này chúng ta sẽ tìm hiểu:
- Sử dụng trình thu gom rác (garbage collection)
- Sử dụng phương thức huỷ (destructor)
- Giải phóng tài nguyên với lệnh try/finally
- Giải phóng tài nguyên với lệnh using
Chu kỳ sống của một đối tượng
Khi chúng ta tạo một đối tượng bằng toán tử new, quá trình gồm hai giai đoạn sẽ diễn ra:
– Một khối bộ nhớ từ heap được cấp phát bởi toán tử new. Chúng ta không thể kiểm soát được giai đoạn này.
– Toán tử new sẽ chuyển khối bộ nhớ đến đối tượng và khởi tạo đối tượng. Chúng ta có thể kiểm soát được giai đoạn này thông qua các phương thức khởi tạo (constructor).
Khi khởi tạo xong, chúng ta có thể truy cập đến đối tượng (với toán tử dấu chấm). Quá trình huỷ đối tượng cũng gồm hai giai đoạn:
– runtime thực hiện các thao tác huỷ đối tượng. Chúng ta có thể kiểm soát giai đoạn này nhờ phương thức huỷ (destructor).
– Các khối bộ nhớ được giải phóng và trả lại heap. Chúng ta không thể kiểm soát giai đoạn này.
Quá trình huỷ đối tượng và trả lại bộ nhớ đến heap gọi là quá trình thu gom rác (garbage collection).
Phương thức huỷ (destructor)
Phương thức huỷ là phương thức đặc biệt, giống phương thức khởi tạo, được runtime gọi sau khi tham chiếu cuối cùng đến đối tượng biến mất. Cú pháp để viết phương thức huỷ gồm dấu ~ và tên của lớp. Ví dụ lớp Tally đếm số thể hiện tồn tại bằng cách tăng giá trị một biến tĩnh trong phương thức khởi tạo và giảm giá trị của biến này trong phương thức huỷ như sau:
class Tally { public Tally() // constructor { this.instanceCount++; } ~Tally() // destructor { this.instanceCount--; } public static int InstanceCount() { return this.instanceCount; } ... private static int instanceCount = 0; }
Một số đặc điểm của phương thức huỷ:
– Chỉ áp dụng cho kiểu tham chiếu, không áp dụng cho kiểu tham trị ví dụ kiểu struct.
– Không dùng các từ khoá chỉ phạm vi truy cập như public, private, v.v. với phương thức huỷ.
– Phương thức huỷ không chứa tham số.
Trong thực tế khi lập trình C#, chúng ta không cần quan tâm đến việc huỷ đối tượng vì trình thu dọn rác (garbage collector) sẽ làm điều đó một cách tự động cho chúng ta. Có thể gọi trực tiếp trình thu dọn rác bằng lệnh System.GC.Collect, tuy nhiên cách này không được khuyên dùng.
Quản lý tài nguyên
Có nhiều cách thức để quản lý tài nguyên một cách hiệu quả.
Sử dụng các phương thức xử lý tài nguyên (disposal methods)
Chúng ta có thể sử dụng các phương thức thu dọn tài nguyên (tự viết hay có sẵn trong các lớp). Một ví dụ dùng phương thức xử lý là đối tượng TextReader có chứa phương thức Close() thực thi để giải phóng các tài nguyên sau khi các thao tác đọc hay ghi các kí tự từ luồng dữ liệu hoàn thành. Ví dụ đọc ký tự:
TextReader reader = new StreamReader(filename); string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } reader.Close();
Sử dụng lệnh try/finally
Sử dụng phương thức xử lý là hữu ích trong một số trường hợp, tuy nhiên, trong một vài trường hợp sẽ không hiệu quả. Ví dụ trong quá trình đọc ký tự từ luồng dữ liệu của đối tượng TextReader, một vài lỗi (hay ngoại lệ) xuất hiện và chương trình sẽ ngưng trước khi phương thức Close được gọi. Điều này sẽ dẫn đến lãng phí tài nguyên. Cách giải quyết trong trường hợp này là dùng lệnh try/finally như ví dụ sau:
TextReader reader = new StreamReader(filename); try { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } finally { reader.Close(); }
Lệnh try/finally cũng tồn tại một số trở ngại như khi chúng ta cần kiểm soát nhiều tài nguyên, thay đổi đoạn mã, hay cần tham chiếu đến các tài nguyên sau khối lệnh finally. Các vấn đề này sẽ được khắc phục bởi lệnh using.
Sử dụng lệnh using
Dùng lệnh using là một cách hiệu quả để quản lý tài nguyên. Chúng ta có thể tạo một đối tượng và đối tượng này sẽ bị huỷ khi khối lệnh using hoàn thành. Cú pháp tổn quát cho lệnh using:
using ( kiểu dữ liệu tên biến = giá trị khởi tạo ) { Khối lệnh }
Sử dụng lệnh using cho đối TextReaer để đảm bảo phương thức Close luôn được thự thi như sau:
using (TextReader reader = new StreamReader(filename)) { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } }
Gọi phương thức Dispose của giao diện IDisposable
Chúng ta có thể gọi trực tiếp phương thức Dispose của giao diện IDisposable từ phương thức destructor để chắc chắn rằng nó hoạt động. Ví dụ:
class Example : IDisposable { private Resource scarce; private bool disposed = false; ... ~Example() { Dispose(); } public virtual void Dispose() { if (!this.disposed) { try { // giải phóng tài nguyên ở đây } finally { this.disposed = true; GC.SuppressFinalize(this); } } } public void SomeBehavior() // phương thức { checkIfDisposed(); ... } ... private void checkIfDisposed() { if (this.disposed) { throw new ObjectDisposedException("Example: object has been disposed of"); } } }