The words you are searching are inside this book. To get more targeted content, please make full-text search by clicking here.

Đây là tập bài giảng lập trình hướng đối tượng được viết dành riêng cho sinh viên ngành Hệ thống thông tin trường Đại học Nội vụ Hà Nội

Discover the best professional documents and content resources in AnyFlip Document Base.
Search
Published by Le Minh Tuan, 2020-04-01 00:20:25

Bài giảng lập trình hướng đối tượng

Đây là tập bài giảng lập trình hướng đối tượng được viết dành riêng cho sinh viên ngành Hệ thống thông tin trường Đại học Nội vụ Hà Nội

Keywords: DHNV,OOP,HTTT

Lập trình Hướng đối tượng

Console.WriteLine("Value of (a + b) * (c / d) is : {0}", e);
e = a + (b * c) / d; // 20 + (150/5)
Console.WriteLine("Value of a + (b * c) / d is : {0}", e);
Console.ReadLine();
}
}
}

2.9. Các câu lệnh điều kiện trong C#

Cấu trúc điều khiển đòi hỏi phải lập trình để chỉ định một hoặc nhiều điều kiện, để
đánh giá hay kiểm tra bởi chương trình, cùng với một hoặc nhiều câu lệnh để được thực thi
nếu điều kiện được xác định là đúng, và câu lệnh khác được thực thi nếu điều kiện được
xác định là sai.

Sau đây là cấu trúc chung của lệnh điều kiện thường được tìm thấy trong hầu hết các
ngôn ngữ lập trình:

Tương tự như trong C/C++, Ngôn ngữ C # cung cấp các loại cấu trúc điều kiện sau:

Câu lệnh Mô tả
If(điều kiện)
If…..else…… Gồm một biểu thức logic cùng với một hoặc nhiều câu
If lồng nhau lệnh.
Câu lệnh switch
Có thêm tùy chọn khác nếu biểu thức logic là sai

Có thể đặt if hoặc if…else…bên trong điều kiện if
hoặc else
Một câu lệnh switch cho phép một biến sẽ được kiểm
tra bình đẳng đối với một danh sách các giá trị.

Trang 51

Trung tâm Tin học – Ngoại ngữ

Switch lồng nhau Bạn có thể sử dụng một câu lệnh switch bên trong một
câu lệnh switch (s).
2.9.1. Câu lệnh if…

Một câu lệnh if… bao gồm một biểu thức logic theo kèm một hoặc nhiều câu lệnh.

Cú pháp:

if(Bieu thuc dieu kien)
{

/* Câu lệnh được thực thi nếu biểu thức điều kiện đúng */
}

Ví dụ:

using System;
namespace DecisionMaking
{

class Program
{

static void Main(string[] args)
{

/* Khai báo biến */
int a = 10;
/* Kiểm tra biểu thức logic */
if (a < 20)
{

/* Nếu điều kiện đúng,
in ra màn hình 'A NHO HON 20'*/

Console.WriteLine("A NHO HON 20");
}
Console.WriteLine("GIA TRI CUA A LA : {0}", a);
Console.ReadLine();
}
}
}

2.9.2. Câu lệnh if..else..

Cú pháp:

if(Bieu thuc dieu kien)
{

/* Lệnh thực thi khi biểu thức điều kiện đúng */

Trang 52

Lập trình Hướng đối tượng

}
else
{

/* Lệnh thực thi khi biểu thức điều kiện sai */
}

Ví dụ:

using System;
namespace DecisionMaking
{

class Program
{

static void Main(string[] args)
{

/* Khai báo biến */
int a = 100;

/* Kiểm tra biểu thức logic */
if (a < 20)
{

/* Nếu điều kiện đúng, in ra */
Console.WriteLine("A NHO HON 20");
}
else
{
/* Nếu điều kiện sai in ra */
Console.WriteLine("A KO NHO HON 20");
}
Console.WriteLine("GIA TRI CUA A LA : {0}", a);
Console.ReadLine();
}
}
}

2.9.3. Câu lệnh if…else if…else

Ví dụ:

using System;
namespace DecisionMaking
{

class Program
{

Trang 53

Trung tâm Tin học – Ngoại ngữ

static void Main(string[] args)
{

int a = 100;

if (a == 10)
{

Console.WriteLine("GIA TRI CUA A LA 10");
}

else if (a == 20)
{

Console.WriteLine("GIA TRI CUA A LA 20");
}

else if (a == 30)
{

Console.WriteLine("GIA TRI CUA A LA 30");
}

} else
{
2.9.4.
Console.WriteLine("KO CO GIA TRI PHU HOP");
}
Console.WriteLine("GIA TRI CHINH XAC CUA A LA: {0}", a);
Console.ReadLine();
}
}

Câu lệnh switch

Một câu lệnh cho phép một biến sẽ được kiểm tra trong sự bình đẳng đối với một
danh sách các giá trị. Mỗi giá trị được gọi là một trường hợp, và biến được được kiểm tra
đối với từng trường hợp.

Cú pháp:

switch(BIEU THUC) {
case GiaTri1:
Cau lenh 1(s);
break; /* KO BẮT BUỘC */
case GiaTri2:

Trang 54

Lập trình Hướng đối tượng

Cau lenh 2(s);
break; /* KO BẮT BUỘC */

/* bạn có thể có nhiều số trường hợp của case */
default: /* KO BẮT BUỘC */
Cau lenh(s);
}

Ví dụ:

using System;
namespace DecisionMaking
{

class Program
{

static void Main(string[] args)
{

char grade = 'B';

switch (grade)
{

case 'A':
Console.WriteLine("XUAT SAC!");
break;

case 'B':
case 'C':

Console.WriteLine("TOT !");
break;
case 'D':
Console.WriteLine("DAU");
break;
case 'F':
Console.WriteLine("CO GANG HON NUA");
break;
default:
Console.WriteLine("CAP DO KO PHU HOP");
break;
}
Console.WriteLine("CAP DO CUA BAN LA {0}", grade);
Console.ReadLine();
}
}
}

Trang 55

Trung tâm Tin học – Ngoại ngữ

2.9.5. Switch lồng nhau

using System;
namespace DecisionMaking
{

class Program
{

static void Main(string[] args)
{

int a = 100;
int b = 200;

switch (a)
{

case 100:
Console.WriteLine("DAY LA MOT PHAN CUA SWITCH ");
switch (b)
{

case 200:
Console.WriteLine("DAY LA MOT PHAN CUA SWITCH ");
break;
}
break;
}
Console.WriteLine("GIA TRI CUA A LA : {0}", a);
Console.WriteLine("GIA TRI CUA A LA : {0}", b);
Console.ReadLine();
}
}
}

2.10. Toán tử điều kiện trong C#

Chúng ta có một toán tử điều kiện …?… :….. Nó có thể được sử dụng để thay thế if
… else. Nó có hình thức chung sau đây:

Exp1 ? Exp2 : Exp3;

Chỗ exp1, exp2, và exp3 là những biểu thức. Chú ý việc sử dụng và vị trí của dấu 2
chấm.

Trang 56

Lập trình Hướng đối tượng
Giá trị của một ? biểu thức được xác định như sau: Giá trị của biểu thức Exp1 trước

dấu ? có giá trị true, Exp2 được thực hiện, và giá trị của nó là giá trị của biểu thức. Nếu
Exp1 là false thì Exp3 được thực hiện và giá trị của nó là giá trị của biểu thức.

2.11. Vòng lặp trong C#

Có thể có một số tình huống bạn cần phải thực thi một khối mã nhiều lần. Các câu
lệnh được thực hiện tuần tự: Câu lệnh đầu được thực hiện đầu tiên, tiếp theo là câu lệnh
thứ hai, và tiếp tục là các câu lệnh tiếp theo. Ngôn ngữ lập trình cung cấp nhiều cấu trúc
điều khiển khác nhau, cho phép ta thực hiện mục đích bằng nhiều cách khác nhau. Một câu
lệnh vòng lặp cho phép chúng ta thực hiện một câu lệnh hoặc một nhóm lệnh nhiều lần.
Sau đây là cấu trúc tổng quát một vòng lặp trong hầu hết các ngôn ngữ lập trình:

• C # cung cấp các loại vòng lặp sau đây để xử lý yêu cầu lặp:
o Vòng lặp while: Nó lặp đi lặp lại một câu lệnh hoặc một khối lệnh khi điều
kiện là đúng. Điều kiện này được kiểm tra trước khi thực hiện thân vòng lặp.
o Vòng lặp for: Nó thực hiện các câu lệnh nhiều lần và ước lượng được số lần
lặp thông qua biến lặp.

Trang 57

Trung tâm Tin học – Ngoại ngữ

o Vòng lặp do..while: Nó tương tự như một vòng lặp while, ngoại trừ việc nó
kiểm tra điều kiện ở cuối thân vòng lặp

o Vòng lặp lồng nhau: Bạn có thể sử dụng một hoặc nhiều vòng lặp bên bất kỳ
một vòng lặp khác.

• C# cũng cung cấp các lệnh điều khiển sử dụng trong vòng lặp, các lệnh này làm
thay đổi trình tự thực hiện bình thường của vòng lặp. Khi thực hiện lại, tất cả các
đối tượng đã được tạo trước đó sẽ bị hủy. C # cung cấp các lệnh điều khiển sau:
o Lệnh break: lệnh này làm kết thúc vòng lặp hoặc switch và chuyển sang thực
hiện câu lệnh sau vòng lặp hoặc switch.
o Lệnh continue: Bỏ qua, không thực hiện phần còn lại của thân vòng lặp
mà quay lại kiểm tra điều kiện để tiến hành lặp tiếp tục.

• Lặp vô tận: là trường hợp một vòng lặp không bao giờ dừng. Điều này xảy ra nếu
một điều kiện không bao giờ sai. Vòng lặp for thường được sử dụng cho mục đích
này. Ta có thể tạo ra một vòng lặp vô hạn khi để trống biếu thức điều kiện của một
vòng lặp for.

Thí dụ:

using System;
namespace Loops
{

class Program
{

static void Main(string[] args)
{

for (;;)
{

Console.WriteLine("Hey!I am SV He thong Thong tin.");
}
}
}
}

Khi các biểu thức điều kiện được bỏ trống, nó được giả định là đúng. Bạn có thể khởi
tạo biểu thức và thay đổi nó để tạo một vòng lặp vô hạn, nhưng các lập trình viên thường
sử dụng cấu trúc for (;;) để biểu hiện một vòng lặp vô hạn.

Trang 58

Lập trình Hướng đối tượng

2.11.1. Vòng lặp while

Cú pháp:

while(condition)
{

statement(s);
}

Với statement(s) có thể là một lệnh đơn hoặc một khối lệnh. Condition có thể là một
biểu thức, với giá trị khác 0 ta hiểu conditon mang giá trị true và ngược lại, biểu thức mang
giá trị 0 thì condition là false. Vòng lặp while lặp khi condition có giá trị true.

Một khi condition false, chương trình sẽ chuyển đến câu lệnh tiếp theo sau vòng lặp
while.

Ví dụ:

Trang 59

Trung tâm Tin học – Ngoại ngữ

using System;
namespace Loops
{

class Program
{

static void Main(string[] args)
{

/*Khai báo biến cục bộ a và gán giá trị là 10*/
int a = 10;

/* Vòng lặp while*/
while (a < 20)
{

Console.WriteLine("value of a: {0}", a);
a++;
}
Console.ReadLine();
}
}
}

Với đoạn chương trình trên, ta thấy a<20 là biểu thức điều kiện condition. Và với
khởi tạo ban đầu a=10 thì biểu thức này cho kết quả true nên khổi lệnh trong while sẽ được
thực hiện.

Khối lệnh while chỉ ngừng khi biến a tăng đến 20 (do biểu thức a++) làm cho
condition a<20 sai.

2.11.2. Vòng lặp for

Vòng lặp này được sử dụng trong những trường hợp ta có thể xác định được trước số
lần lặp.

Cú pháp:

for ( init; condition; increment )
{

statement(s);
}

Trang 60

Lập trình Hướng đối tượng
Với biểu thức init được thực hiện đầu tiên và 1 lần duy nhất. Bước này dùng để khai

báo và khởi tạo biến điều khiển. Đôi lúc ta không cần có biểu thức init, miễn là có dấu ;
Condition vẫn là biểu thức điều kiện. Nếu nó đúng, phần thân của vòng lặp sẽ được

thực thi. Nếu nó là sai, phần thân của vòng lặp không thực hiện và chuyển đến thực thi câu
lệnh tiếp theo ngay sau vòng lặp.

Sau khi thực thi khối lệnh trong thân vòng lặp, biểu thức increment sẽ được thực
hiện. Increment này cho phép ta cập nhật giá trị biến điều khiển vòng lặp. Phần này cũng
có thể để trống.

Tiếp theo, chương trình sẽ lại kiểm tra condition. Nếu đúng, vòng lặp lại được thực
hiện và quá trình này sẽ lặp đi lặp lại (thực hiện khối lệnh trong thân của vòng lặp, sau đó
thay đổi biến điều khiển, và sau đó một lần nữa kiểm tra điều kiện). Vòng lặp for chấm dứt
chỉ khi condition false.

Trang 61

Trung tâm Tin học – Ngoại ngữ

Ví dụ:

using System;
namespace Loops
{

class Program {
static void Main(string[] args) {
/* Vòng lặp for */
for (int a = 10; a < 20; a = a + 1)
{
Console.WriteLine("value of a: {0}", a);
}
Console.ReadLine();
}

}
}

Trang 62

Lập trình Hướng đối tượng
Với đoạn chương trình trên, biểu thức init khai báo một biến a và gán giá trị là 10.

Tiếp theo biểu thức condition a<20 sẽ được kiểm tra. Với a=10 condition là true, câu lệnh
trong for sẽ được thực hiện. và sau đó biến a được tăng lên 1 thông qua biểu thức increment
a=a+1.

Chương trình quay lại kiểm tra condition và thực thi câu lệnh trong for. Cho đến khi
a tăng đến giá trị 20 làm cho condition sai thì vòng lặp kết thúc.

Kết quả vòng lặp này tương tự vòng lặp while.

2.11.3. Vòng lặp do…while

Không giống như for và while kiểm tra các điều kiện ở đầu vòng lặp, do … while
kiểm tra điều kiện của nó ở cuối vòng lặp.

do…while tương tự như vòng lặp while, ngoại trừ việc nó đảm bảo rằng vòng lặp này
sẽ được thực hiện ít nhất một lần.

Cú pháp:

do{
statement(s);

}while( condition );

Chú ý rằng các biểu thức điều kiện xuất hiện ở cuối của vòng lặp, vì vậy statement
(s) trong vòng lặp thực hiện một lần trước khi điều kiện được kiểm tra.

Nếu điều kiện là đúng chương trình sẽ trở lại đầu khối lệnh để thực hiện tiếp
statement(s) trong vòng lặp một lần nữa. Quá trình này lặp đi lặp lại cho đến khi điều kiện
trở thành sai.

Trang 63

Trung tâm Tin học – Ngoại ngữ

Ví dụ:

using System;
namespace Loops
{

class Program
{

static void Main(string[] args)
{

/* Khai báo biến cục bộ */
int a = 10;
/* vòng lặp do..while */
do
{

Console.WriteLine("value of a: {0}", a);
a = a + 1;
} while (a < 20);
Console.ReadLine();
}
}
}

Khi bắt đầu vòng lặp, câu lệnh console.ReadLine(“Value of a: {0}”,a); sẽ được thực
hiện ngay sau đó. Tiếp theo là lệnh a=a+1; làm thay đổi giá trị biến a. Thực hiện hết các

Trang 64

Lập trình Hướng đối tượng

câu lệnh trong thân vòng lặp, tiếp đến chương trình kiểm tra điều kiện lặp. a<20 đúng khi
a=11 do đó vòng lặp do…while được thực hiện một lần nữa. Vòng lặp được thực hiện tiếp
tục cho đến khi biến a tăng đến giá trị làm biểu thức điều kiện sai.

Đoạn chương trình trên cho a kết quả tương tự vòng lặp for và while.

2.11.4. Vòng lặp lồng nhau

Ta có thể lồng các vòng lặp vào nhau như ví dụ sau đây:

using System;
namespace Loops
{

class Program
{

static void Main(string[] args)
{

/* Khai báo biến */
int i, j;
/* Vòng lặp for lồng trong 1 vòng lặp for khác */
for (i = 2; i < 100; i++)//for1
{

for (j = 2; j <= (i / j); j++)//for2
if ((i % j) == 0)
break; // if factor found, not prime

if (j > (i / j))
Console.WriteLine("{0} is prime", i);

}
Console.ReadLine();
}
}
}

2.11.5. Lệnh điều khiển break và continue

Ví dụ sau đây cho ta thấy cách mà lệnh break và continue điều khiển vòng lặp:

using System;
namespace Loops
{

class Program
{

Trang 65

Trung tâm Tin học – Ngoại ngữ

static void Main(string[] args)
{

/* local variable definition */
int a;
int count = 0;
for (a = 1; a < 20; a++)
{

if (a % 2 == 0)//if1
{

continue;
}
Console.WriteLine("Value of a:{0}", a);
count++;
if (count == 5)//if2

break;
}
Console.ReadLine();
}
}
}

Ta thấy lệnh continue được thực hiện chỉ khi giá trị của a không chia hết cho 2. Lệnh
này làm cho các câu lệnh phía sau không được thực hiện. Và chương trình sẽ dừng nếu
biến count đạt đến giá trị 5 do lệnh break trong câu if thứ 2.

Trang 66

Lập trình Hướng đối tượng

CHƯƠNG 3. LỚP VÀ ĐỐI TƯỢNG

C# là ngôn ngữ lập trình hướng đối tượng 100%. Hầu như mọi thứ trong C# đều là
class. Do đó, học cách xây dựng và sử dụng class là yêu cầu bắt buộc trong khi học lập
trình C#. Không nắm vững khái niệm class/object sẽ rất khó để học C#; Không biết kỹ
thuật xây dựng class thì hầu như không làm được gì trong C#.

3.1. Class, trừu tượng hóa, lập trình hướng đối tượng
3.1.1. Class và trừu tượng hóa

Class là bản mô tả những tính chất và hành vi chung của những gì tồn tại trong thực
tế. “Những gì tồn tại trong thực tế” đó được gọi là các đối tượng (object) cụ thể. Từ các
đối tượng cụ thể, chúng ta phân tích ra những điểm chung về thông tin và hoạt động để tạo
thành class.

Việc phân tích và tóm lược những tính chất và hành vi chung của một nhóm những
đối tượng thực tế như vậy được gọi là trừu tượng hóa (abstraction). Việc trừu tượng hóa
giúp chúng ta tách rời những thông tin cần thiết của đối tượng để nghiên cứu, đồng thời bỏ
qua những gì không liên quan.

Class là kết quả của sự trừu tượng hoá các đối tượng cùng loại về hai khía cạnh: thông
tin mô tả đối tượng, và những hành vi (hoạt động) trên các thông tin đó.

Với ý nghĩa trên, class không nhất thiết phải liên quan đến việc lập trình. Chúng ta có
thể tạo ra các class với ý nghĩa là một sự trừu tượng hóa bất kỳ khi nào cần nghiên cứu về
các đối tượng.

3.1.2. Object và cụ thể hóa

Ở chiều ngược lại, nếu có một bản mô tả trừu tượng, chúng ta có thể tạo ra nhiều
phiên bản cụ thể của nó.

Trang 67

Trung tâm Tin học – Ngoại ngữ

Ví dụ, nếu có bản mô tả trừu tượng về tủ, chúng ta có thể dùng vật liệu để tạo ra nhiều
chiếc tủ thực sự dựa trên mô tả đó.

Những phiên bản cụ thể tạo ra từ mô tả trừu tượng như vậy được gọi là object.

Như vậy, quan hệ giữa class và object là loại quan hệ giữa mô tả (nằm trên giấy, trong
suy nghĩ, v.v.) với sự vật/hiện tượng cụ thể trong thực tế.

3.2. Class trong lập trình hướng đối tượng và C#

Class trong các ngôn ngữ lập trình hướng đối tượng nói chung, trong C# nói riêng,
mang ý nghĩa là một kiểu dữ liệu. Đối với C#, class là các khối xây dựng cơ sở của các
chương trình ứng dụng, và là trung tâm của lập trình C#.

Class trong C# là một loại kiểu dữ liệu đặc biệt chứa định nghĩa những thuộc tính
(thông tin) và phương thức (hành vi), dùng để mô tả chung cho một nhóm những thực thể
cùng loại.

Trong C#, mỗi class có thể chứa:
• Biến thành viên (field): Lưu trữ các thông tin mô tả về đối tượng hay trạng thái

của đối tượng;
• Thuộc tính (property): có vai trò lưu trữ thông tin tương tự như biến thành viên

nhưng có khả năng kiểm soát dữ liệu xuất nhập;
• Phương thức (method): Dùng để cập nhật, tính toán, cung cấp và xử lý thông tin;
• Sự kiện (delegate/event): Gửi thông báo về sự thay đổi trạng thái của đối tượng ra

bên ngoài.

Ngoài ra, trong class còn có thể chứa định nghĩa của kiểu dữ liệu khác, gọi là kiểu
thành viên (member /inner/nested type). Class có thể chứa định nghĩa của bất kỳ nhóm kiểu
nào mà bạn đã biết (class, struct, interface, delegate, enum).

3.2.1. Khai báo lớp

Cú pháp khai báo lớp trong C#

Trang 68

Lập trình Hướng đối tượng

public|internal] class <tên class>{ [thân class] }

trong đó:

class là từ khóa của C# dùng để khai báo class; tên class do người lập trình lựa chọn
và phải tuân thủ quy tắc đặt định danh (xem dưới đây);

public hoặc internal được gọi là các từ khóa điều khiển truy cập (access modifier)
của class. Các phần này viết tách nhau bởi dấu cách.

Dấu cách trong C# chỉ có tác dụng phân tách các thành phần của một lệnh, khác với
một số ngôn ngữ dùng dấu cách như một phần của cú pháp. Số lượng dấu cách không ảnh
hưởng tới ý nghĩa của code. Compiler sẽ tự bỏ qua những dấu cách thừa.

Từ khóa “class” là bắt buộc khi khai báo lớp. Mặc định, Visual Studio thể hiện từ
khóa bằng màu xanh da trời. Từ khóa là những cụm ký tự được C# lựa chọn cho những
mục đích riêng để diễn đạt cú pháp của ngôn ngữ. Một số kiểu dữ liệu dựng sẵn của C#
cũng được đặt thành từ khóa (sẽ xem xét trong các bài sau).

Từ khóa điều khiển truy cập quyết định phạm vi sử dụng của class. Mỗi class trong
C# có thể chỉ được sử dụng nội bộ trong phạm vi của project, hoặc có thể được sử dụng
bởi các project khác. Mặc định, mỗi class trong C# chỉ được sử dụng trong phạm vi của
project (sử dụng bởi các class khác trong cùng project). Do đó, nếu không thấy điều khiển
truy cập nào thì sẽ hiểu là “internal”. Nếu muốn sử dụng class này trong các project khác,
trước từ khóa class cần bổ sung từ khóa “public”.

Từ khóa public sẽ thể hiện rõ vai trò của mình khi bạn bắt đầu tách một project lớn
thành nhiều project con. Trong đó, project con thường là các thư viện lớp. Các class trong
thư viện lớp phải đặt truy cập public thì mới sử dụng được trong project khác. Nếu chỉ có
một project, internal hay public không có gì khác biệt.

Ví dụ minh họa là file mã nguồn đầu tiên của chúng ta (Program.cs) của project đã
xây dựng trong bài cài đặt Visual Studio.

Trang 69

Trung tâm Tin học – Ngoại ngữ

Khi xem class như một kiểu dữ liệu thì object của class tương ứng chính là biến thuộc
kiểu dữ liệu đó. Class chứa mô tả trừu tượng còn object chứa giá trị cụ thể của mỗi mô tả
đó. Class trong ví dụ này được đặt tên là Program, là một internal class (không chỉ rõ từ
khóa truy cập => C# sẽ coi là internal). Khối code thân class này hiện có một phương thức
(Main() hay Entry Point).

Đặt tên class trong C#
Tên class do người lập trình tự chọn và phải tuân thủ quy tắc đặt định danh (identifier)
trong C#, cụ thể như sau:
• Định danh phải bắt đầu bằng chữ cái, ký tự gạch chân “_” hoặc ký tự “@.”;
• Định danh chỉ được chứa chữ cái, chữ số và ký tự gạch chân;
• Định danh không được trùng với từ khóa; nếu muốn đặt định danh trùng với từ

khóa, cần đặt ký tự @ phía trước;
• Độ dài định danh không giới hạn và có thể chứa ký tự Unicode (ví dụ, có thể đặt

định danh bằng tiếng Việt có dấu);
• Định danh có phân biệt chữ hoa và chữ thường (case-sensitive).
• Ngoài các quy tắc trên, việc đặt tên class trong C# cũng nên tuân thủ các quy ước

sau:

Trang 70

Lập trình Hướng đối tượng

• Mỗi file mã nguồn chỉ nên chứa một class và tên class đặt trùng tên file. Quy ước
này kết hợp với quy ước đặt tên namespace giúp đồng bộ giữa cấu trúc quản lý
class với cấu trúc file vật lý, giúp việc quản lý mã nguồn dễ dàng hơn;

• Tên class bắt đầu bằng chữ cái in hoa;
• Nếu tên class gồm nhiều từ ghép lại thì nên đặt theo kiểu CamelCase (viết hoa chữ

cái đầu của mỗi từ).
• Tên class C# được Visual Studio hiển thị bằng màu xanh nhạt.

Thân class

• Thân class là một khối code và có thể chứa khai báo biến/hằng thành viên, thuộc
tính và phương thức thành viên.

• Các thành phần của class sẽ lần lượt được xem xét trong các bài học tương ứng.
• Đặc biệt hơn, trong thân class cũng có thể khai báo class khác, gọi là nested class

(sẽ xem xét trong bài học riêng). Toàn bộ thân class phải đặt trong cặp dấu {}.
• Toàn bộ code nằm trong một cặp dấu {} được gọi là một khối code (code block).
• Thân của class, namespace, method, interface, struct, các cấu trúc điều khiển (sẽ

xem xét sau) đều là khối code.

3.2.2. Biến thành viên

Biến thành viên (member variable) là thành phần chứa dữ liệu của class trong C#. Nó
được khai báo trực tiếp trong thân của class (phải nằm trực tiếp trong khối code của thân
class). Dữ liệu này có thể được sử dụng bởi bất kỳ thành phần nào khác của class. Tùy vào
thiết lập, dữ liệu này cũng có thể được sử dụng bởi thành phần bên ngoài class.

Trong C#, nếu một biến được khai báo nằm trong thân của phương thức, nó được gọi
là biến cục bộ (local variable). Chúng ta xem xét biến cục bộ và phương thức ở bài học
khác.

Biến thành viên thường được gọi ngắn gọn là trường (field), hay trường dữ liệu (data
field).

Biến thành viên được xem là nơi lưu trữ trạng thái (status) của class. Thay đổi giá trị
biến thành viên là thay đổi trạng thái của class.

Trang 71

Trung tâm Tin học – Ngoại ngữ

Cú pháp khai báobiếnthành viên

[public|private|protected] <kiểu-dữ-liệu> <tên-biến> [= <giá-trị>];

Trong đó:

“public”, “private” và “protected” là các từ khóa điều khiển truy cập (access
modifier) cho biến thành viên, cụ thể:

• public: biến có thể được truy xuất từ bên ngoài class
• private (mặc định): chỉ truy xuất được bên trong class
• protected: có thể truy cập bên trong class và lớp kế thừa
Nếu không ghi rõ từ khóa, C# mặc định sẽ hiểu đó biến đó là private, nghĩa là chỉ
có thể sử dụng nội bộ trong class, bên ngoài không thể truy xuất giá trị này. Sự khác biệt
giữa ba từ khóa này sẽ thể hiện rõ ràng hơn ở bài học về kế thừa.

Khái niệm “trong” và “ngoài” class có thể hiểu đơn giản như sau: “trong” class nghĩa
là tất cả code nằm trong khối code thân class. Những code nào không nằm trong khối code
thân class được coi là “ngoài”. Code trong class đều có thể “nhìn thấy” các biến thành viên
và sử dụng chúng. Code ở bên ngoài class chỉ có thể nhìn thấy và truy xuất các thành viên
đặt là public.

Kiểu dữ liệu

Kiểu dữ liệu có thể là những kiểu dữ liệu do C# định nghĩa sẵn (int, bool, string, v.v.)
hoặc do người dùng tự định nghĩa.

C# đã định nghĩa sẵn các kiểu dữ liệu cơ bản, tương tự trong nhiều ngôn ngữ lập trình
khác.

C# cũng cho phép người dùng định nghĩa thêm các kiểu dữ liệu có cấu trúc của riêng
mình, như mảng (array), bản ghi (struct), liệt kê (enum), lớp (như chúng ta đang làm).

Trang 72

Lập trình Hướng đối tượng

Bộ thư viện .NET framework cung cấp một số lượng kiểu dữ liệu (lớp) khổng lồ có
thể sử dụng trong các khai báo này. Trong quá trình học bạn đã lần lượt gặp các kiểu dữ
liệu nói trên, cũng như tự định nghĩa ra các kiểu dữ liệu của riêng mình.

Các kiểu dữ liệu cơ sở của C# được Visual Studio hiển thị màu sắc tương tự như từ
khóa; các kiểu dữ liệu do người dùng định nghĩa được hiển thị tương tự như class.

Quy ước đặt tên biến thành viên trong C#

Tên biến do người lập trình tự chọn và tuân thủ theo quy tắc đặt định danh giống như
đối với tên class. Ngoài ra có một số quy ước (không bắt buộc nhưng nên tuân thủ để thống
nhất) sau về cách đặt tên biến:

• Tên biến public và protected nên bắt đầu bằng chữ cái in hoa và tuân theo cách
viết PascalCase;

• Tên biến private nên bắt đầu bằng ký tự gạch chân và tuân theo cách viết
camelCase;

Quy ước đặt tên biến thành viên khác với quy ước đặt tên biến cục bộ (biến khai báo
trong thân phương thức). Biến cục bộ đều viết theo kiểu camelCase.

Gán giá trị cho biến thành viên, giá trị mặc định

Khi khai báo một biến thành viên, C# sẽ tự động gán cho biến này một giá trị mặc
định (lúc khởi tạo object) tùy thuộc vào kiểu dữ liệu: giá trị 0 đối với kiểu số nguyên, false
đối với kiểu bool. Ví dụ:

int a; // a tự nhận giá trị 0
bool b; // b tự nhận giá trị false

Nếu muốn gán một giá trị khác, người dùng có thể sử dụng biểu thức gán (assignment
operator) để gán giá trị cho biến ngay trong lúc khai báo.

int a = 10;
bool b = true;

Ví dụ về cách khai báo biến thành viên của class C#

Trang 73

Trung tâm Tin học – Ngoại ngữ

Trong phần này bạn sẽ thực hiện một ví dụ nhỏ về khai báo biến thành viên cho class

để nắm rõ hơn các vấn đề lý thuyết trình bày ở trên.

using System;
namespace LTHDT
{

class Program
{

static void Main(string[] args)
{

// khởi tạo biến kiểu Car
Car bmw = new Car();
// phương thức Main thuộc lớp Program nằm "bên ngoài"
// class Car,do đó nó chỉ "nhìn thấy"
// các biến thành viên public của Car
bmw.Color = "White";
bmw.Seats = 2;
// các biến thành viên private khác của Car
// hoàn toàn "vô hình" với Program.
Console.WriteLine("A new " + bmw.Color + " BMW");
Console.ReadKey();
}
}

/// <summary>
/// Lớp mô tả ô tô
/// </summary>
class Car
{

// dưới đây là các trường private
//(chứa thông tin nội bộ của class)
// các trường này không được gán giá trị đầu,
// chúng sẽ nhận giá trị mặc định theo kiểu
// lưu ý cách đặt tên: có dầu gạch chân ở đầu,
// bắt đầu là chữ cái thường, camelCase,
// báo hiệu đây là trường private
string _make; // nhãn hiệu, giá trị mặc định theo kiểu là null
int _yearOfProduction;
// năm sản xuất, giá trị mặc định theo kiểu là 0
bool _hasInsurance;
// có mua bảo hiểm hay không,
// giá trị mặc định theo kiểu là false
// dưới đây là các trường public

Trang 74

Lập trình Hướng đối tượng

// (các class khác có thể đọc/ghi giá trị)
// mỗi trường sẽ được gán giá trị đầu
// lưu ý cách đặt tên: bắt đầu là chữ cái hoa
public int Seats = 5; // số chỗ ngồi, gán giá trị đầu là 5
public string Color = "Black"; // màu sắc, giá trị đầu là "Black"
}
}

3.2.3. Một số vấn đề khi sử dụng biến thành viên

Hạn chế sử dụng biến thành viên public

Trong lập trình hướng đối tượng, nhìn chung đều khuyến nghị hạn chế sử dụng biến
thành viên public.

Các biến thành viên thường đường khai báo là private/protected, sau đó viết các
phương thức xuất/nhập dữ liệu riêng cho từng biến (gọi là các hàm getter/setter). Cách thức
này giúp kiểm soát được giá trị xuất nhập cho các biến.

C# cung cấp một tính năng riêng giúp giải quyết vấn đề này và chúng ta sẽ xem xét
trong bài Property.

Như trong ví dụ trên bạn khai báo hai biến public Seats và Color. Ví dụ này hoàn toàn
mang tính chất minh họa. Nếu xây dựng class thực sự, bạn không nên khai báo như vậy mà
cần dùng đặc tính (Property) thay cho hai biến public này.

Biến thành viên chỉ đọc, từ khóa readonly

Mặc định C# cho phép tự do thay đổi giá trị của biến thành viên. Nếu biến public, có
thể thay đổi giá trị của nó ở trong hoặc ngoài class. Nếu là biến protected/private thì chỉ có
thể thay đổi giá trị của nó ở bên trong class.

Tuy nhiên, có một số trường hợp bạn muốn rằng giá trị của biến sau khi khởi tạo sẽ
không thay đổi được nữa. Tức là, nó biểu hiện gần giống như hằng số.

Vậy tại sao không sử dụng hằng?

Trang 75

Trung tâm Tin học – Ngoại ngữ

Trong C#, hằng thành viên của class (và struct) có chút hơi khác biệt. Hằng thành
viên được xem là một thành viên tĩnh (static) của class. Bạn sẽ học về thành viên tĩnh trong
một bài học khác. Đặc điểm này khiến hằng không phù hợp với vai trò lưu trữ dữ liệu chỉ
đọc cho object của class.

C# cung cấp một từ khóa riêng để tạo ra các biến chỉ đọc: từ khóa readonly. Từ khóa
này cần đặt trước tên kiểu khi khai báo biến thành viên.

Hãy cùng thực hiện ví dụ sau để hiểu rõ hơn về biến readonly

using static System.Console;
namespace ReadOnly
{

class Product
{

// đây là các biến readonly,
// chỉ có thể khởi tạo giá trị trực tiếp ở đây
public readonly double UnitPrice = 100;
public readonly double Discount = 0.1;

// đây là một biến thông thường, có thể tự do thay đổi giá trị
public int Amount;
public Product()
{

}
public Product(double unitPrice, double discount)
{
// hoặc thay đổi giá trị trong constructor
// một khi đã khởi tạo giá trị,
// biến readonly không thay đổi giá trị được nữa
UnitPrice = unitPrice;
Discount = discount;
Amount = 0;
}

/* Phương thức này lỗi.
* UnitPrice và Discount là biến readonly.
* Không thể thay đổi giá trị của biến readonly
* trong phương thức

Trang 76

Lập trình Hướng đối tượng

*/
public void Reset()
{

UnitPrice = 0;
Discount = 0;
}
}
class Program
{
static void Main(string[] args)
{
var product = new Product();

WriteLine($"Unit Price = {product.UnitPrice}");

// Lệnh này lỗi. Không thể thay đổi giá trị biến readonly
//product.UnitPrice = 300;

product = new Product(200, 0.2);
WriteLine($"Unit Price = {product.UnitPrice}");
ReadKey();
}
}
}

Biến readonly có đặc điểm:

• Chỉ có thể gán giá trị một lần duy nhất lúc khai báo, hoặc trong hàm tạo
(constructor).

• Không thể gán giá trị ở bất kỳ phương thức nào khác.
• Một khi đã gán giá trị, nó không cho phép thay đổi giá trị.

3.2.4. Phương thức thành viên

Như bạn đã biết, phương thức (method) trong C# là một thành viên của class (và
struct), là một khối code được đặt tên và chứa các lệnh để cùng thực hiện một nhiệm vụ cụ
thể. Phương thức cho phép tái sử dụng code mà không phải viết lặp đi lặp lại nhiều lần.

Phương thức trong C# tương tự như hàm (function) và thủ tục (procedure) của Pascal,
chương trình con Sub của visual basic, v.v.. Sự khác biệt lớn là phương thức của C# bắt

Trang 77

Trung tâm Tin học – Ngoại ngữ

buộc phải là thành viên của một kiểu dữ liệu như struct hay class. Trong C# không có
phương thức “tự do” hay “toàn cục”.

Làm việc với phương thức chia làm hai giai đoạn:

• Khai báo (định nghĩa): Ở giai đoạn khai báo chúng ta mô tả các thông tin bắt buộc
về phương thức, cũng như viết các lệnh cần thực hiện trong thân phương thức.

• Gọi (sử dụng): đây là giai đoạn chúng ta cung cấp dữ liệu thực sự để phương thức
thực hiện những lệnh đã được thiết kế sẵn ở phần định nghĩa.

Khai báo phương thức

Cấu trúc chung để khai báo phương thức trong class C# như sau:

[public|protected|private] <kiểu ra> <tên phương thức> ([danh sách tham số])
{

[thân phương thức]
[return [giá trị];]
}

Trong đó:

public, protected và private là các từ khóa điều khiển truy cập tương tự như
đối với biến thành viên. Mặc định C# xem phương thức là private nếu không có từ khóa
nào được chỉ định.

So với struct, phương thức thành viên class có thêm từ khóa protected.

Tên phương thức do người dùng tự đặt và tuân thủ theo quy tắc đặt định danh của C#.
Ngoài ra, trong C# quy ước tên phương thức viết theo kiểu PascalCase (luôn bắt đầu bằng
chữ in hoa).

Kiểu dữ liệu trả về

Trang 78

Lập trình Hướng đối tượng

Kiểu trả về là kiểu dữ liệu của kết quả nhận được sau khi kết thúc thực hiện các lệnh
trong thân phương thức. Kiểu trả về có thể là bất kỳ kiểu dữ liệu nào được C#/.NET định
nghĩa sẵn hoặc cũng có thể là những kiểu dữ liệu do người dùng định nghĩa.

Cũng có thể có trường hợp phương thức không trả về kết quả nào (ví dụ, chúng ta chỉ
yêu cầu phương thức viết thông tin ra màn hình). Khi đó, C# yêu cầu phải viết kiểu trả về
là void, là một từ khóa của C#.

Nếu kiểu trả về khác void, trong thân phương thức bắt buộc phải có lệnh return để
báo rằng, giá trị đi sau return sẽ là kết quả thực hiện của phương thức. Nếu kiểu trả về là
void thì không bắt buộc phải có return.

Danh sách tham số

Danh sách tham số (còn gọi là danh sách tham số hình thức) là danh sách biến có thể
sử dụng trong phương thức.

Ở giai đoạn định nghĩa phương thức, chúng ta không biết giá trị cụ thể của các biến
này mà chỉ có thể sử dụng tên biến trong các lệnh ở thân phương thức.

Ở giai đoạn gọi phương thức người sử dụng phương thức mới cung cấp các giá trị cụ
thể (gọi là tham số thực).

Vì lý do này, ở giai đoạn định nghĩa thường phải kiểm tra hết các tình huống có thể
xảy ra với tham số hình thức.

Danh sách tham số được định nghĩa theo quy tắc sau:

(<kiểu_1> <tham_số_1>, <kiểu_2> <tham_số_2, …)

Hình dung một cách đơn giản, danh sách tham số chính là một chuỗi khai báo biến
cục bộ viết tách nhau bởi dấu phẩy. Do đó, mỗi tham số đều tuân thủ quy tắc khai báo:

<kiểu_dữ_liệu> <tên_biến>

Trang 79

Trung tâm Tin học – Ngoại ngữ

Danh sách tham số không bắt buộc phải có trong khai báo phương thức. Nếu danh
sách tham số trống, ta chỉ cần viết cặp dấu ngoặc tròn sau tên phương thức.

Gọi phương thức trong C#

Phương thức trong C# chỉ có thể khai báo là thành viên của một class hoặc struct nào
đó (trừ phương thức lambda, phương thức vô danh và hàm cục bộ sẽ xem xét sau). Khai
báo phương thức trong C# không thể nằm trực tiếp trong namespace, không thể nằm ngoài
namespace, cũng không thể nằm ngoài class/struct.

Ở giai đoạn sử dụng (gọi phương thức), người dùng cung cấp giá trị đầu vào thực
(nếu có) và nhận giá trị trả về (nếu có) qua “lời gọi phương thức” theo cấu trúc:

<tên_phương_thức>([biến_1, biến_2, …]);

Trong đó biến 1, biến 2, v.v. phải có kiểu theo đúng trật tự như khi khi định nghĩa
phương thức. Danh sách biến cung cấp cho lời gọi phương thức gọi là các tham số thực (để
phân biệt với danh sách tham số hình thức khi khai báo phương thức).

3.2.5. Tham số của phương thức trong C#: value type và reference type

Như bạn đã biết, các kiểu dữ liệu của C# chia làm hai loại: value type và reference
type. Object của value type nằm trong stack, còn object của reference type nằm trong heap.
Hai loại kiểu này biểu hiện khác nhau khi sử dụng trong tham số của phương thức.

Truyền kiểu giá trị

Khi truyền một biến thuộc kiểu giá trị cho một phương thức, một bản sao của biến
này được tạo ra trong stack của phương thức được gọi. Tất cả các thao tác mà phương thức
thực hiện trên tham số này đều chỉ tác động trên bản sao.

Do đó, sau khi kết thúc phương thức, giá trị của biến tham số vẫn giữ nguyên như
trước khi truyền vào phương thức. Tức là những thay đổi (nếu có) của biến trong phương
thức không được giữ lại.

Trang 80

Lập trình Hướng đối tượng

Ví dụ, nếu truyền giá trị i = 100 cho phương thức, một bản sao của i được tạo ra và
truyền vào phương thức. Những gì thay đổi trong thức thực chất đều tác động lên bản sao
của i, chứ không phải chính i. Do vậy, khi kết thúc phương thức, bản sao bị hủy bỏ, còn i
không thay đổi gì.

Truyền kiểu tham chiếu, từ khóa ref

Khi truyền một biến thuộc kiểu tham chiếu, bản thân địa chỉ của vùng heap nơi lưu
giá trị đó được truyền vào cho phương thức. Tất cả những thao tác trên biến tham số đó
thực chất đều tác động thẳng lên giá trị nằm trong heap. Vì vậy, sau khi kết thúc phương
thức, những thay đổi này vẫn được lưu lại.

Từ khóa ref cho phép truyền một biến thuộc kiểu giá trị nhưng có thể lưu giữ thay
đổi như khi sử dụng biến thuộc kiểu tham chiếu. Khi khai báo phương thức, nếu tham số
truyền vào thuộc kiểu giá trị nhưng cần phải giữ lại những thay đổi thực hiện trong thân
phương thức, C# cho phép sử dụng từ khóa ref trước khai báo tham số đó.

Ví dụ về truyền tham số cho phương thức

Hãy cùng thực hiện ví dụ sau và đọc kỹ comment để hiểu rõ sự khác biệt giữa tham
số kiểu giá trị và kiểu tham chiếu, cũng như tác dụng của từ khóa ref.

Ví dụ:

using System;
namespace LTHDT
{

internal class Data
{

public int Id { get; set; }
public string Name { get; set; }
}

internal class ParameterPassingTest
{
// truyền tham số kiểu value

Trang 81

Trung tâm Tin học – Ngoại ngữ

public void MethodWithValueType(int a)
{

a += 10; // thay đổi giá trị tham số
}
// truyền tham số kiểu reference
public void MethodWithReferenceType(Data s)
{
// thay đổi giá trị tham số

s.Name += " Edited";
s.Id += 10;
}
public void Method2WithReferenceType(Data s)
{
// khởi tạo object khác cho s,
// tương đương với việc cho s tham chiếu sang vùng nhớ khác
s = new Data { Id = 2, Name = "Donald Trump" };
}
// sử dụng từ khóa ref cho kiểu value
public void Method1WithRefKeyword(ref int a)
{
a += 10;
}
// sử dụng từ khóa ref cho kiểu reference
public void Method2WithRefKeyword(ref Data s1, ref Data s2)
{
// chỉ đổi giá trị
s1.Id += 10; s1.Name += " Edited";
// đổi thành một object khác
//(thay đổi địa chỉ tham chiếu tới)
s2 = new Data { Id = 100, Name = "Donald Trump" };
}
}

internal Program
{

private static void Main()
{

ParameterPassingTest test = new ParameterPassingTest();
int a = 0;
test.MethodWithValueType(a);
Console.WriteLine(a);
// a = 0, không thay đổi, vì a là biến kiểu value
Data d = new Data { Id = 0, Name = "Hello world" };

Trang 82

Lập trình Hướng đối tượng

test.MethodWithReferenceType(d);
// Id = 10, Name = Hello world Edited,
// giá trị thay đổi vì d thuộc kiểu reference
Console.WriteLine($"{d.Id}, {d.Name}");
// phương thức này lại không làm thay đổi d
// d vấn giữ giá trị Id = 10, Name = Hello world Edited
test.Method2WithReferenceType(d);
Console.WriteLine($"{d.Id}, {d.Name}");
// Id = 10, Name = Hello world Edited
// như vậy, địa chỉ d trỏ tới không đổi
test.Method1WithRefKeyword(ref a);
Console.WriteLine(a); // a = 10, đã thay đổi, vì từ khóa ref
Data d2 = new Data { Id = 1, Name = "SV He thong Thong tin" };
test.Method2WithRefKeyword(ref d, ref d2);
// d chỉ thay đổi giá trị, giống trường hợp trên
Console.WriteLine($"{d.Id}, {d.Name}");
// Id = 10, Name = Hello world Edited
// d2 trỏ sang object khác
Console.WriteLine($"{d2.Id}, {d2.Name}");
// Id = 100, Name = Donald Trump
// như vậy, từ khóa ref cho phép kiểu tham chiếu
// thay đổi cả địa chỉ vùng nhớ trỏ tới
Console.ReadKey();
}
}
}

Qua ví dụ trên chúng ta thấy từ khóa ref giúp chúng ta truyền tham số thuộc kiểu
value nhưng lại có thể giữ lại những thay đổi đã thực hiện trong phương thức.

Đối với kiểu reference, nếu trong thân phương thức chỉ thay đổi giá trị các thành viên
của biến tham số thì những thay đổi này sẽ được lưu lại sau khi kết thúc phương thức.

Tuy nhiên, nếu trong thân phương thức chúng ta thay đổi địa chỉ biến đó trỏ tới (ví
dụ, bằng lệnh khởi tạo object mới) thì sự thay đổi này lại không được lưu giữ. Lý do là vì
địa chỉ của một vùng nhớ cũng có thể xem là một dạng biến value (thực chất địa chỉ thuộc
kiểu số nguyên), do đó không thể thay đổi.

Trang 83

Trung tâm Tin học – Ngoại ngữ

Khi sử dụng từ khóa ref với kiểu reference, chúng ta có thể đổi cả địa chỉ mà biến
đó trỏ tới.

3.2.6. Tham số out

Tham số out là gì?

Qua các phần trên bạn có thể thấy, mỗi phương thức có thể nhận một danh sách biến
hình thức cung cấp thông tin đầu vào cho phương thức. Các biến này sẽ nhận giá trị thực
khi gọi phương thức. Đây là cách sử dụng mặc định của tham số. Tham số này cũng được
gọi làm tham số vào.

Kết quả thực hiện của phương thức được trả về thông qua lời gọi phương thức. Bình
thường phương thức chỉ có thể trả về một giá trị thông qua lời gọi phương thức.

Có trường hợp ta muốn nhận nhiều hơn một giá trị từ việc thực hiện phương thức.
Hãy cùng thử một phương thức đặc biệt: TryParse. TryParse là một phương thức gặp trong
hầu hết các struct cơ bản như int, bool. Nó có nhiệm vụ biến đổi chuỗi về kiểu dữ liệu
tương ứng.

Phương thức TryParse sẽ thử chuyển đổi chuỗi input sang kiểu đích. Nếu chuỗi hợp
lệ và chuyển đổi thành công phương thức sẽ trả về giá trị true; nếu bị lỗi, phương thức sẽ
trả về giá trị false. Giá trị số nguyên kết quả của việc biến đổi này sẽ được gán cho biến i
kiểu int. Như vậy, tham số thứ hai của TryParse giờ không cung cấp dữ liệu đầu vào, mà
trở thành nơi chứa dữ liệu đầu ra của phương thức.

Việc gọi phương thức TryParse như vậy giúp người lập trình kiểm tra được kết quả
thực hiện mà không bị lỗi dừng chương trình. Nó hoạt động tốt hơn nhiều so với phương
thức Parse với cùng chức năng.

C# cung cấp một tính năng đặc biệt gọi là tham số ra (out parameter): nếu trước một
tham số trong định nghĩa phương thức đặt từ khóa out, tham số đó có thể giữ lại giá trị nó

Trang 84

Lập trình Hướng đối tượng

có được trong quá trình thực hiện phương thức, và qua đó có thể dùng để chứa kết quả thực

hiện của các lệnh trong thân phương thức.

Khai báo phương thức với tham số out

Cùng xem xét ví dụ sau đây để hiểu rõ hơn về cách định nghĩa và sử dụng của tham

số out:

using System;
namespace LTHDT
{

internal class Program
{

/// <summary>
/// Thực hiện 3 phép toán trong cùng một phương thức
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="sum">tổng (tham số ra)</param>
/// <param name="product">tích (tham số ra)</param>
/// <param name="div">thương (tham số ra)</param>
/// <returns>true nếu b != 0, false nếu b == 0
/// (không thực hiện được phép chia)</returns>
private static bool DoMath(int a, int b, out int sum, out int
product, out float div)
{

sum = a + b;
product = a * b;
if (b == 0)
{

div = float.NaN;
return false;
}
div = a / b;
return true;
}
private static void Main(string[] args)
{
int sum, product;
float div;
// người dùng nhập a, b từ bàn phím

Trang 85

Trung tâm Tin học – Ngoại ngữ

// và biến đổi kiểu thành int
int a = int.Parse(Console.ReadLine());
int b = int.Parse(Console.ReadLine());
// gọi phương thức DoMath,
// kết quả hiển thị phụ thuộc giá trị thu được
// khi gọi phương thức
bool result = DoMath(a, b, out sum, out product, out div);
Console.WriteLine($"Sum = {sum}");
Console.WriteLine($"Product = {product}");
if (result == true)
{

// nếu phép chia không có lỗi thì in kết quả
Console.WriteLine($"Division = {div}");
}
else
{
// nếu phép chia có lỗi thì báo "chia cho 0"
Console.WriteLine("Division by zero!!!!!!");
}
Console.ReadKey();
}
}
}

Khi sử dụng tham số ra có một số vấn đề sau cần lưu ý:

• Tham số nào được xác định là tham số ra thì trước khi gọi phương thức phải khai
báo biến tương ứng. Biến này sẽ được truyền vào cho phương thức và sẽ lưu lại
kết quả sau khi phương thức thực hiện xong.

• Phải dùng từ khóa out cho cả giai đoạn định nghĩa và giai đoạn gọi phương thức.
• Tham số ra bắt buộc phải được gán giá trị trong thân phương thức.
• Biến được khai báo là tham số ra sẽ không bắt buộc phải gán giá trị trước.

C# cho phép khai báo và truyền tham số out trực tiếp trong lời gọi phương thức. Trong
ví dụ trên, từ C# bạn có thể thực hiện lời gọi sau đây:

bool result = DoMath(a, b, out int sum, out int product, out float div);

Lời gọi phương thức này sẽ thực hiện khai báo luôn biến sum, product và div. Sau
khi kết thúc phương thức DoMath, các biến này sẽ nhận được giá trị từ thân phương thức.

Trang 86

Lập trình Hướng đối tượng

Bạn không cần khai báo riêng rẽ các biến này trước khi gọi phương thức. Thay đổi này
giúp việc gọi các phương thức có tham số out đơn giản hơn rất nhiều.

Qua ví dụ trên chúng ta cũng lưu ý, nếu trong khai báo phương thức sử dụng từ
khóa ref trước tham số nào thì khi gọi phương thức cũng phải sử dụng từ khóa ref trước
tham số tương ứng.

3.2.7. Tham số tùy chọn

Trước hết hãy cùng thực hiện một ví dụ.

using System;
namespace LTHDT
{

internal class ConsoleHelper
{

/// <summary>
/// Xuất thông tin ra console với màu sắc (WriteLine có màu)
/// </summary>
/// <param name="message"></param>
/// <param name="bgColor"></param>
/// <param name="fgColor"></param>
/// <param name="resetColor"></param>
public void WriteLine(object message, ConsoleColor bgColor =
ConsoleColor.Black, ConsoleColor fgColor = ConsoleColor.White,
bool resetColor = true)
{

Console.ForegroundColor = fgColor;
Console.BackgroundColor = bgColor;
Console.WriteLine(message);
if (resetColor)

Console.ResetColor();
}
}

internal class Program
{

private static void Main(string[] args)
{

Console.Title = "Optional parameters";
var helper = new ConsoleHelper();

Trang 87

Trung tâm Tin học – Ngoại ngữ

helper.WriteLine("Hello world from C#");
helper.WriteLine("Hello world from C#", ConsoleColor.Cyan);
helper.WriteLine("Hello world from C#", ConsoleColor.Cyan,
ConsoleColor.Magenta);

Console.ReadKey();
}
}
}

Trong ví dụ trên trên chúng ta xây dựng phương thức WriteLine với danh sách tham
số có chút khác biệt với những phương thức bình thường. Trong hai phương thức này, tham
số bgColor, grColor và resetColor được gán sẵn giá trị: bgColor =
ConsoleColor.Black, fgColor = White và resetColor = true.

Tính năng này của C# được gọi là tham số với giá trị mặc định hoặc tham số không
bắt buộc hoặc tham số tùy chọn (Optional Arguments / Parameters).

Tham số tùy chọn là loại tham số đã được gán sẵn giá trị mặc định khi định nghĩa
phương thức, và do đó khi gọi phương thức có thể bỏ qua việc truyền tham số này.

Tham số tùy chọn không có gì khác biệt với tham số bình thường khi sử dụng trong
thân phương thức. Tuy nhiên, C# bắt buộc các tham số tùy chọn phải nằm cuối cùng trong
danh sách tham số.

Khi gọi phương thức, nếu không cần truyền giá trị khác với giá trị mặc định, có thể
bỏ qua tham số tùy chọn này .

Các phương thức Write và WriteLine ở trên mặc dù được định nghĩa với 3 tham số
vào nhưng hai tham số sau là tham số tùy chọn: môt tham số nhận màu sắc mặc định là
White; một tham số nhận giá trị mặc định là true.

Do đó, khi gọi các phương thức này trong Main, chúng ta chỉ cung cấp giá trị cho
tham số color (do chúng ta muốn viết ra chữ màu Magenta, khác với màu White mặc định)

Trang 88

Lập trình Hướng đối tượng

nhưng không cung cấp giá trị cho tham số thứ 3 (vì vẫn muốn dùng giá trị true, vốn là giá
trị có sẵn của tham số tùy chọn này).

3.2.8. Tham số params

Bình thường, danh sách tham số của phương thức là cố định. Nó có nghĩa là, nếu bạn
khai báo phương thức với, giả sử, 3 tham số, khi gọi phương thức, bạn phải cung cấp đúng
3 tham số theo đúng thứ tự về kiểu.

Giờ hãy nghĩ một tình huống khác. Bạn cần viết một phương thức để cộng các số.
Nếu bạn cần cộng một số lượng không giới hạn số thì phải làm sao? Rõ ràng, cách thức sử
dụng danh sách tham số bình thường không làm được việc này.

C# cung cấp khả năng viết phương thức mà có thể tiếp nhận số lượng không hạn chế
tham số. Hãy cùng xem một ví dụ.

Tạo project mới trong solution. Viết code cho Program.cs như sau:

using static System.Console;
namespace LTHDT
{

internal class Math
{

public double Sum(params double[] operands)
{

var sum = 0.0;
foreach (var o in operands)
{

sum += o;
}
return sum;
}

public double Product(params double[] operands)
{

var product = 1.0;
foreach (var o in operands)
{

product *= o;

Trang 89

Trung tâm Tin học – Ngoại ngữ

}
return product;
}
}

class Message
{

public string Greeting(params string[] message)
{

var greeting = string.Join(" ", message);
return greeting;
}
}

internal class Program
{

private static void Main(string[] args)
{

Title = "params";

var math = new Math();
var sum1 = math.Sum(1, 2, 3, 4, 5, 6);
var sum2 = math.Sum(1, 2, 3);
var product1 = math.Product(7, 8, 9, 10);
var product2 = math.Product(4, 5, 6);
WriteLine($"Sum(1, 2, 3, 4, 5) = {sum1}");
WriteLine($"Sum(1, 2, 3) = {sum2}");
WriteLine($"Product(4, 5, 6) = {product2}");

var msg = new Message();
var greeting1 = msg.Greeting("Hello", "world", "from", "C#");
var greeting2 = msg.Greeting("Hi", "this", "is", "params",
"method");
WriteLine(greeting1);
WriteLine(greeting2);

ReadKey();
}
}
}

Trong ví dụ này, bạn đã tạo một class Math với hai phương thức Sum và Product,
class Message với phương thức Greeting.

Trang 90

Lập trình Hướng đối tượng

Điều đặc biệt của các phương thức này nằm ở danh sách tham số. Tất cả chúng đều
có cùng cú pháp:

(params <type>[] <name>)

Ví dụ:

double Sum(params double[] operands)
double Product(params double[] operands)
string Greeting(params string[] messages)

Đây là cách khai báo của loại tham số đặc biệt: tham số params. Cách khai báo tham
số này cho phép bạn cung cấp số lượng không hạn chế tham số (thực) cùng loại khi gọi
phương thức:

var sum1 = math.Sum(1, 2, 3, 4, 5, 6);
var sum2 = math.Sum(1, 2, 3);
var product1 = math.Product(7, 8, 9, 10);
var product2 = math.Product(4, 5, 6);
var greeting1 = msg.Greeting("Hello", "world", "from", "C#");
var greeting2 = msg.Greeting("Hi", "this", "is", "params",
"method");

Trong thân phương thức, bạn có thể sử dụng loại tham số này như một mảng một
chiều.

foreach (var o in operands)
{

sum += o;
}

3.3. Nạp chồng phương thức (method overloading) trong C#
3.3.1. Nạp chồng phương thức trong C# là gì?

Nạp chồng phương thức (method overloading) là hiện tượng trong một class có thể
tồn tại nhiều phương thức trùng tên.

Nạp chồng phương thức cùng với nạp chồng toán tử (operator overloading) thuộc về
nguyên lý đa hình tĩnh (static polymorphism).

Trang 91

Trung tâm Tin học – Ngoại ngữ

Ví dụ phương thức WriteLine của lớp Console có 19 overload khác nhau:

Mỗi overload này nhận một danh sách tham số khác nhau. Intellisense của Visual
studio hiển thị tất cả các overload của một phương thức vào một danh sách như trên. Bạn
có thể dùng phím mũi tên Up và Down để duyệt qua danh sách này. Ứng với mỗi overload
sẽ cung cấp thông tin chi tiết riêng.

Khi gặp hiện tượng nạp chồng phương thức, trình biên dịch của C# sẽ căn cứ vào
danh sách tham số thực của lời gọi hàm để quyết định xem người lập trình đang muốn gọi
phương thức nào.

Vì vậy, các phương thức nạp chồng bắt buộc phải khác nhau về danh sách tham số.
Nói một cách chính xác hơn, các phương thức nạp chồng trong C# bắt buộc phải khác nhau
về signature. Ngược lại, C# compiler sẽ báo lỗi định nghĩa phương thức trùng nhau.

Tiếp theo đây bạn sẽ biết signature của phương thức là gì.

3.3.2. Signature của phương thức trong C#

Signature của phương thức trong C# bao gồm các thông tin sau:
• Tên của phương thức;
• Số lượng tham số;
• Kiểu và trật tự của các tham số;
• Các từ khóa điều khiển cho tham số (out, ref, in).
Kiểu dữ liệu trả về không thuộc về signature của phương thức. Tên của tham số hình
thức cũng không được tính vào signature của phương thức. Rất nhiều bạn nhầm lẫn hai vấn
đề này.

Trang 92

Lập trình Hướng đối tượng

C# bắt buộc trong cùng một class không được phép có hai phương thức trùng nhau
về signature.

Trong hiện tượng nạp chồng, tên của phương thức trùng nhau, do đó ít nhất 1 trong 3
yếu tố còn lại phải khác nhau. Cả ba yếu tố này đều liên quan đến danh sách tham số. Nói
cách khác, các phương thức nạp chồng phải có danh sách tham số khác nhau.

Ví dụ: (string s, int i, bool b) và (string str, int ii, bool
bb) là hai danh sách tham số giống nhau:

• Cả hai đều có 3 tham số;
• Thứ tự tham số tính theo kiểu đều là (string, int, bool);
• Tên các tham số không quan trọng.
Nói tóm lại, bạn được phép khai báo các phương thức nạp chồng (trùng tên) nhưng 3
yếu tố còn lại của signature phải khác nhau. Điều này có nghĩa là các phương thức nạp
chồng (trùng tên) phải thỏa mãn ít nhất một trong số các điều kiện:
• Số lượng tham số khác nhau;
• Thứ tự tham số tính theo kiểu (không phải tính theo tên) khác nhau;
• Sử dụng modifier khác nhau.

Trang 93

Trung tâm Tin học – Ngoại ngữ

3.4. Một số vấn đề khác của phương thức trong C#
3.4.1. Phương thức với Expression body

Nếu thân phương thức chỉ có một lệnh duy nhất, C# cho phép viết phương thức đó ở
dạng đơn giản hóa, gọi là expression body. Cách viết expression body loại bỏ cặp dấu {}
và lệnh return (nếu có) ở thân phương thức. Expression body sử dụng toán tử => để ghép
tên và thân phương thức.

Hãy cùng xem ví dụ:

public bool IsSquare(Rectangle rect) => rect.Height == rect.Width;

Đây là cách viết ở dạng expression body. Cách viết “truyền thống” của phương thức
trên là:

public bool IsSquare(Rectangle rect)
{

return rect.Height == rect.Width;
}

Như vậy, nếu thân của phương thức (thông thường) chứa đúng 1 lệnh return, bạn chỉ
cần viết biểu thức tính giá trị đó và ghép với tên phương thức qua dấu =>. C# sẽ tự hiểu
cần trả lại giá trị của biểu thức cho lời gọi phương thức.

Nếu thân phương thức là một lệnh không trả về giá trị (kiểu trả về là void), bạn cũng
chỉ cần viết đúng lệnh đó và ghép với tên phương thức bằng dấu =>.

3.4.2. Named Arguments

Ở trên bạn đã biết cách gọi phương thức bằng cách cung cấp danh sách giá trị theo
đúng thứ tự về kiểu:

// khai báo
public void MoveAndResize(int x, int y, int width, int height) { ...
}

// gọi phương thức
r.MoveAndResize(30, 40, 20, 40);

Trang 94

Lập trình Hướng đối tượng

C# cho phép gọi phương thức theo một cách khác:

r.MoveAndResize(x: 30, y: 40, width: 20, height: 40);

Cách gọi phương thức này có tên là named arguments. Trong cách gọi này, mỗi
tham số được truyền bằng cách viết tên tham số (đặt khi khai báo phương thức), dấu hai
chấm và giá trị. Cách gọi này không cần quan tâm về thứ tự viết tham số.

Cách gọi này rất hữu ích nếu phương thức có nhiều tham số hoặc khi sử dụng kết hợp
với tham số tùy chọn.

3.5. Properties
3.5.1. Property trong C# là gì?

Trong bài học biến thành viên bạn đã biết rằng có thể sử dụng biến public để lưu trữ
và truy xuất dữ liệu cho class. Tuy nhiên, class xây dựng với biến thành viên public có một
số nhược điểm:

• Người sử dụng class có thể trực tiếp truy xuất giá trị của các trường. Việc trực tiếp
truy xuất này là khó chấp nhận nếu chúng ta cần giới hạn một phần việc truy xuất
thông tin. Ví dụ, có trường thông tin chúng ta chỉ muốn cho đọc mà không cho ghi
giá trị.

• Giá trị nhập vào không được kiểm soát hoặc biến đổi phù hợp. Ví dụ, năm không
thể nhận giá trị âm.

Trong lập trình hướng đối tượng nên hạn chế tối đa việc sử dụng biến thành viên
public.

Để giải quyết vấn đề này, người lập trình thường xây dựng một cặp phương thức để
gán/lấy giá trị cho mỗi biến thành viên. Cặp phương thức như vậy thường được gọi là
setter/getter. Phương pháp này rất phổ biến khi lập trình PHP hay Java.

Tuy nhiên, cách viết như vậy làm code trở nên dài dòng.

Trang 95

Trung tâm Tin học – Ngoại ngữ

C# cung cấp một công cụ đặc biệt để giải quyết vấn đề này, giúp viết code class đơn
giản hơn, an toàn hơn, cũng như tiện lợi hơn về sau (khi khởi tạo object của class): property
(đặc tính).

Property là một loại thành viên đặc biệt có vai trò lai giữa biến thành viên (lưu trữ và
truy xuất dữ liệu) nhưng cho phép kiểm soát dữ liệu (gán vào hoặc xuất ra) giống như
phương thức, cũng như cho phép kiểm soát riêng rẽ từng chiều truy xuất. Property được sử
dụng đặc biệt phổ biến trong class C#.

Thực tế, property trong C# chỉ là một dạng viết tắt và viết gộp (syntactic sugar) của
hai phương thức get và set.

Ví dụ:

namespace PropertyWithBackedField
{

using static System.Console;

/// <summary>
/// sách điện tử
/// </summary>
internal class Book
{

private int _id = 1;
private string _authors = "Unknown author";
private string _title = "A new book";
private string _publisher = "Unknown publisher";
private int _year = 2018;
private string _description;

public int Id
{
get { return _id; }
protected set {
_id = value;
}
}
/// <summary>
/// tên tác giả/ nhóm tác giả

Trang 96

Lập trình Hướng đối tượng

/// </summary>
public string Authors
{

get { return _authors; }
set {

_authors = value;
}
}
/// <summary>
/// tiêu đề
/// </summary>
public string Title
{
get { return _title; }
set {

_title = value;
}
}
/// <summary>
/// nhà xuất bản
/// </summary>
public string Publisher
{

get { return _publisher; }
set {

_publisher = value;
}
}
/// <summary>
/// năm xuất bản
/// </summary>
public int Year
{
get { return _year; }
set {

_year = value;
}
}
/// <summary>
/// thông tin mô tả
/// </summary>
public string Description
{

Trang 97

Trung tâm Tin học – Ngoại ngữ

get { return _description; }
set {

_description = value;
}
}
}

internal class Program
{

private static void Main(string[] args)
{

var book = new Book();

// lệnh này lỗi, vì setter của Id là protected
// chỉ có thể gán giá trị cho Id từ trong class
// không thể gán giá trị từ ngoài class
//book.Id = 2;

book.Authors = "Christian Nagel";
book.Title = "Professional C# 7 and .NET Core";
book.Publisher = "Wrox";
book.Year = 2018;
book.Description = "The best book ever";

WriteLine($"{book.Authors}, {book.Title}, - {book.Publisher},
{book.Year}");

ReadKey();
}
}
}

Trong class Book bạn đã khai báo (và khởi tạo) một loạt biến thành viên private (_id,
_authors, _title, v.v.). Tiếp theo bạn lại viết một số cấu trúc lạ như

public string Authors
{

get { return _authors; }
set {

_authors = value;
}
}

Trang 98

Lập trình Hướng đối tượng

Đây chính là cách khai báo property trong C#. Property này có tên là Authors. Như
đã nói, property trong C# thực chất chỉ là một dạng viết tắt của hai phương thức get và set.
Phương thức get dùng để trả lại giá trị của biến _authors; Phương thức set dùng để gán giá
trị cho biến _authors. Hai phương thức này hoạt động không có gì khác biệt phương thức
thông thường, ngoại trừ chúng không có danh sách tham số.

Biến _authors có tên gọi là backed field cho property Authors. Đó là nơi lưu dữ liệu
thực sự. Còn property Authors đóng vai trò kiểm soát xuất/nhập cho biến backed field này.

Từ khóa value có vai trò đặc biệt. Trong client code, phép gán

var book = new Book();
book.Authors = "Christian Nagel";

Giá trị “Christian Nagel” cung cấp vào cho property Authors thông qua từ khóa value.
Nói cách khác, bạn có thể xem value là tham số đầu vào của phương thức get.

Bạn cũng nhìn thấy trong phương thức Main, việc sử dụng các property không khác
biệt gì so với sử dụng biến thành viên public.

book.Authors = "Christian Nagel";
book.Title = "Professional C# 7 and .NET Core";
book.Publisher = "Wrox";
book.Year = 2018;
book.Description = "The best book ever";

Riêng đối với Id có chút khác biệt. Do setter của nó đặt là protected, bạn không gán
giá trị cho nó được. Đây là một property chỉ đọc (đối với client code).

3.5.2. Auto property trong C#

Nếu không cần kiểm soát giá trị xuất/nhập, bạn có thể thu gọn code bằng cách sử
dụng auto-property. Auto-property là loại property trong đó trường backed field được
compiler sinh tự động. Bạn không biết tên của backed field khi viết code, do đó cũng không
trực tiếp sử dụng được nó.

Trang 99

Trung tâm Tin học – Ngoại ngữ

Cấu trúc khai báo auto-property (kết hợp gán giá trị đầu) trong C# như sau:

[public|protected|private] <tên-kiểu> <tên-thuộc-tính>
{

[public|protected|private] get;
[public|protected|private] set;
} [= <giá-trị>];

Trong đó, tên property được đặt theo quy tắc đặt định danh và quy ước giống như
biến thành viên public (sử dụng PascalCase).

Hai phương thức get và set được gọi chung là accessor, đôi khi cũng được gọi là
getter và setter. Getter hoặc setter có thể sử dụng từ khóa điều khiển truy cập của riêng
mình, giúp property đó biến thành loại:

• Chỉ đọc (read-only): public get, protected/private set;
• Chỉ gán (assign-only): protected/private get, public set;
• Truy cập tự do (full access): public get, public set (mặc định).

Modifier mặc định cho getter và setter là “public”, do đó cấu trúc khai báo ngắn gọn
nhất là:

public <tên-kiểu> <tên-thuộc-tính> { get; set; }

Ví dụ:

namespace AutoProperty
{
using static System.Console;

/// <summary>
/// sách điện tử
/// </summary>
class Book
{
public int Id { get; protected set; } = 1;

/// <summary>
/// tên tác giả/ nhóm tác giả
/// </summary>
public string Authors { get; set; } = "Unknown author";

Trang 100


Click to View FlipBook Version