1. Trang chủ
  2. » Công Nghệ Thông Tin

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

11 511 0
Tài liệu đã được kiểm tra trùng lặp

Đ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

Thông tin cơ bản

Tiêu đề Constructor trong C#
Định dạng
Số trang 11
Dung lượng 154,61 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 2

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

Trang 3

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:

Trang 4

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:

Trang 5

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

Trang 6

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;

}

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 8

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

Trang 9

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()

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

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

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm