Chương 5: Lớp 101
Tác giả: Sưu tầm
Khái quát
Lớp là trái tim của bất kỳ một ứng dụng nào viết bằng một ngôn ngữ hướng đối tượng. Chương này được chia thành vài phần. Phần đầu nói về những phần C# được dùng thường xuyên, và phần sau nói về những thứ ít được sử dụng, nhưng điều này còn phụ thuộc vào loại chương trình mà bạn viết.
Một lớp đơn giản
Một lớp C# có thể là rất đơn giản:
class VerySimple {
int simpleValue = 0;
}
class Test {
public static void Main() {
VerySimple vs = new VerySimple();
}
}
Lớp này là vật chứa cho một giá trị nguyên đơn lẻ. Bởi vì giá trị nguyên là được khai báo mà không chỉ rõ cách có thể được truy xuất nó, nên nó là thành phần private của lớp VerySimple và không thể tham chiếu từ bên ngoài lớp. Bổ từ private có thể được chỉ rõ để phát biểu điều này rõ ràng.
Biến nguyên simpleValue là một thành phần của lớp; có thể có nhiều kiểu thành phần khác nhau. Trong hàm Main(), hệ thống tạo ra một thể hiện trong bộ nhớ heap, lưu trữ tất cả những thành phần dữ liệu của lớp, và trả ra một tham chiếu cho thể hiện. Một tham chiếu là một cách đơn giản để tham chiếu đến một thể hiện.
Không cần thiết phải chỉ rõ khi nào một thể hiện là không còn cần nữa. Trong ví dụ trước, ngay khi hàm Main() hoàn tất, thì tham chiếu tới thể hiện sẽ không còn tồn tại nữa. Nếu một tham chiếu là không được lưu giữ ở một nơi nào đó, thì sau đó thể hiện có khả năng sẽ bị đòi lại bởi trình gom rác. Trình gom rác sẽ đòi lại bộ nhớ mà đã được cấp khi cần thiết.
Đến đây thì mọi chuyện vẫn rất tốt, nhưng lớp trên không làm gì hữu ích cả bởi vì ta không thể truy xuất được biến nguyên. Sau đây là ví dụ hữu ích hơn:
using System;
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x;
public int y;
}
class Test {
public static void Main(){
Point myPoint = new Point(10, 15);
Console.WriteLine(“myPoint.x {0}”, myPoint.x);
Console.WriteLine(“myPoint.y {0}”, myPoint.y);
}
}
Trong ví dụ này, có một lớp Point, với hai biến nguyên là x và y. Những thành phần này là public, điều này có nghĩa là những giá trị của chúng có thể được truy xuất từ bất kỳ đoạn mã nào mà sử dụng lớp.
Ngoài những thành phần dữ liệu, còn có một hàm thiết lập cho lớp, mà nó là một hàm đặc biệt được gọi để giúp xây dựng một thể hiện của lớp. Hàm thiết lập cần hai tham số nguyên. Trong hàm thiết lập này, một từ khoá đặc biệt là this được sử dụng; từ khoá this có sẵn trong tất cả những hàm thành phần và luôn luôn tham chiếu đến thể hiện của lớp hiện tại.
Trong các hàm thành phần, trình biên dịch tìm kiếm các biến cục bộ và các tham số cho một tên trước khi tìm các thành phần thể hiện. Khi tham chiếu đến một biến của một thể hiện như một tham số, thì cú pháp this. phải được sử dụng.
Trong hàm thiết lập này, x tự bản thân nó tham chiếu đến tham số x, và this.x tham chiếu đến thành phần kiểu nguyên x.
Thêm vào lớp Point, còn có lớp Test mà nó chứa một hàm Main được gọi đến để bắt đầu chương trình. Hàm Main tạo ra một thể hiện của lớp Point, mà nó sẽ định vị bộ nhớ cho một đối tượng và sau đó gọi hàm thiết lập cho lớp. Hàm thiết lập sẽ thiết đặt các giá trị cho x và y.
Phần còn lại của hàm Main() in ra các giá trị của x và y.
Hàm thành phần
Hàm thiết lập trong ví dụ trên là một dạng của hàm thành phần; một phần mã được gọi trong một thể hiện của đối tượng. Hàm thiết lập chỉ có thể tự động được gọi khi một thể hiện của đối tượng là được tạo ra với từ khoá new.
Các hàm thành phần khác có thể được khai báo như sau:
using System;
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int GetX() {return(x);}
public int GetY() {return(y);}
int x;
int y;
}
class Test {
public static void Main() {
Point myPoint = new Point(10, 15);
Console.WriteLine(“myPoint.X {0}”, myPoint.GetX());
Console.WriteLine(“myPoint.Y {0}”, myPoint.GetY());
}
}
Trong ví dụ này, các trường dữ liệu là được truy xuất trực tiếp. Điều này không phải là một ý tưởng tốt, vì nó có nghĩa là người sử dụng lớp phụ thuộc vào tên của các trường, mà có thể bị thay đổi về sau.
Trong C#, thay vì viết một hàm thành phần để truy xuất một giá trị private, thì một thuộc tính nên được sử dụng, mà nó mang lại các lợi ích của một hàm thành phần trong khi giữ lại cho người sử dụng mô hình của một trường. Hãy xem Chương 18, “Thuộc tính”, để biết thêm chi tiết.
Các tham số ref và out
Việc phải gọi hai hàm thành phần để nhận các giá trị có thể không luôn luôn tiện lợi, nên sẽ tốt hơn nếu có thể có cả hai giá trị mà chỉ cần một lời gọi hàm. Tuy nhiên, chỉ có một giá trị trả về. Giải pháp là sử dụng các tham số tham chiếu (hay ref), để các giá trị của các tham số được truyền vào trong hàm thành phần có thể được sửa đổi:
// lỗi
using System;
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void GetPoint(ref int x, ref int y) {
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test {
public static void Main() {
Point myPoint = new Point(10, 15);
int x;
int y;
myPoint.GetPoint(ref x, ref y);
Console.WriteLine(“myPoint({0}, {1})”, x, y);
}
}
Trong đoạn mã này, các tham số được khai báo sử dụng từ khoá ref khi có lời gọi đến hàm. Đoạn mã này sẽ làm việc, nhưng kh biên dịch, nó phát sinh một thông báo lỗi nói rằng các giá trị chưa khởi tạo được sử dụng cho các tham số ref x và y. Điều này có nghĩa là các biến được đưa vào trong hàm trước tiên phải thiết đặt giá trị cho chúng, và trình biên dịch sẽ không cho phép các chưa khởi tạo được đưa vào.
Có hai cách để sửa lại chương trình trên. Thứ nhất là khởi tạo các biến khi chúng ta khai báo:
using System;
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void GetPoint(ref int x, ref int y) {
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test {
public static void Main() {
Point myPoint = new Point(10, 15);
int x = 0;
int y = 0;
myPoint.GetPoint(ref x, ref y);
Console.WriteLine(“myPoint({0}, {1})”, x, y);
}
}
Bây giờ biên dịch mã này, nhưng biến được khởi tạo là zero chỉ để được ghi đè lên trong lời gọi đến hàm GetPoint(). Đối với C#, một lựa chọn khác là thay đổi định nghĩa của hàm GetPoint() để sử dụng tham số out thay vì tham số ref:
using System;
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void GetPoint(out int x, out int y) {
x = this.x;
y = this.y;
}
int x;
int y;
}
class Test {
public static void Main() {
Point myPoint = new Point(10, 15);
int x;
int y;
myPoint.GetPoint(out x, out y);
Console.WriteLine(“myPoint({0}, {1})”, x, y);
}
}
Các tham số out cũng như các tham số ref chỉ có điều một biến chưa khởi tạo có thể được chuyển đến chúng, và lời gọi được thực hiện với out thay vì với ref.
Chồng hàm
Đôi khi thật là có ích nếu có hai hàm làm cùng một việc nhưng cần những tham số khác nhau. Điều này đặc biệt phổ biến với các hàm thiết lập, khi có thể có vài cách để tạo ra một thể hiện mới.
class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(Point p) {
this.x = p.x;
this.y = p.y;
}
int x;
int y;
}
class Test {
public static void Main() {
Point myPoint = new Point(10, 15);
Point mySecondPoint = new Point(myPoint);
}
}
Lớp trên có hai hàm thiết lập; một có thể được gọi với các giá trị x và y, và một có thể được gọi với một điểm khác. Hàm Main() sử dụng cả hai hàm thiết lập; một để tạo một thể hiện từ hai giá trị x và y, và một để tạo một thể hiện từ một thể hiện đã tồn tại.
Khi một hàm bị chồng được gọi tới, trình biên dịch chọn hàm thích hợp bằng cách so sánh những tham số trong lời gọi với các tham số định nghĩa trong hàm.