Tài liệu Hướng Đối Tượng Trong C# part 5 docx

11 509 0
Tài liệu Hướng Đối Tượng Trong C# part 5 docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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ệ: public 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. Gọ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: Car 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: public 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. Thê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; } 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. 2. 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. 3. 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() { // 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); [...]... 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 . độ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. 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

Ngày đăng: 24/12/2013, 11:15

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan