You are on page 1of 183

UBND TỈNH BÌNH ĐỊNH

TRƢỜNG CAO ĐẲNG NGHỀ QUY NHƠN

GIÁO TRÌNH
Mô đun: LẬP TRÌNH C#.NET
NGHỀ: CÔNG NGHỆ THÔNG TIN
(ỨNG DỤNG PHẦN MỀM)
TRÌNH ĐỘ CAO ĐẲNG NGHỀ

Bình Định, năm 2014


UBND TỈNH BÌNH ĐỊNH
TRƢỜNG CAO ĐẲNG NGHỀ QUY NHƠN

GIÁO TRÌNH
Mô đun: LẬP TRÌNH C#.NET
NGHỀ: CÔNG NGHỆ THÔNG TIN
(ỨNG DỤNG PHẦN MỀM)
TRÌNH ĐỘ CAO ĐẲNG NGHỀ
Ban hành kèm theo Quyết định số: /QĐ-CĐN ngày ..... tháng.... năm 2014

của Hiệu trưởng trường Cao đẳng nghề Quy Nhơn

Bình Định, năm 2014


TUYÊN BỐ BẢN QUYỀN
Giáo trình này đƣợc biên soạn bởi giáo viên Nguyễn Lê Ngọc Thành, khoa Công
Nghệ Thông Tin trƣờng Cao đẳng nghề Quy Nhơn, sử dụng cho việc tham khảo và
làm tài liệu giảng dạy nghề Công Nghệ Thông Tin tại trƣờng Cao đẳng nghề Quy
Nhơn. Mọi hình thức sao chép, in ấn và đƣa lên mạng Internet không đƣợc sự cho
phép của Hiệu trƣởng trƣờng Cao đẳng nghề Quy Nhơn là vi phạm pháp luật.
MỤC LỤC
LỜI GIỚI THIỆU .......................................................................................................1
BÀI 1 GIỚI THIỆU MICROSOFT .NET ...................................................................5

Mã bài: MĐ33-01 ........................................................................................................5

1.1 Nguồn gốc .NET ...............................................................................................6

1.2 Tổng quan Microsoft .NET ................................................................................6

1.3 Kiến trúc .NET Framework ...............................................................................7

BÀI 2 CƠ BẢN NGÔN NGỮ C# .............................................................................10

Mã bài: MĐ33-02 ......................................................................................................10

2.1 Giới thiệu về ngôn ngữ C# ..............................................................................10

2.1.1. C# là ngôn ngữ đơn giản ...........................................................................11

2.1.2. C# là ngôn ngữ hiện đại ............................................................................11

2.1.3. C# là ngôn ngữ hƣớng đối tƣợng .............................................................11

2.1.4. C# là ngôn ngữ mạnh mẽ và mềm dẻo .....................................................12

2.1.5. C# là ngôn ngữ hƣớng module .................................................................12

2.1.6. C# sẽ trở nên phổ biến ..............................................................................12

2.2 Kiểu dữ liệu ......................................................................................................12

2.2.1. Kiểu dữ liệu dựng sẵn ...............................................................................12

2.2.2. Chuyển đổi kiểu dữ liệu ............................................................................14

2.3 Biến, hằng, toán tử ...........................................................................................15

2.3.1. Biến ...........................................................................................................15

2.3.2. Hằng ..........................................................................................................16

2.3.3. Toán tử ......................................................................................................17

2.4 Lệnh, khối lệnh, Chú thích ...............................................................................18

2.5 Namespace .......................................................................................................18

2.6 Cách tạo chƣơng trình trên C# .........................................................................19


2.6.1 Tạo và thực thi chƣơng trình bằng ngôn ngữ C# với Console Application
.............................................................................................................................19

2.6.2 Tạo và thực thi chƣơng trình C# với Windows Form Application ..........21

2.7 Biên dịch & chạy chƣơng trình ........................................................................22

BÀI 3 CẤU TRÚC ĐIỀU KHIỂN – CẤU TRÚC LẶP ...........................................24

Mã bài: MĐ33-03 ......................................................................................................24

3.1 Cấu trúc điều khiển ..........................................................................................24

3.1.1. Câu lệnh if … else.....................................................................................24

3.1.2. Câu lệnh if lồng nhau ................................................................................25

3.1.3. Câu lệnh switch ........................................................................................26

3.2 Cấu trúc lặp ......................................................................................................27

3.2.1. Lệnh lặp while...........................................................................................27

3.2.2. Lệnh lặp do … while.................................................................................28

3.2.3. Lệnh lặp for ...............................................................................................28

3.2.4. Lệnh lặp foreach .......................................................................................29

a. Cú pháp: ..........................................................................................................29

BÀI 4 MẢNG (ARRAY) – CHUỖI (STRING)- XỬ LÝ NGOẠI LỆ


(EXCEPTION) ..........................................................................................................31

Mã bài: MĐ33-04 ......................................................................................................31

4.1. Mảng 1 chiều ...................................................................................................31

4.1.1. Định nghĩa .................................................................................................31

4.1.2. Khai báo mảng: .........................................................................................32

4.1.3. Khởi tạo thành phần của mảng .................................................................32

4.1.4. Giá trị mặc định: .......................................................................................32

4.1.5. Truy cập các thành phần trong mảng:.......................................................32

4.1.6. Duyệt mảng 1 chiều: .................................................................................32


4.2 Mảng nhiều chiều .............................................................................................36

4.2.1. Định nghĩa .................................................................................................36

4.2.2 Khai báo mảng 2 chiều <kiểu dữ liệu>[ , ] <tên mảng>............................36

4.2.3. Khởi tạo thành phần của mảng .................................................................36

4.2.4. Duyệt mảng 2 chiều ..................................................................................37

4.3 String (Chuỗi)...................................................................................................38

4.3.1. Tạo một chuỗi ...........................................................................................38

4.3.2. Thao tác trên chuỗi....................................................................................39

4.4 Exception (Ngoại lệ) ........................................................................................47

4.4.1. Khái niệm ..................................................................................................47

4.4.2. Ví dụ .........................................................................................................47

BÀI 5 LỚP (CLASS) – ĐỐI TƢỢNG (OBJECT)- PHƢƠNG THỨC


(METHOD) ...............................................................................................................50

Mã bài: MĐ33-05 ......................................................................................................50

5.1 Lớp và đối tƣợng ..............................................................................................51

5.1.1. Khái niệm ..................................................................................................51

5.1.2 Tạo và sử dụng đối tƣợng lớp (class) ........................................................51

5.1.2.2. Tạo đối tƣợng .........................................................................................52

5.1.2.3. Sử dụng đối tƣợng.................................................................................52

5.2 Properties - Method ..........................................................................................58

5.2.1. Thuộc tính (Properties): ............................................................................58

5.2.2 Thuộc tính truy cập ....................................................................................58

5.2.3 Phƣơng thức (Method) ...............................................................................58

5.2.4. Tham số của phƣơng thức .........................................................................59

5.3 Phƣơng thức tạo lập (constructor) của một đối tƣợng .....................................62

5.4 Phƣơng thức tạo lập sao chép (copy constructor) ............................................63
5.5 Sử dụng các thành viên tĩnh (static member)...................................................64

5.6 Đóng gói dữ liệu với thuộc tính (property) .....................................................66

5.7 Toán tử .............................................................................................................70

BÀI 6 KẾ THỪA VÀ ĐA HÌNH ..............................................................................77

Mã bài: MĐ33-06 ......................................................................................................77

6.1 Sự kế thừa.........................................................................................................77

6.1.1. Quan hệ chuyên biệt hóa và tổng quát hóa ...............................................77

6.1.2. Khái niệm kế thừa ....................................................................................78

6.1.3. Thực thi kế thừa ........................................................................................78

6.1.4. Gọi phƣơng thức tạo lập của lớp cơ sở .....................................................81

6.1.5. Định nghĩa phiên bản mới trong lớp dẫn xuất ..........................................81

6.1.6. Điều khiển truy xuất..................................................................................82

6.2. Đa hình ............................................................................................................83

6.2.1 Kiểu đa hình ...............................................................................................83

6.2.2 Lớp Object .................................................................................................84

6.3 Lớp trừu tƣợng(abstract) ..................................................................................84

BÀI 7 THỰC THI GIAO DIỆN................................................................................90

Mã bài: MĐ33-07 ......................................................................................................90

7.1 Thực thi giao diện (Interface) ..........................................................................90

7.1.1. Thực thi nhiều giao diện ...........................................................................96

7.1.2. Mở rộng giao diện ...................................................................................100

7.1.3. Kết hợp giao diện ....................................................................................100

7.2 Kiểm tra đối tƣợng có hỗ trợ giao diện hay không bằng toán tử is ...............101

7.3 Thực thi các giao diện tƣờng minh: Icomparer, IComparable (giao diện so
sánh) và ArrayList ................................................................................................101

BÀI 8 CƠ CHẾ ỦY QUYỀN - SỰ KIỆN ..............................................................107


Mã bài: MĐ33-08 ....................................................................................................107

8.1 Ủy quyền (delegate) .......................................................................................108

8.1.1 Sử dụng ủy quyền để xác nhận phƣơng thức lúc thực thi .......................108

8.1.2 Uỷ quyền tĩnh ...........................................................................................119

8.1.3 Sử dụng ủy quyền nhƣ thuộc tính ............................................................120

8.1.4 Thiết lập thứ tự thi hành với mảng ủy quyền...........................................121

8.2 Sự kiện............................................................................................................130

BÀI 9 CÁC LỚP CƠ SỞ CỦA .NET .....................................................................141

Mã bài: MĐ33-09 ....................................................................................................141

9.1 Lớp đối tƣợng trong .NET Framework ..........................................................141

9.2 Lớp Timer ......................................................................................................143

9.3 Lớp về thƣ mục và hệ thống ..........................................................................146

9.4 Lớp Math ........................................................................................................148

9.5 Lớp thao tác tập tin ........................................................................................151

9.6 Làm việc với tập tin dữ liệu ...........................................................................154

BÀI TẬP MỞ RỘNG VÀ NÂNG CAO ..............................................................165

TÀI LIỆU THAM KHẢO ....................................................................................174


LỜI GIỚI THIỆU
Ngôn ngữ C#.NET là một ngôn ngữ lập trình mới đƣợc sử dụng phổ biến hiện
nay trong ngành công nghiệp lập trình với nền tảng .NET FRAMEWORK. Ngôn
ngữ này đƣợc lựa chọn đào tạo trong chƣơng trình đào tạo nghề Công Nghệ Thông
Tin (ứng dụng phần mềm) thuộc mô đun Lập trình Windows 3(C#.NET). Vì vậy để
đáp ứng nhu cầu có thêm nguồn tài liệu tham khảo để phục vụ công tác giảng dạy
và học tập của giáo viên và sinh viên ngành CNTT (ứng dụng phần mềm), tôi cùng
với một số giáo viên trong khoa đã mạnh dạn đề xuất xây dựng giáo trình Lập trình
ngôn ngữ C#.NET
Giáo trình bao gồm những vấn đề: giới thiệu về .NET FRAMEWORK của
Microsoft; những đặc trƣng cơ bản của ngôn ngữ lập trình C#.NET nhƣ từ khoá,
kiểu dữ liệu, cách khai báo biến,hằng, biểu thức trong ngôn ngữ C#.NET;các cấu
trúc điều khiển,các kiểu tổ chức dữ liệu nhƣ mảng-array, chuỗi-string trong ngôn
ngữ C#; lập trình hƣớng đối tƣợng trong C# với các khái niệm nhƣ tính đa hình,
đóng gói dữ liệu và kế thừa và kỹ năng xây dựng lớp, đối tƣợng, phƣơng thức; kỹ
năng tạo và sử dụng các lớp kế thừa đa hình; kỹ năng sử dụng cơ chế uỷ quyền và
sự kiện; Cách sử dụng các lớp cơ sở .NET. Giáo trình chia thành chín bài với nội
dung nhƣ sau:
• Bài 1: Giới thiệu về Microsoft .NET
• Bài 2: Cơ bản ngôn ngữ C#
• Bài 3: Các cấu trúc điều khiển trong ngôn ngữ C#
• Bài 4: Mảng(array) – Chuỗi(string) –Ngoại lệ (Exception)
• Bài 5: Lớp (Class) – Đối tƣợng (Object) -Phƣơng thức ( Method)
• Bài 6: Kế thừa và Đa hình
• Bài 7: Thực thi giao diện
• Bài 8: Cơ chế uỷ quyền và sƣ kiện
• Bài 9: Các lớp cơ sở .NET
• Ngoài chín bài trên, còn có phần bài tập nâng cao và mở rộng và phần hƣớng
dẫn giải các bài tập này ở cuối giáo trình liên quan đến những nội dung đã đề cập
trong giáo trình để bạn đọc tiện tham khảo đào sâu những nội dung đã đƣợc tìm
hiểu trong giáo trình.
Chúng tôi hi vọng rằng giáo trình này sẽ thực sự có ích đối với bạn đọc. Chúng
tôi rất mong nhận đƣợc những ý kiến đóng góp của các bạn.
Bình Định, ngày 15 tháng 8 năm 2014
1
Tham gia biên soạn

1. Tên chủ biên: Nguyễn Lê Ngọc Thanh

2. Tên thành viên 1: Trần Quốc Hùng

2
MÔ ĐUN LẬP TRÌNH C#.NET
Mã mô đun: MĐ 33
Vị trí, tính chất, ý nghĩa và vai trò của mô đun:
-Lập trình trên Windows là mô đun tự chọn thuộc chuyên môn nghề của
chƣơng trình đào tạo Cao đẳng nghề (ứng dụng phần mềm). Mô đun này đƣợc bố trí
giảng sau các môn cơ sở ngành.
-Là mô đun lập trình cơ sở trên nền lập trình .NET FRAMEWORK, để làm
nền tảng cho việc tiếp cận lập trình các ứng dụng .NET và lập trình ASP.NET.
Mục tiêu của mô đun:
 Trình bày đƣợc các kiến thức về nền tảng Microsoft .NET.
 Vận dụng đƣợc các kỹ thuật lập trình cơ bản trong ngôn ngữ C#
 Có kiến thức và kỹ năng xử lý mảng, chuỗi;
 Vận dụng các kiến thức và kỹ năng về lập trình hƣớng đối tƣợng trên C#.
 Có kiến thức và kỹ năng về giao diện trong C#.
 Có kiến thức và kỹ năng về cơ chế uỷ quyền;
 Tạo đƣợc các ứng dụng trên windows sử dụng ngôn ngữ C# trên môi trƣờng
.Net;
 Nghiêm túc, tỉ mỉ trong việc tiếp nhận kiến thức. Chủ động, tích cực trong thực
hành và tìm kiếm nguồn bài tập liên quan.
Nội dung của mô đun:
Thời gian (giờ)
Số
Tên chƣơng, mục Tổng Lý Thực Kiểm
TT
số thuyết hành tra
1 Bài 1: Giới thiệu về Microsoft .NET 2 2 0 0
2 Bài 2: Cơ bản ngôn ngữ C# 6 3 3 0
Bài 3: Các cấu trúc điều khiển trong
3 12 3 8 1
ngôn ngữ C#
Bài 4: Mảng (Array) – Chuỗi (String)
4 18 6 11 1
– Xử lý ngoại lệ (Exception)
Bài 5: Lớp (Class) – Đối tƣợng
5 18 6 11 1
(Object) -Phƣơng thức (Method)

3
6 Bài 6: Kế thừa và Đa hình 14 3 9 1
7 Bài 7: Thực thi giao diện 6 3 3 0
8 Bài 8: Cơ chế uỷ quyền và sƣ kiện 5 2 3 0
9 Bài 9: Các lớp cơ sở .NET 9 2 5 0
10 Cộng 90 30 56 4

4
BÀI 1
GIỚI THIỆU MICROSOFT .NET
Mã bài: MĐ33-01
Giới thiệu:
Microsoft .Net là một công nghệ lập trình mới đƣợc thực thi trên nền .NET
Framework của hãng Microsoft.
Thành phần Framework là quan trọng nhất .NET là cốt lõi và tinh hoa của môi
trƣờng lập trình trên thƣ viện CLR, còn IDE chỉ là công cụ để phát triển dựa trên
nền tảng đó thôi. Trong .NET toàn bộ các ngôn ngữ C#, Visual C++ hay Visual
Basic.NET đều dùng cùng một IDE.
Tóm lại Microsoft .NET là nền tảng cho việc xây dựng và thực thi các ứng dụng
phân tán thế hệ kế tiếp. Bao gồm các ứng dụng từ client đến server và các dịch vụ
khác. Một số tính năng của Microsoft .NET cho phép những nhà phát triển sử dụng
nhƣ sau:
□ Một mô hình lập trình cho phép nhà phát triển xây dựng các ứng dụng dịch
vụ web và ứng dụng client với Extensible Markup Language (XML).
□ Tập hợp dịch vụ XML Web, nhƣ Microsoft .NET My Services cho phép nhà
phát triển đơn giản và tích hợp ngƣời dùng kinh nghiệm.
□ Cung cấp các server phục vụ bao gồm: Windows 2000, SQL Server, và
BizTalk Server, tất cả điều tích hợp, hoạt động, và quản lý các dịch vụ XML
Web và các ứng dụng.
□ Các phần mềm client nhƣ Windows XP và Windows CE giúp ngƣời phát
triển phân phối sâu và thuyết phục ngƣời dùng kinh nghiệm thông qua các
dòng thiết bị.
□ Nhiều công cụ hỗ trợ nhƣ Visual Studio .NET, để phát triển các dịch vụ Web
XML, ứng dụng trên nền Windows hay nền web một cách dể dàng và hiệu
quả.
Mục tiêu:
 Trình bày đƣợc các kiến thức nền tảng Microsoft.Net;
 Sử dụng cơ chế trình biên dịch MSIL;
 Thao tác đƣợc trên môi trƣờng lập trình C#.Net;
 Nghiêm túc, tỉ mỉ trong quá trình tiếp cận với môi trƣờng lập trình mới.
Nội dung chính:
5
1.1 Nguồn gốc .NET
Đầu năm 1998, sau khi hoàn tất phiên bản Version 4 của Internet Information
Server (IIS), các đội ngũ lập trình ở Microsoft nhận thấy họ còn rất nhiều sáng kiến
để kiện toàn IIS. Họ bắt đầu xây dựng một kiến trúc mới trên nền tảng ý tƣởng đó
và đặt tên là Next Generation Windows Services (NGWS).
Sau khi Visual Basic đƣợc trình làng vào cuối 1998, dự án kế tiếp mang tên
Visual Studio 7 đƣợc xác nhập vào NGWS. Đội ngũ COM+/MTS góp vào một
universal runtime cho tất cả ngôn ngữ lập trình chung trong Visual Studio, và tham
vọng của họ cung cấp cho các ngôn ngữ lập trình của các công ty khác dùng chung
luôn. Công việc này đƣợc xúc tiến một cách hoàn toàn bí mật mãi cho đến hội nghị
Professional Developers‟ Conference ở Orlado vào tháng 7/2000. Đến tháng
11/2000 thì Microsoft đã phát hành bản Beta 1 của .NET gồm 3 đĩa CD. Tính đến
lúc này thì Microsoft đã làm việc với .NET gần 3 năm rồi, do đó bản Beta 1 này
tƣơng đối vững chắc.
.NET mang dáng dấp của những sáng kiến đã đƣợc áp dụng trƣớc đây nhƣ p-
code trong UCSD Pascal cho đến Java Virtual Machine. Có điều là Microsoft góp
nhặt những sáng kiến của ngƣời khác, kết hợp với sáng kiến của chính mình để làm
nên một sản phẩm hoàn chỉnh từ bên trong lẫn bên ngoài. Hiện tại Microsoft đã
công bố phiên bản release của .NET.
Thật sự Microsoft đã đặt cƣợc vào .NET vì theo thông tin của công ty, đã tập
trung 80% sức mạnh của Microsoft để nghiên cứu và triển khai .NET (bao gồm
nhân lực và tài chính ?), tất cả các sản phẩm của Microsoft sẽ đƣợc chuyển qua
.NET.
1.2 Tổng quan Microsoft .NET
Microsoft .NET gồm 2 phần chính : Framework và Integrated Development
Environment (IDE). Framework cung cấp những gì cần thiết và căn bản, chữ
Framework có nghĩa là khung hay khung cảnh trong đó ta dùng những hạ tầng cơ
sở theo một qui ƣớc nhất định để công việc đƣợc trôi chảy. IDE thì cung cấp một
môi trƣờng giúp chúng ta triển khai dễ dàng, và nhanh chóng các ứng dụng dựa trên
nền tảng .NET. Nếu không có IDE chúng ta cũng có thể dùng một trình soạn thảo
ví nhƣ Notepad hay bất cứ trình soạn thảo văn bản nào và sử dụng command line để
biên dịch và thực thi, tuy nhiên việc này mất nhiều thời gian. Tốt nhất là chúng ta
dùng IDE phát triển các ứng dụng, và cũng là cách dễ sử dụng nhất.
Thành phần Framework là quan trọng nhất .NET là cốt lõi và tinh hoa của môi
trƣờng, còn IDE chỉ là công cụ để phát triển dựa trên nền tảng đó thôi. Trong .NET
toàn bộ các ngôn ngữ C#, Visual C++ hay Visual Basic.NET đều dùng cùng một
IDE.
6
Tóm lại Microsoft .NET là nền tảng cho việc xây dựng và thực thi các ứng dụng
phân tán thế hệ kế tiếp. Bao gồm các ứng dụng từ client đến server và các dịch vụ
khác. Một số tính năng của Microsoft .NET cho phép những nhà phát triển sử dụng
nhƣ sau:
□ Một mô hình lập trình cho phép nhà phát triển xây dựng các ứng dụng dịch
vụ web và ứng dụng client với Extensible Markup Language (XML).
□ Tập hợp dịch vụ XML Web, nhƣ Microsoft .NET My Services cho phép nhà
phát triển đơn giản và tích hợp ngƣời dùng kinh nghiệm.

□ Cung cấp các server phục vụ bao gồm: Windows 2000, SQL Server, và
BizTalk Server, tất cả điều tích hợp, hoạt động, và quản lý các dịch vụ XML
Web và các ứng dụng.
□ Các phần mềm client nhƣ Windows XP và Windows CE giúp ngƣời phát
triển phân phối sâu và thuyết phục ngƣời dùng kinh nghiệm thông qua các
dòng thiết bị.
□ Nhiều công cụ hỗ trợ nhƣ Visual Studio .NET, để phát triển các dịch vụ Web
XML, ứng dụng trên nền Windows hay nền web một cách dể dàng và hiệu
quả.
1.3 Kiến trúc .NET Framework
.NET Framework là một platform mới làm đơn giản việc phát triển ứng dụng
trong môi trƣờng phân tán của Internet. .NET Framework đƣợc thiết kế đầy đủ để
đáp ứng theo quan điểm sau:
□ Để cung cấp một môi trƣờng lập trình hƣớng đối tƣợng vững chắc, trong đó
mã nguồn đối tƣợng đƣợc lƣu trữ và thực thi một cách cục bộ. Thực thi cục
bộ nhƣng đƣợc phân tán trên Internet, hoặc thực thi từ xa.
□ Để cung cấp một môi trƣờng thực thi mã nguồn mà tối thiểu đƣợc việc đóng
gói phần mềm và sự tranh chấp về phiên bản.
□ Để cung cấp một môi trƣờng thực thi mã nguồn mà đảm bảo việc thực thi an
toàn mã nguồn, bao gồm cả việc mã nguồn đƣợc tạo bởi hãng thứ ba hay bất
cứ hãng nào mà tuân thủ theo kiến trúc .NET.
□ Để cung cấp một môi trƣờng thực thi mã nguồn mà loại bỏ đƣợc những lỗi
thực hiện các script hay môi trƣờng thông dịch.
□ Để làm cho những ngƣời phát triển có kinh nghiệm vững chắc có thể nắm
vững nhiều kiểu ứng dụng khác nhau. Nhƣ là từ những ứng dụng trên nền
Windows đến những ứng dụng dựa trên web.
7
□ Để xây dựng tất cả các thông tin dựa triên tiêu chuẩn công nghiệp để đảm
bảo rằng mã nguồn trên .NET có thể tích hợp với bất cứ mã nguồn khác.
.NET Framework có hai thành phần chính: Common Language Runtime (CLR) và
thƣ viện lớp .NET Framework. CLR là nền tảng của .NET Framework. Chúng ta có
thể hiểu runtime nhƣ là một agent quản lý mã nguồn khi nó đƣợc thực thi, cung cấp
các dịch vụ cốt lõi nhƣ: quản lý bộ nhớ, quản lý tiểu trình, và quản lý từ xa. Ngoài
ra nó còn thúc đẩy việc sử dụng kiểu an toàn và các hình thức khác của việc chính
xác mã nguồn, đảm bảo cho việc thực hiện đƣợc bảo mật và mạnh mẽ. Thật vậy,
khái niệm quản lý mã nguồn là nguyên lý nền tảng của runtime. Mã nguồn mà đích
tới runtime thì đƣợc biết nhƣ là mã nguồn đƣợc quản lý (managed code). Trong khi
đó mã nguồn mà không có đích tới runtime thì đƣợc biết nhƣ mã nguồn không đƣợc
quản lý (unmanaged code).
Thƣ viện lớp, một thành phần chính khác của .NET Framework là một tập
hợp hƣớng đối tƣợng của các kiểu dữ liệu đƣợc dùng lại, nó cho phép chúng ta có
thể phát triển những ứng dụng từ những ứng dụng truyền thống command-line hay
những ứng dụng có giao diện đồ họa (GUI) đến những ứng dụng mới nhất đƣợc
cung cấp bởi ASP.NET, nhƣ là Web Form và dịch vụ XML Web.

Hình 1.1 Kiến trúc .NET FRAMEWORK

8
Tập hợp các ngôn ngữ : C#, VB.Net, J#, F#, VC++…
 Công cụ phát triển Visual Studio
 Lớp đặc tả ngôn ngữ dùng chung (CLS)
 Các thƣ viện để phát triển ứng dụng
 Bộ thực thi ngôn ngữ dùng chung (CLR)
Chƣơng trình không biên dịch thành tập tin thực thi, mà biên dịch thành ngôn
ngữ trung gian IL (Intermediate Language-IL), sau đó đƣợc CLR (Common
Language Runtime) thực thi.

9
BÀI 2
CƠ BẢN NGÔN NGỮ C#
Mã bài: MĐ33-02
Giới thiệu:
Khi tìm hiểu bất kỳ ngôn ngữ nào ta cũng cần hiểu các đặc trƣng của ngôn
ngữ đó nhƣ bảng chữ cái, từ, cấu tạo câu... Ví dụ khi học về ngôn ngữ tiếng Anh ta
cần biết bảng chữ cái, các từ và cấu trúc câu trong tiếng Anh nhƣ thế nào? Với ngôn
ngữ lập trình nói chung cũng vậy và ngôn ngữ lập trình C#.NET nói riêng ta cũng
cần tìm hiểu những đặc trƣng trên ngôn ngữ này là gì? Để có thể làm việc tốt với
ngôn ngữ C# ta cần tìm hiểu từ khoá, kiểu dữ liệu, cấu trúc điều khiển trong ngôn
ngữ này đƣợc khai báo và sử dụng nhƣ thế nào.
Ngôn ngữ C# khá đơn giản, chỉ khoảng hơn 80 từ khóa và hơn mƣời mấy
kiểu dữ liệu đƣợc dựng sẵn. Tuy nhiên, ngôn ngữ C# có ý nghĩa to lớn khi nó thực
thi những khái niệm lập trình hiện đại. C# bao gồm tất cả những hỗ trợ cho cấu trúc,
thành phần component, lập trình hƣớng đối tƣợng. Những tính chất đó hiện diện
trong một ngôn ngữ lập trình hiện đại. Hơn nữa ngôn ngữ C# đƣợc xây dựng trên
nền tảng hai ngôn ngữ mạnh nhất là C++ và Java.

Mục tiêu:
 Trình bày đƣợc các kiến thức và chức năng tiên tiến trên C#;
 Vận dụng đƣợc các kiểu dữ liệu dựng sẵn của C#;
 Vận dụng đƣợc các cơ chế thực thi các biến, hằng và các biểu thức trên C#;
 Vận dụng đƣợc các cấu trúc điều khiển trong ngôn ngữ C# để giải các bài toán
theo yêu cầu đặt ra.
 Trình bày đƣợc kiến thức về không gian tên (Namespace);
 Trình bày đƣợc kiến thức về các toán tử;
 Trình bày đƣợc kiến thức về chỉ dẫn biên dịch;
 Tạo và thực thi đƣợc ứng dụng đơn giản trên C#;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
2.1 Giới thiệu về ngôn ngữ C#
Ngôn ngữ C# khá đơn giản, chỉ khoảng hơn 80 từ khóa và hơn mƣời mấy kiểu
10
dữ liệu đƣợc dựng sẵn. Tuy nhiên, ngôn ngữ C# có ý nghĩa to lớn khi nó thực thi
những khái niệm lập trình hiện đại. C# bao gồm tất cả những hỗ trợ cho cấu trúc,
thành phần component, lập trình hƣớng đối tƣợng. Những tính chất đó hiện diện
trong một ngôn ngữ lập trình hiện đại. Hơn nữa ngôn ngữ C# đƣợc xây dựng trên
nền tảng hai ngôn ngữ mạnh nhất là C++ và Java.
Tóm lại, C# có các đặc trƣng sau đây:
 C# là ngôn ngữ đơn giản
 C# là ngôn ngữ hiện đại
 C# là ngôn ngữ hƣớng đối tƣợng
 C# là ngôn ngữ mạnh mẽ và mềm dẻo
 C# là ngôn ngữ hƣớng module
 Hoạt động trên .NET Framework.
 Dựa trên phƣơng pháp thiết kế hƣớng đối tƣợng.
 Dùng cho cả 3 loại ứng dụng: Console, Winform, Webform.
 Có tính diễn đạt ngữ nghĩa cao.
 Phân biệt chữ hoa thƣờng.
2.1.1. C# là ngôn ngữ đơn giản
- C# loại bỏ đƣợc một vài sự phức tạp và rối rắm của các ngôn ngữ C++ và
Java.
- C# khá giống C / C++ về diện mạo, cú pháp, biểu thức, toán tử.
- Các chức năng của C# đƣợc lấy trực tiếp từ ngôn ngữ C / C++ nhƣng đƣợc
cải tiến để làm cho ngôn ngữ đơn giản hơn.
2.1.2. C# là ngôn ngữ hiện đại
C# có đƣợc những đặc tính của ngôn ngữ hiện đại nhƣ:
- Xử lý ngoại lệ
- Thu gom bộ nhớ tự động
- Có những kiểu dữ liệu mở rộng - Bảo mật mã nguồn
2.1.3. C# là ngôn ngữ hƣớng đối tƣợng
C# hỗ trợ tất cả những đặc tính của ngôn ngữ hƣớng đối tƣợng là: - Sự đóng
gói (encapsulation)
- Sự kế thừa (inheritance) - Đa hình (polymorphism)
11
2.1.4. C# là ngôn ngữ mạnh mẽ và mềm dẻo
- Với ngôn ngữ C#, chúng ta chỉ bị giới hạn ở chính bản thân của chúng ta.
Ngôn ngữ này không đặt ra những ràng buộc lên những việc có thể làm.
- C# đƣợc sử dụng cho nhiều dự án khác nhau nhƣ: tạo ra ứng dụng xử lý văn
bản, ứng dụng đồ họa, xử lý bảng tính; thậm chí tạo ra những trình biên dịch cho
các ngôn ngữ khác.
- C# là ngôn ngữ sử dụng giới hạn những từ khóa. Phần lớn các từ khóa dùng
để mô tả thông tin, nhƣng không gì thế mà C# kém phần mạnh mẽ. Chúng ta có
thể tìm thấy rằng ngôn ngữ này có thể đƣợc sử dụng để làm bất cứ nhiệm vụ nào.
2.1.5. C# là ngôn ngữ hƣớng module
- Mã nguồn của C# đƣợc viết trong Class (lớp). Những Class này chứa các
Method (phƣơng thức) thành viên của nó.
- Class (lớp) và các Method (phƣơng thức) thành viên của nó có thể đƣợc sử
dụng lại trong những ứng dụng hay chƣơng trình khác.
2.1.6. C# sẽ trở nên phổ biến
- C# mang đến sức mạnh của C++ cùng với sự dễ dàng của ngôn ngữ Visual
Basic.
2.2 Kiểu dữ liệu
C# chia kiểu dữ liệu thành hai tập hợp kiểu dữ liệu chính:
- Kiểu xây dựng sẵn (built-in): do ngôn ngữ cung cấp cho ngƣời lập trình. -
Kiểu do ngƣời dùng định nghĩa (user-defined): do ngƣời lập trình tạo ra.
2.2.1. Kiểu dữ liệu dựng sẵn
Kiểu số
Kiểu C# Số byte Kiểu .NET Mô tả
byte 1 Byte Số nguyên dƣơng không dấu từ 0 đên
char 2 Char Ký tự Unicode 255
bool 1 Boolean Giá trị logic true / false
sbyte 1 Sbyte Số nguyên có dấu từ -128 đên 127
short 2 Int16 Số nguyên có dấu từ -32768 đên 32767
ushort 2 Uint16 Số nguyên dƣơng không dấu từ 0 đên
int 4 Int32 Số nguyên có dấu65535
từ -2.147.483.647 đên
uint 4 Uint32 Số nguyên không dấu từ 0 đên
2.147483.647
float 4 Single 4.294.967.295
Kiểu dấu chấm động, giá trị xấp xỉ từ -
12 đên 3.4E+38, với 7 chữ số có
3.4E-38
nghĩa
double 8 Double Kiểu dấu chấm động có độ chính xác
decimal 8 Decimal gấp
Có độđôi, giá xác
chính trị xấp
đênxỉ28từcon số và giáđên
-1.7E-308 trị
long 8 Int64 1.7E+308,
thập
Kiểu phân, với
đƣợc
số nguyên 15,dùng
có16dấu
chữ số giá
trong
có có nghĩa
tính toán
trị tài
trong
ulong 8 Uint64 chính,
khoảng kiểu
Số nguyên này đòi hỏi phảitừcó 0hậuđên
-9.223.370.036.854.775.808
không dấu tố
đên
“m” hay “M”
9.223.372.036.854.775.807
0xfffffffffffffff
Kiểu chuỗi
 C# định nghĩa kiểu string nhƣ một kiểu dữ liệu cơ bản (khác với C, C++)
 Kiểu string có thể chứa nội dung không giới hạn, vì đây là kiểu dữ liệu đối
tƣợng đƣợc chứa ở bộ nhớ heap.
 Khai báo : string s = “Nguyen van a”;
Kiểu mảng
 Mảng là một tập hợp các phần tử cùng một kiểu dữ liệu và đƣợc truy xuất
thông qua chỉ số.
 Chỉ số bắt đầu từ 0.
 Khai báo :
Mảng một chiều
<Kiểu dữ liệu> [ ] <Tên mảng> = new <Kiểu dữ liệu> [Số phần tử];
Mảng hai chiều
<Kiểu dữ liệu> [ , ] <Tên mảng> = new <Kiểu dữ liệu> [Số dòng, số
cột];
Kiểu Enum
Là một cách thức để đặt tên cho các trị nguyên, làm cho chƣơng trình rõ
ràng, dễ hiểu hơn.
Ví dụ 2.1:
enum Ngay {Hai, Ba, Tu, Nam, Sau,Bay, CN};
 Hai= 0; Ba= 1; … ; CN= 6
Ví dụ 2.2:
enum Ngay {Hai=1,Ba,Tu,Nam,Sau,Bay, CN};
 Hai=1; Ba=2; … ; CN=7
Kiểu struct

13
Struct dùng để nhóm các dữ liệu cùng liên quan đến một đối tƣợng nào
đó.
Khai báo :
struct <Tên cấu trúc>
{
Danh sách các thuộc tính;
}
Ví dụ 2.1: Khai báo một struct SV nhƣ sau
struct SV
{
public string ten;
public string maso;
}
* Bảng trình bày các ký tự đặc biệt

Ký tự Ý nghĩa
\' Dấu nháy đơn
\" Dấu nháy kép
\\ Dấu chéo
\0 Ký tự null
\a Alert
\b Backspace
\f Sang trang form feed
\n Dòng mới
\r Đầu dòng
\t Tab ngang
\v Tab dọc
2.2.2. Chuyển đổi kiểu dữ liệu
Ví dụ a:
short x = 10 ;
int y = x ; // chuyển đổi ngầm định

14
Ví dụ b:
short x ;
int y = 100 ;
x = (short) y; // ép kiểu tƣờng minh, trình biên dịch không báo lỗi
Ví dụ c:
short x ;
int y = 100 ;
x = y ; // không biên dịch, lỗi
2.3 Biến, hằng, toán tử
2.3.1. Biến
a) Khái niệm:
- Biến là một vùng lƣu trữ ứng với một kiểu dữ liệu.
- Biến có thể đƣợc gán giá trị và cũng có thể thay đổi giá trị trong khi thực
hiện các lệnh của chƣơng trình.
b) Khai báo biến: Sau khi khai báo biến phải gán giá trị cho biến
<Kiểu_Dữ_Liệu> <tên_biến> [ = <giá_trị> ] ;
c) Ví dụ 2.1: Khởi tạo và gán giá trị một biến
class Bien
{
static void Main()
{
// Khai bao va khoi tao bien
int bien = 9 ;
System.Console.WriteLine("Sau khi khoi tao: bien = {0}",
bien)
// Gan; gia tri cho bien
bien = 5 ;
// Xuat ra man hinh
System.Console.WriteLine("Sau khi gan: bien = {0}", bien) ;
}
}

15
2.3.2. Hằng
a) Khái niệm:
- Hằng cũng là một biến nhƣng giá trị của hằng không thay đổi trong khi thực
hiện các lệnh của chƣơng trình.
- Hằng đƣợc phân làm 3 loại:
+ Giá trị hằng (literal)
+ Biểu tƣợng hằng (symbolic constants)
+ Kiểu liệt kê (enumerations)
b) Giá trị hằng:
Ví dụ: x = 100; // 100 đƣợc gọi là giá trị hằng
c) Biểu tƣợng hằng: gán một tên hằng cho một giá trị hằng. Khai báo:
<const> <Kiểu_Dữ_Liệu> <tên_hằng> = <giá_trị> ;
Ví dụ 1.2: Nhập vào bán kính, in ra chu vi và diện tích hình tròn.
class HinhTron
{
static void Main()
{
// Khai bao bieu tuong hang
const double PI = 3.14159 ;
// Khai bao bien
xv int bankinh ;
double chuvi , dientich ;
string chuoi ;
// Nhap gia tri cho bien chuoi
System.Console.Write("Nhap ban kinh hinh tron: ") ;
chuoi = System.Console.ReadLine() ;
// Doi chuoi thanh so va gan vao bien so
bankinh = System.Convert.ToInt32(chuoi) ;
// Gan gia tri cho bien
chuvi = 2 * bankinh * PI ;
dientich = bankinh * bankinh * PI;
// Xuat ra man hinh
16
System.Console.WriteLine("Chu vi hinh tron = {0:0.00}",
chuvi) ;
System.Console.WriteLine("Dien tich hinh tron = {0:0.00}",
} dientich) ;

2.3.3.}Toán tử
2.3.3.1. Toán tử số học

Ký hiệu Ý nghĩa Ghi chú

+ Cộng

- Trừ

* Nhân

/ Chia Đối với số chia và bị chia là số nguyên thì cho


kết quả là phần nguyên

% Chia lấy phần Chỉ áp dụng cho số chia và bị chia là số nguyên


++x; Tăng x lên 1


x++ đơn vị

--x; x-- Giảm x xuống


1 đơn vị

2.3.3.2 Toán tử quan hệ và logic:

Ký hiệu Ý nghĩa
> Lớn hơn
>= Lớn hơn hoặc bằng
< Nhỏ hơn
<= Nhỏ hơn hoặc bằng
== Bằng
!= Khác
17
&& Và
|| Hoặc
! Phủ định
2.4 Lệnh, khối lệnh, Chú thích
 Câu lệnh thực hiện một chức năng nào đó(gán, xuất, nhập, …) và kết thúc
bằng dấu chấm phẩy (;)
 Khối lệnh gồm nhiều lệnh và đƣợc đặt trong cặp dấu ngoặc nhọn { }
 Chú thích:
 // Chú thích một dòng
 /* Chú thích nhiều dòng */

2.5 Namespace
Nhƣ chúng ta đã biết .NET cung cấp một thƣ viện các lớp đồ sộ và thƣ viện này
có tên là FCL (Framework Class Library). Trong đó Console chỉ là một lớp nhỏ
trong hàng ngàn lớp trong thƣ viện. Mỗi lớp có một tên riêng, vì vậy FCL có hàng
ngàn tên nhƣ ArrayList, Dictionary, FileSelector,...
Điều này làm nảy sinh vấn đề, ngƣời lập trình không thể nào nhớ hết đƣợc tên của
các lớp trong .NET Framework. Tệ hơn nữa là sau này có thể ta tạo lại một lớp
trùng với lớp đã có chẳng hạn. Ví dụ trong quá trình phát triển một ứng dụng ta cần
xây dựng một lớp từ điển và lấy tên là Dictionary, và điều này dẫn đến sự tranh
chấp khi biên dịch vì C# chỉ cho phép một tên duy nhất.
Chắc chắn rằng khi đó chúng ta phải đổi tên của lớp từ điển mà ta vừa tạo thành
một cái tên khác chẳng hạn nhƣ myDictionary. Khi đó sẽ làm cho việc phát triển
các ứng dụng trở nên phức tạp, cồng kềnh. Đến một sự phát triển nhất định nào đó
thì chính là cơn ác mộng cho nhà phát triển.
Giải pháp để giải quyết vấn đề này là việc tạo ra một namespace, namsespace sẽ
hạn chế phạm vi của một tên, làm cho tên này chỉ có ý nghĩa trong vùng đã định
nghĩa.
Giả sử có một ngƣời nói Tùng là một kỹ sƣ, từ kỹ sƣ phải đi kèm với một lĩnh
vực nhất định nào đó, vì nếu không thì chúng ta sẽ không biết đƣợc là anh ta là kỹ
sƣ cầu đƣờng, cơ khí hay phần mềm. Khi đó một lập trình viên C# sẽ bảo rằng
Tùng là CauDuong.KySu phân biệt với CoKhi.KySu hay PhanMem.KySu.
Namespace trong trƣờng hợp này là CauDuong, CoKhi, PhanMem sẽ hạn chế phạm

18
vi của những từ theo sau. Nó tạo ra một vùng không gian để tên sau đó có nghĩa.
Tƣơng tự nhƣ vậy ta cứ tạo các namespace để phân thành các vùng cho các lớp
trùng tên không tranh chấp với nhau.
 Namespace là khái niệm đƣợc sử dụng để phân nhóm các lớp đối tƣợng trong
.Net Framework, tránh cho hai lớp đối tƣợng có cùng tên.
 Ví dụ:
System.Drawing2D.Pen và System.Drawing3D.Pen
đều đề cập đến một lớp đối tƣợng Pen nhƣng thuộc hai namespace khác nhau, do đó
chúng là hai lớp đối tƣợng khác nhau.
2.6 Cách tạo chƣơng trình trên C#
2.6.1 Tạo và thực thi chƣơng trình bằng ngôn ngữ C# với Console Application
Sử dụng Microsoft Visual Studio 2010
Khởi tạo một Project

Bƣớc 1: Khởi động Visual Studio 2010


Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual
Studio 2010
Bƣớc 2: Vào menu File | New | Project
Bƣớc 3: Khai báo

19
 Chọn loại ứng dụng Console Application hoặc Windows Application
 Đặt tên cho dự án
 Chọn nơi lƣu trữ
Bƣớc 4: Thiết kế Form – Viết code
· Thiết kế form: Nhắp vào View Designer (trong cửa số Solution
Explorer)
· Viết code: Nhắp vào View Code (trong cửa số Solution Explorer)
Bƣớc 5: Để chạy chƣơng trình, nhấn F5 hoặc nhắp vào nút
Để dừng chƣơng trình, nhấn Shift + F5 hoặc nhắp vào nút
* Các thao tác với Project / Solution
a. Tạo Project
C1. Vào menu File | New | Project
C2. Ctrl + Shift + N
C3. Chọn công cụ New Project trên thanh Standart
b. Mở Project / Solution:
C1. Vào menu File | Open | Project / Solution
C2. Ctrl + Shift + O
c. Lƣu Project / Solution
C1. Vào menu File | Save All
C2. Chọn công cụ Save All trên thanh Standart
d. Đóng Solution: Vào menu File | Close Solution
Giao diện

20
File Program.cs là file mặc định chứa hàm Main của chƣơng trình
Cấu trúc một Project:
//khai báo thƣ viện sử dụng không gian tên
using System;
namespace ConsoleApplication1
{
class Program //tên lớp, tên file = tên lớp
{
//hàm xử lý chính
static void Main(string[] args)
{
//Chƣơng trình chính viết tại đây
}
}
}

2.6.2 Tạo và thực thi chƣơng trình C# với Windows Form Application
a. Cửa sổ thiết kế Form (Designer):

21
b. Cửa sổ thiết viết code:

2.7 Biên dịch & chạy chƣơng trình


Trình biên dịch (compiler) sẽ biên dịch các tập tin chứa ngôn ngữ C# (file
.cs) thành file thực thi.exe
Có 2 cách biên dịch :
- Tại cửa sổ cmd, gõ : csc.exe tenfile.cs
- Nhấn Build/Compile(hoặc Build / Build Solution) Có 2 cách chạy chƣơng
trình
- Chạy file tenfile.exe trong thƣ mục Bin\Debug
- Hoặc click Debug/Start (Ctrl + F5)

22
Bài tập
1. Viết chƣơng trình nhập vào 1 số nguyên n. Cho biết: a) n là số chẵn hay số lẻ ?
b) n là số âm hay số không âm ?
2. Viết chƣơng trình nhập vào 2 số thực dƣơng chỉ chiều dài và chiều rộng của
hình chữ nhật. In ra màn hình chu vi và diện tích của hình chữ nhật đó.

3. Viết chƣơng trình nhập vào một số thực dƣơng chỉ cạnh của một hình vuông.
Tính diện tích và chu vi của hình vuông đó.
4. Viết chƣơng trình nhập vào họ tên (HoTen), điểm toán (Toan), điểm lý (Ly),
điểm hoá (Hoa) của một học sinh. In ra màn hình họ tên của học sinh dƣới dạng
chữ IN HOA và điểm trung bình (Dtb) của học sinh này theo công thức: Dtb =
(Toan + Ly + Hoa) / 3
5. Viết chƣơng trình nhập bậc lƣơng (BacLuong), ngày công (NgayCong), phụ cấp
(PhuCap). Tính tiền lãnh (TienLanh) = BacLuong * 650000 * NCTL + PhuCap
Với: NCTL = NgayCong nếu NgayCong < 25
= (NgayCong – 25) * 2 + 25 nếu NgayCong >= 25
--- oOo ---

23
BÀI 3
CẤU TRÚC ĐIỀU KHIỂN – CẤU TRÚC LẶP
Mã bài: MĐ33-03
Giới thiệu:
Để có thể làm việc đƣợc với một ngôn ngữ lập trình ngoài việc tìm hiểu các
từ khoá, kiểu dữ liệu, cách khai báo biến, hằng, biểu thức…trên ngôn ngữ lập trình
thì việc hiểu và vận dụng tốt các cấu trúc điều khiển trên ngôn ngữ lập trình đó là
một vấn đề không thể thiếu khi học một ngôn ngữ lập trình. Bài học này sẽ trình
bày các cấu trúc điều khiển cơ bản trong ngôn ngữ C#
Mục tiêu:
 Trình bày cú pháp của các cấu trúc điều khiển và nguyên tắc hoạt động của
những cấu trúc này trên trình biên dịch của ngôn ngữ C#;
 Vận dụng đƣợc các cấu trúc điều khiển trong ngôn ngữ C# để giải các bài toán
theo yêu cầu đặt ra;
 Phân tích đƣợc với bài toán nào thì sử dụng cấu trúc điều khiển nào cho phù
hợp;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
3.1 Cấu trúc điều khiển
3.1.1. Câu lệnh if … else
a. Cú pháp:
if (Điều_Kiện)
<Khối lệnh Điều_Kiện đúng>
[else
<Khối lệnh Điều_Kiện sai>]
c. Ví dụ 3.1: Dùng câu lệnh điều kiện if … else
d.
using System;
class Chan_Le
{
static void Main()

24
{
// Khai bao va khoi tao bien
int bienDem = 9 ;
// Xuat ra man hinh
if (bienDem % 2 == 0)
Console.WriteLine("{0} la so chan", bienDem) ;
else Console.WriteLine("{0} la so le", bienDem) ;
}

3.1.2. Câu
} lệnh if lồng nhau
a. Cú pháp:
if (Điều_Kiện_1) <Khối lệnh 1>
else if (Điều_Kiện_2) <Khối lệnh 2.1>
else
<Khối lệnh 2.2>
b. Ví dụ 3.2:
using System;
class Thu_Trong_Tuan
{
static void Main()
{
// Khai bao va khoi tao bien
int thu = 5 ; // 0: Chu nhat, 1: Thu hai, 2: Thu ba, 3: Thu tu,
// 4: Thu nam, 5: Thu sau, 6: Thu bay
// Xuat ra man hinh
if ((thu == 1) || (thu == 3) || (thu == 5))
Console.WriteLine("Day la ngay 2-4-6") ;
else if ((thu == 2) || (thu == 4) || (thu == 6))
Console.WriteLine("Day la ngay 3-5-7") ;
else Console.WriteLine("Day la ngay chu nhat") ;
}
}

25
3.1.3. Câu lệnh switch
a. Cú pháp:
switch (Biểu_Thức) {
case <giá_trị_1>: < Khối lệnh 1> <Lệnh Nhảy>
case <giá_trị_2>: < Khối lệnh 2> <Lệnh Nhảy>
…. [default:
< Khối lệnh khác>] }
b. Ví dụ 3.3:
using System;
class Thu
{
static void Main()
{
// Khai bao va khoi tao bien
int thu = 5 ; // 0: Chu nhat, 1: Thu hai, 2: Thu ba, 3: Thu tu,
// 4: Thu nam, 5: Thu sau, 6: Thu bay
// Xuat ra man hinh
switch (thu)
{
case 0:
Console.WriteLine("Chu nhat") ;
break;
case 1:
Console.WriteLine("Thu hai") ;
break;
case 2:
Console.WriteLine("Thu ba") ;
break;
case 3:
Console.WriteLine("Thu tu") ;
break;
case 4:

26
Console.WriteLine("Thu nam") ;
break;
case 5:
Console.WriteLine("Thu sau") ;
break;
case 6:
Console.WriteLine("Thu bay") ;
break;
default:
Console.WriteLine("Khong phai la thu trong
tuan")
break; ;
}
}
}
3.2 Cấu trúc lặp
3.2.1. Lệnh lặp while
a. Cú pháp:
while (Điều_Kiện) < Khối lệnh>
b. Ví dụ 3.4: using
System; class
UsingWhile
{
static void Main()
{
// Khai bao va khoi tao bien dem
int i = 1 ;
// Xuat ra man hinh
while (i<=10) {
Console.WriteLine("i = {0}",i) ;
i++ ; // tang bien dem,
}
}
}

27
3.2.2. Lệnh lặp do … while
a. Cú pháp:
do < Khối lệnh> while (Điều_Kiện) ;
b. Ví dụ 3.5:
using System;
class UsingDoWhile
{
static void Main()
{
// Khai bao va khoi tao bien dem
int i = 1 ;
// Xuat ra man hinh
do {
Console.WriteLine("i = {0}",i) ;
i++ ; // tang bien dem
} while (i<= 10) ;
}

} Lệnh lặp for


3.2.3.
a. Cú pháp:
for ([Khởi_tạo] ; [Điều_kiện] ; [Bƣớc_lặp]) < Khối lệnh>
b. Ví dụ 3.6:
using System;
class UsingFor
{
static void Main()
{
for (int i=1 ; i<=30 ; i++)
if (i % 10 ==0)
Console.Write("{0} \n\r",i) ;
else Console.Write("{0} ",i) ;
}
}
28
3.2.4. Lệnh lặp foreach
a. Cú pháp:
foreach (<Kiểu_tập_hợp> <Tên_truy_cập_thành_phần> in <Tên_tập_hợp>)
< Khối lệnh>
b. Ví dụ 3.7:
using System;
public class UsingForeach
{
public static void Main()
{
int[] intArray = {1,2,3,4,5,6,7,8,9,10};
foreach (int item in intArray)
Console.WriteLine("i = {0} ",item) ;
}

29
Bài tập
1. Viết chƣơng trình nhập vào 3 số nguyên. In ra màn hình số nguyên nhỏ nhất
trong 3 số đó.
2. Viết chƣơng trình nhập vào họ tên, điểm thi cuối kỳ của một học sinh. In ra họ
tên học sinh bằng chữ IN HOA, và kết quả xếp loại của học sinh theo tiêu
chuẩn sau:
- Giỏi: Nếu Điểm kết quả >= 8 - Khá: Nếu 8 > Điểm >= 6.5
- Trung bình: Nếu 6.5 > Điểm >= 5 - Yếu: Nếu Điểm < 5
3. Viết chƣơng trình giải phƣơng trình bậc 1: bx + c = 0
4. Viết chƣơng trình giải phƣơng trình bậc 2: ax2 + bx + c = 0
5. Viết chƣơng trình nhập vào một số nguyên cho đến khi nhận đƣợc số nguyên
dƣơng thì dừng.
6. Viết chƣơng trình nhập vào một số nguyên n. Cho biết số nguyên n có phải là số
nguyên tố không ?
7. Viết chƣơng trình nhập vào một số nguyên dƣơng n chỉ năm dƣơng lịch. Cho
biết n có phải là năm nhuận không ?
8. Viết chƣơng trình nhập vào số nguyên dƣơng n. In ra màn hình kết quả của các
tổng sau:
a) S1 = 1 + 2 + 3 + ... + n
1 1 1
b) S 2  1    ... 
2 3 n
9. Viết chƣơng trình nhập vào số nguyên dƣơng n. In ra màn hình: a) Các số
nguyên dƣơng từ 1 đến n
b) Tổng và trung bình cộng của n số nguyên dƣơng này.
--- oOo ---

30
Bài 4:
MẢNG (ARRAY) – CHUỖI (STRING)- XỬ LÝ NGOẠI LỆ (EXCEPTION)
Mã bài: MĐ33-04
Giới thiệu:
Trong quá trình lập trình việc tổ chức dữ liệu cho một bài toán là vấn đề cần
đặt ra khi giải quyết bài toán đó. Đối với những bài toán đơn giản với kiểu dữ liệu
dựng sẵn có thể sử dụng để tổ chức dữ liệu thì ta sử dụng những kiểu dữ liệu dựng
sẵn trong ngôn ngữ C# để giải quyết. Nhƣng đối với những bài toán dữ liệu ở dạng
tập hợp thì kiểu mảng hoặc chuỗi là lựa chọn tối ƣu cho việc tổ chức dữ liệu cho
bài toán. Vậy mảng và chuỗi đƣợc sử dụng trong ngôn ngữ C# nhƣ thế nào?
Mục tiêu:
 Trình bày đƣợc các kiến thức về mảng và danh sách mảng;
 Khai báo mảng, chuỗi đúng cú pháp;
 Vận dụng đƣợc dữ liệu kiểu mảng (array) và chuỗi (string) để tổ chức dữ liệu
cho bài toán;
 Giải quyết đƣợc một số bài tập trên mảng, chỉ mục và tập hợp;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
4.1. Mảng 1 chiều
4.1.1. Định nghĩa
- Mảng là một tập hợp có thứ tự của những đốitượng (objects), tất cả các đối
tƣợng này có cùng một kiểu dữ liệu.
- Mảng trong ngôn ngữ C# sử dụng những phƣơng thức và các thuộc tính. Thể
hiện của mảng trong C# có thể truy cập những phƣơng thức và các thuộc tính của
System.Array.
- Một số các thuộc tính và phƣơng thức của lớp System.Array:
Thành viên Mô tả
Sort() Phƣơng thức sắp xếp giá trị tăng dần trong mảng một chiều
Reverse() Phƣơng thức sắp xếp giá trị giảm dần trong mảng một chiều
Length Thuộc tính chiều dài của mảng
SetValue() Phƣơng thức thiết lập giá trị cho một thành phần xác định
trong mảng

31
4.1.2. Khai báo mảng:
<kiểu dữ liệu>[] <tên mảng> ;
Ví dụ:
int[] myIntArray ;
Tạo thể hiện của mảng: sử dụng từ khóa new Ví dụ:
myIntArray = new int[5] ;
4.1.3. Khởi tạo thành phần của mảng
- Tạo thể hiện của mảng đồng thời với khởi tạo các giá trị:
+ Cách 1:
int[] myIntArray = new int[5] {2, 4, 6, 8, 10};
+ Cách 2:
int[] myIntArray = {2, 4, 6, 8, 10};
- Các khai báo trên sẽ thiết lập bên trong bộ nhớ một mảng chứa 5 số nguyên.
*Chú ý: Không thể thiết lập lại kích thƣớc cho mảng.
4.1.4. Giá trị mặc định:
- Khi chúng ta tạo một mảng có kiểu dữ liệu giá trị, mỗi thành phần sẽ chứa
giá trị mặc định của kiểu dữ liệu.
Ví dụ:
Với khai báo int myIntArray = new int[5] ; thì:
- Mỗi thành phần của mảng đƣợc thiết lập giá trị là 0 (giá trị mặc định của số
nguyên).
- Những kiểu tham chiếu trong một mảng không đƣợc khởi tạo giá trị mặc
định, chúng đƣợc khởi tạo giá trị null.
4.1.5. Truy cập các thành phần trong mảng:
- Để truy cập vào thành phần trong mảng ta sử dụng toán tử chỉ mục ([]). - Chỉ
mục của thành phần đầu tiên trong mảng luôn luôn là 0.
- Thuộc tính Length của mảng cho biết số đối tƣợng trong một mảng.
4.1.6. Duyệt mảng 1 chiều:
for (int i=0; i < myIntArray.Length; i++) { xử lý
myIntArraya[i];
}
32
Có thể thay for bằng foreach nhƣ sau foreach (int phantu in
a){
xử lý myIntArraya[i]; }
Ví dụ 4.1: (Mảng 5 số nguyên từ 1 đến 5)
* Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi Du 5.1
nhƣ sau:

* Yêu cầu
- Thiết kế form nhƣ mẫu (lblKQ, btnIn, btnXoa, btnDung). - Khai báo mảng 1
chiều (a) gồm 5 số nguyên từ 1 đến 5.
- Nhắp vào button In mảng (btnIn) sẽ in ra label (lblKQ) các giá trị trong
mảng. - Nhắp vào button Xóa (btnXoa) sẽ xóa trống nội dung của label
(lblKQ).
- Nhắp vào button Dừng sẽ dừng chƣơng trình. * Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Khai báo mảng: qua code, thêm đoạn code để đƣợc kết quả nhƣ sau:
public partial class Form1 : Form
{
// Khai bao mang 1 chieu gom 5 so nguyen tu 1 den
5 int[] a = { 1, 2, 3, 4, 5 };
}
- Nhắp đúp vào button In mảng, thêm đoạn code sau:
// Xuat cac phan tu trong mang ra man hinh
this.lblKQ.Text="Các phần tử trong mảng là:\n\r"; for
(int i=0; i < a.Length; i++)
{ this.lblKQ.Text += a[i]+" "; }

33
Có thể thay for bằng foreach nhƣ sau
foreach (int phantu in a)
{ this.lblKQ.Text += a[i]+" "; }
- Nhắp đúp vào button Xóa, thêm đoạn code sau:
this.lblKQ.Text = "";
- Nhắp đúp vào button Dừng, thêm đoạn code sau:
Application.Exit();

Ví dụ 4.2: (Mảng 5 số nguyên)


* Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi Du 5.2
nhƣ sau:

* Yêu cầu
- Thiết kế form: btnNhap, txtNhap, btnTang, btnGiam, lblKQ, btnIn, btnXoa,
btnDung. - Khai báo mảng 1 chiều (a) chứa 5 số nguyên, số phần tử hiện có (sopt)
là 0.
- Nhập số vào TextBox txtNhap, nhắp vào button Nhập 1 phần tử mảng
(btnNhap) cho phép đƣa giá trị trong TextBox txtNhap vào mảng. Khi mảng đã đủ
5 số nguyên thì phải thông báo “Mảng đã đầy” và không cho nhập nữa.
- Nhắp vào button Sắp tăng (btnTang) sẽ sắp xếp mảng theo thứ tự tăng dần.
- Nhắp vào button Sắp giảm (btnGiam) sẽ sắp xếp mảng theo thứ tự giảm dần.
- Nhắp vào button In mảng (btnIn) sẽ in ra label (lblKQ) các giá trị trong
mảng.
- Nhắp vào button Xóa (btnXoa) sẽ xóa trống nội dung của label (lblKQ) đồng
thời khai báo lại số phần tử hiện có (sopt) của mảng là 0.
- Nhắp vào button Dừng sẽ dừng chƣơng trình.

34
* Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Khai báo mảng nhƣ sau:
public partial class Form1 : Form {
// Khai bao mang 1 chieu gom 5 so nguyen int[]
a = new int[5];
// Khai bao so phan tu hien co cua mang int
sopt = 0;
}
- Nhắp đúp vào button Nhập 1 phần tử mảng, thêm đoạn code sau:
// Nhap mot phan tu cho mang
if (sopt == 5)
MessageBox.Show("Mảng đã đầy!"); else
{
a[sopt] = Convert.ToInt32(this.txtNhap.Text);
sopt++;
this.txtNhap.ResetText(); this.txtNhap.Focus();
}
- Nhắp đúp vào button Sắp tăng, thêm đoạn code sau:
// Sap xep mang giam
if (sopt == 0)
this.lblKQ.Text = "Mảng rỗng!";
else
Array.Sort(a, 0, sopt);
this.lblKQ.Text = "Đã sắp xếp mảng tăng dần!";
- Nhắp đúp vào button Sắp giảm, thêm đoạn code sau:
// Sap xep mang giam
if (sopt == 0)
this.lblKQ.Text = "Mảng rỗng!";
else

35
Array.Reverse(a, 0, sopt);
this.lblKQ.Text = "Đã sắp xếp mảng giảm dần!";
- Nhắp đúp vào button Xóa, thêm đoạn code sau:
this.lblKQ.Text = "";
sopt = 0;
this.txtNhap.Focus();
- Nhắp đúp vào button In mảng, thêm đoạn code sau:
// Xuat cac phan tu trong mang ra man hinh
if (sopt==0)
this.lblKQ.Text = "Mảng rỗng!";
else
{
this.lblKQ.Text="Các phần tử trong mảng là:\n\r";
for (int i = 0; i < sopt; i++)
this.lblKQ.Text += a[i] + " "; }
- Nhắp đúp vào button Dừng, thêm đoạn code sau:
Application.Exit();
4.2 Mảng nhiều chiều
4.2.1. Định nghĩa
- Mảng đa chiều là mảng mà mỗi thành phần là một mảng khác. - Ngôn ngữ
C# hỗ trợ hai kiểu mảng đa chiều là:
+ Mảng đa chiều cùng kích thƣớc.
+ Mảng đa chiều khác kích thƣớc.
- Trong phạm vi bài học này, ta chỉ khảo sát mảng 2 chiều mà thôi.
4.2.2 Khai báo mảng 2 chiều <kiểu dữ liệu>[ , ] <tên mảng>
Ví dụ:
int[ , ] myRectangularArray ;
4.2.3. Khởi tạo thành phần của mảng
int[] myRectangularArray = new int[sodong , socot] ;

36
4.2.4. Duyệt mảng 2 chiều
for (int i = 0; i {< sodong; i++)
for (int j = {0; j < socot; j++)
myRectangularArray[i,j];
Ví dụ 4.3:
* Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi Du 5.3
nhƣ sau:

Yêu cầu:
- Thiết kế form nhƣ mẫu (lblKQ, btnIn, btnXoa, btnDung, btnNhap,
btnSapXep). - Khai báo mảng 2 chiều gồm 4 dòng, 3 cột chứa các số nguyên.
- Nhắp vào button Nhập mảng để nhập các phần tử cho mảng (có giá trị =
dòng - cột).
- Nhắp vào button Sắp Xếp sẽ sắp xếp mảng tăng dần theo từng hàng.
- Nhắp vào button In mảng (btnIn) sẽ in ra label (lblKQ) các giá trị trong
mảng. - Nhắp vào button Xóa (btnXoa) sẽ xóa trống nội dung của label (lblKQ).
- Nhắp vào button Dừng sẽ dừng chƣơng trình. * Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Khai báo mảng: qua code, thêm đoạn code để đƣợc kết quả nhƣ sau: public
partial class Form1 : Form
{ // Khai bao 4 dong 3 cot const int sodong = 4;
const int socot = 3;
// Khai bao mang 2 chieu gom 4 dong, 3 cot chua
12 so nguyen
int[,] Array2 = new int[sodong,socot]; }

37
- Nhắp đúp vào button Nhập mảng, thêm đoạn code sau:
// Nhap cac phan tu cho mang
for (int i=0; i < sodong; i++) for (int j=0; j<
socot; j++)
Array2[i,j]= i-j;
- Nhắp đúp vào button Sắp xếp, thêm đoạn code sau:
// Sap xep mang
int[] t = new int[sodong * socot]; for (int i = 0;
i < sodong; i++)
{
for (int j = 0; j < socot; j++)
t[j] = Array2[i, j];
Array.Sort(t, 0, socot);
for (int j = 0; j < socot; j++)
Array2[i,j]=t[j];
}
- Nhắp đúp vào button In mảng, thêm đoạn code sau:
// Xuat cac phan tu trong mang ra man hinh
this.lblKQ.Text = "Các phần tử trong mảng là:\n\r"; for
(int i = 0; i < sodong; i++)
{
for (int j = 0; j < socot; j++) {
this.lblKQ.Text += Array2[i,j] + " "; }
this.lblKQ.Text += "\n\r"; }
- Nhắp đúp vào button Xóa, thêm đoạn code sau:
this.lblKQ.Text = "";
- Nhắp đúp vào button Dừng, thêm đoạn code sau:
Application.Exit();
4.3 String (Chuỗi)
4.3.1. Tạo một chuỗi
a. Chuỗi hằng
38
string TenChuoi = "Chuỗi" ; Ví dụ:
string thongbao = "Đây là một câu thông báo." ; Chú ý: Ta có 2 khai báo
chuỗi sau là nhƣ nhau
string chuoi = "Dong mot \n Dong hai"; string chuoi = @"Dong mot
Dong hai";
b. Chuỗi dùng phƣơng thức ToString Ví dụ:
int myInt = 9 ;
string intString = myInt.ToString();
4.3.2. Thao tác trên chuỗi
Lớp string cung cấp rất nhiều các phƣơng thức để so sánh, tìm kiếm, thay thế
…; các phƣơng thức này đƣợc trình bày trong bảng sau:
Phƣơng thức Ý nghĩa
Compare() So sánh hai chuỗi (Chuỗi 1 ? Chuỗi 2) = (-1 ; 0 ; 1) tƣơng
Concat() ứng chuỗi
Nối (<, =, >)
EndsWidth() Xem chuỗi có kết thúc bằng một nhóm ký tự xác định hay
IndexOf() không.
Chỉ ra vị trí xuất hiện đầu tiên của một chuỗi con trong
Insert() chuỗi
Trả vềlớn.
một chuỗi mới đã đƣợc chèn thêm.
LastIndexOf() Chỉ ra vị trí xuất hiện cuối cùng của một chuỗi con trong
Length chuỗi lớn.
Chiều dài của chuỗi.
Remove() Xoá đi một chuỗi con.
Replace() Thay thế chuỗi cũ bằng chuỗi mới.
Split() Trả về chuỗi con đƣợc phân định bởi ký tự xác định.
StartsWidth() Xem chuỗi có bắt đầu bằng một nhóm ký tự xác định hay
Substring() không.
Lấy chuỗi con.
ToLower() Trả về bản sao của chuỗi ở kiểu chữ thƣờng.
ToUpper() Trả về bản sao của chuỗi ở kiểu chữ IN HOA.

Ví dụ 4.4
Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi_Du
4.4 nhƣ sau:
*Yêu cầu:
- Thiết kế form gồm: lblTieuDe, lbl1, txtS1, lbl2, txtS2, lbl3, lblKQ, và các

39
button (xem hình).
- Nhắp vào button Compare: so sánh 2 chuỗi txtS1 và txtS2 (có phân biệt
chữ HOA và chữ thƣờng), kết quả xuất trong lblKQ.
- Nhắp vào button COMPARE: so sánh 2 chuỗi txtS1 và txtS2 (không phân
biệt chữ HOA và chữ thƣờng), kết quả xuất trong lblKQ.
- Nhắp vào button Concat, nối 2 chuỗi txtS1 và txtS2, kết quả xuất trong
lblKQ.
- Nhắp button IndexOf, cho biết vị trí xuất hiện của chuỗi txtS2 trong chuỗi
txtS1. Nếu có txtS2 trong txtS1 thì thay thế txtS2 (trong txtS1) bằng chuỗi "CHỖ
NÀY". Xuất kết quả trong lblKQ.
- Nhắp vào button Insert, chèn chuỗi txtS2 vào sau từ đầu tiên của chuỗi
txtS1; và chèn chuỗi txtS2 vào trƣớc từ sau cùng của chuỗi txtS1. Xuất kết quả
trong lblKQ.
- Nhắp vào button Substring, cho biết vị trí xuất hiện của chuỗi "TRÌNH
XỬ LÝ" trong lblTieuDe. Nếu có thì xóa chuỗi ra khỏi lblTieuDe. Xuất kết quả
trong lblKQ.
- Nhắp button Xóa thì xóa trống: TextBox txtS1, TextBox txtS2, Label
lblKQ đồng thời đƣa con trỏ vào TextBox txtS1.
- Nhắp button Dừng thì dừng chƣơng trình.

40
* Hƣớng dẫn:
- Thiết kế form nhƣ yêu cầu.
- Nhắp đúp vào button Compare, thêm vào đoạn code: string
s1=this.txtS1.Text;
string s2=this.txtS2.Text;
// So sánh hai chuỗi với nhau có phân biệt chữ thƣờng và chữ hoa
int kq = string.Compare(s1,s2); this.lblKQ.Text =
"txtS1 ";
if (kq == -1) this.lblKQ.Text += "<";
else if (kq == 0) this.lblKQ.Text += "=";
else
this.lblKQ.Text += ">"; this.lblKQ.Text += "
txtS2";
- Nhắp đúp vào button COMPARE, thêm vào đoạn code:
String s1=this.txtS1.Text;
string s2=this.txtS2.Text;
// So sánh hai chuỗi với nhau không phân biệt chữ thƣờng và chữ
hoa
int kq = string.Compare(s1,s2, true);
this.lblKQ.Text = "txtS1 ";
if (kq == -1) this.lblKQ.Text += "<";
else if (kq == 0) this.lblKQ.Text += "=";
else
this.lblKQ.Text += ">"; this.lblKQ.Text += "
txtS2";
- Nhắp đúp vào button Concat, thêm vào đoạn code:
string s1 = this.txtS1.Text;
string s2 = this.txtS2.Text;
// Nối chuỗi
this.lblKQ.Text = string.Concat(s1,s2);
- Nhắp đúp vào button IndexOf, thêm vào đoạn code:

41
string s1 = this.txtS1.Text;
string s2 = this.txtS2.Text;
// Chỉ ra vị trí xuất hiện của chuỗi 2 trong chuỗi 1 if
(s1.IndexOf(s2) >= 0)
{
this.lblKQ.Text = "txtS2 xuất hiện trong txtS1 tại vị trí
";
this.lblKQ.Text += s1.IndexOf(s2);
this.lblKQ.Text += ".!";
// Thay thế chuỗi s="CHỖ NÀY" vào vị trí chuỗi //2 trong
chuỗi 1
string s = "CHỖ NÀY";
this.lblKQ.Text += "\n\rThay thế txtS2 trong txtS1
bằng chuỗi CHỖ NÀY,";
this.lblKQ.Text+="\n\rKết quả:"+s1.Replace(s2,
s);
}
else this.lblKQ.Text = "txtS2 không xuất hiện trong
txtS1!";
- Nhắp đúp vào button Insert, thêm vào đoạn code:
string s1 = this.txtS1.Text;
string s2 = this.txtS2.Text;
// Chèn chuỗi 2 vào sau từ đầu tiên của chuỗi 1
this.lblKQ.Text = "Chèn txtS2 vào sau từ đầu tiên của
txtS1:\n\r";
this.lblKQ.Text += s1.Insert(s1.IndexOf(" "), s2);
// Chèn chuỗi 2 vào trƣớc từ cuối cùng của chuỗi 1
this.lblKQ.Text += "\n\rChèn txtS2 vào trƣớc từ cuối cùng của
txtS1:\n\r ";
this.lblKQ.Text += s1.Insert(s1.LastIndexOf("
"),s2);
- Nhắp đúp vào button Substring, thêm vào đoạn code:
42
// Chỉ ra vị trí xuất hiện của s="TRÌNH XỬ LÝ"
// trong lblTieuDe
string s="TRÌNH XỬ LÝ";
int ix;
ix = this.lblTieuDe.Text.IndexOf(s);
// Trích s từ lblTieuDe và in vào lblKQ
if (ix >= 0)
{
this.lblKQ.Text =
this.lblTieuDe.Text.Substring(ix, s.Length);
this.lblKQ.Text += " xuất hiện trong tiêu đề tại vị trí ";
this.lblKQ.Text += ix;
// Xóa s ra khỏi lblTieuDe
this.lblKQ.Text += "\n\rTiêu đề sau khi xóa " + s;
this.lblKQ.Text += "\n\rKết quả là: ";
this.lblKQ.Text +=
this.lblTieuDe.Text.Remove(ix, s.Length);
}
else this.lblKQ.Text = "Không xuất hiện trong chuỗi 1!";
- Nhắp đúp vào button Xóa, thêm vào đoạn code:
this.txtS1.ResetText(); this.txtS2.ResetText();
this.lblKQ.Text = ""; this.txtS1.Focus();
- Nhắp đúp vào button Dừng, thêm vào đoạn code:
Application.Exit();
Ví dụ 4.5
Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi_Du_4.5
nhƣ sau:

43
* Yêu cầu
- Thiết kế form gồm: lbl1, txtHoTen, lbl2, lblKQ, và các button (xem hình).
- Nhắp vào button CountOfWord: đếm số từ trong chuỗi txtHoTen, kết quả
xuất trong lblKQ.
- Nhắp vào button Proper: đổi thành chữ Hoa Đầu Từ cho chuỗi txtHoTen, kết
quả xuất trong lblKQ.
- Nhắp vào button Left, lấy ra từ bên trái của chuỗi txtHoTen, kết quả xuất
trong lblKQ.
- Nhắp vào button Right, lấy ra từ bên phải của chuỗi txtHoTen, kết quả xuất
trong lblKQ.
- Nhắp button Xóa thì xóa trống: TextBox txtS1, TextBox txtS2, Label lblKQ
đồng thời đƣa con trỏ vào TextBox txtS1.
- Nhắp button Dừng thì dừng chƣơng trình.
* Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Nhắp đúp vào button CountOfWord, thêm vào đoạn code:
// Đếm số từ trong chuỗi
// Tạo ra hằng ký tự khoảng trắng
const char Space =' ';
// Gán giá trị cho chuỗi
string s = hoten.Trim();
// Thực hiện việc chia chuỗi vào mảng --> Đếm từ = độ dài mảng
44
int cWord = s.Split(Space).Length;)
// Gởi kết quả trả về
return cWord;
** Tham khảothêmđoạn code sau:
// Đếm số từ trong chuỗi txtHoTen
int count=0;
string s=this.txtHoTen.Text.Trim();
for (int i = 0; i < s.Length-1; i++)
if ((s.Substring(i, 1)==" ") && (s.Substring(i
+ 1, 1)!=" "))
count++;
if (s.Length > 0) count++;
this.lblKQ.Text = "Số từ là: " + count;
- Nhắp đúp vào button Proper, thêm vào đoạn code:
// Đổi thành chữ Hoa Đầu Từ trong chuỗi txtHoTen string s =
this.txtHoTen.Text.Trim();
if (s.Length == 0)
this.lblKQ.Text = "Chuỗi rỗng!";
else
{
this.lblKQ.Text = "Chuỗi kết quả là: ";
this.lblKQ.Text += s.Substring(0,1).ToUpper();
for (int i = 1; i < s.Length; i++)
{
if ((s[i-1].ToString()==" "
)&&(s[i].ToString()!=" "))
{
string ss = s[i].ToString();
this.lblKQ.Text += ss.ToUpper();
}
else this.lblKQ.Text += s[i].ToString();
45
}
}
- Nhắp đúp vào button Left, thêm vào đoạn code:
// Từ đầu tiên của chuỗi txtHoTen
string s = this.txtHoTen.Text.Trim();
if (s.Length == 0)
this.lblKQ.Text = "Chuỗi rỗng!";
else
{
this.lblKQ.Text = "Từ đầu tiên của chuỗi là: ";
this.lblKQ.Text += s.Substring(0, s.IndexOf("
"));
}
- Nhắp đúp vào button Right, thêm vào đoạn code:
// Từ cuối cùng của chuỗi txtHoTen
string s = this.txtHoTen.Text.Trim();
if (s.Length == 0)
this.lblKQ.Text = "Chuỗi rỗng!";
else
{
this.lblKQ.Text = " Từ đầu tiên của chuỗi là:";
this.lblKQ.Text +=
s.Substring(s.LastIndexOf(" ")+1, s.Length-
s.LastIndexOf(" ") - 1);
}
- Nhắp đúp vào button Xóa, thêm vào đoạn code:
this.txtHoTen.ResetText();
this.lblKQ.Text = "";
this.txtHoTen.Focus();
- Nhắp đúp vào button Dừng, thêm vào đoạn code:
Application.Exit();
46
4.4 Exception (Ngoại lệ)
4.4.1. Khái niệm
- Exception có thể đƣợc hiểu là bắt giữ lỗi với những đoạn mã hợp lệ để
không tổn hại đến chƣơng trình.
- Lỗi có thể do nguyên nhân từ chính ngƣời sử dụng; hoặc có thể do những
vấn đề không mong đợi khác nhƣ: thiếu bộ nhớ, thiếu tài nguyên hệ thống ….
- Một trình xử lý ngoại lệ là một khối lệnh chƣơng trình đƣợc thiết kế xử lý
các ngoại lệ mà chƣơng trình phát sinh.
- Xử lý ngoại lệ đƣợc thực thi trong trong câu lệnh catch.
- Các câu lệnh có khả năng xảy ra ngoại lệ thực thi trong câu lệnh try.
* Một cách lý tƣởng, nếu một ngoại lệ đƣợc bắt và đƣợc xử lý thì chƣơng
trình có thể sửa chữa đƣợc vấn đề bị lỗi và tiếp tục thực hiện hoạt động. Thậm
chí nếu chƣơng trình không tiếp tục, bằng việc bắt giữ ngoại lệ chúng ta cũng có
cơ hội để in ra những thông điệp có ý nghĩa và kết thúc chƣơng trình một cách
rõ ràng.
4.4.2. Ví dụ
Ví dụ 4.6
Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi Du
4.6 nhƣ sau:

* Yêu cầu:
- Thiết kế form gồm: lbl1, txtSo1, lbl2, txtSo2, lbl3, txtKQ và các button (xem
hình). - Nhập số vào 2 TextBox txtSo1 và TxtSo2.
- Nhắp vào button Chia:
+ Nếu txtSo1, txtSo2 không phải là số; hoặc nhập vào txtSo2 là 0 thì báo lỗi:
“Lỗi rồi!”. + Nếu txtSo1, txtSo2 là số thì xuất kết quả là txtSo1 / txtSo2 vào
TextBox txtKQ.
- Nhắp button Xóa thì xóa trống: TextBox txtS1, TextBox txtS2, Label lblKQ

47
đồng thời đƣa con trỏ vào TextBox txtS1.
- Nhắp buuton Dừng thì dừng chƣơng trình. * Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Nhắp đúp vào button Chia, thêm vào đoạn code:
// Xóa trống TextBox txtKQ txtKQ.ResetText();
// Đoạn code có xảy ra ngoại lệ khi thực hiện
try
{
int so1 = int.Parse(this.txtSo1.Text); int so2
= int.Parse(this.txtSo2.Text); this.txtKQ.Text
+= (float)so1 / so2;
}
// Xử lý ngoại lệ
catch (Exception ex)
{
this.txtKQ.Text = "Lỗi rồi!";
}

48
Bài tập
Khởi tạo một ứng dụng Windows Forms Application:
1. Khai báo 1 mảng nguyên 1 chiều tối đa 10 phần tử. Viết chƣơng trình:
- Nhập vào giá trị cho 1 phần tử trong mảng.
- In giá trị của các phần tử trong mảng.
- In giá trị lớn nhất, giá trị nhỏ nhất của các phần tử trong mảng.
- In tổng số các giá trị, trung bình cộng các giá trị của các phần tử trong mảng.
2. Khai báo 1 mảng nguyên 2 chiều 4 dòng, 5 cột. Viết chƣơng trình:
- Nhập giá trị cho các phần tử trong mảng (giá trị = số thứ tự dòng + số thứ tự
cột). - In giá trị các phần tử trong mảng.
- In giá trị lớn nhất, giá trị nhỏ nhất của các phần tử trong mảng.
- In tổng số các giá trị, trung bình cộng các giá trị của các phần tử trong mảng.
3. Viết chƣơng trình nhập vào một chuỗi họ và tên. In ra: - Độ dài và số từ của
chuỗi họ tên.
- Chuỗi họ và tên dƣới dạng chữ thƣờng.
- Chuỗi họ và tên dƣới dạng chữ IN HOA. - Chuỗi họ và tên dƣới dạng Hoa
Đầu Từ.
- Chuỗi họ và tên đã đƣợc loại bỏ các khoảng trắng thừa (đầu chuỗi, cuối chuỗi,
bên trong chuỗi).
4. Viết chƣơng trình giải phƣơng trình bậc 1: bx + c = 0
Lƣu ý: có xử lý trƣờng hợp nhập vào b, c không phải là số.
-- oOo --

49
Bài 5
LỚP (CLASS) – ĐỐI TƢỢNG (OBJECT)-
PHƢƠNG THỨC (METHOD)
Mã bài: MĐ33-05
Giới thiệu:
Trong phƣơng pháp lập trình thủ tục, chƣơng trình là một hệ thống các thủ tục,
hàm. Tức là, khi viết chƣơng trình, ta phải xác định chƣơng trình làm những công
việc (thao tác) nào? Mỗi thao tác gồm những thao tác con nào? Từ đó mỗi thao tác sẽ
tƣơng ứng với một hàm. Nhƣ vậy, lập trình theo phƣơng pháp thủ tục là xác định các
hàm, định nghĩa các hàm và gọi các hàm này để giải quyết vấn đề đƣợc đặt ra.
Một trong những nhƣợc điểm của phƣơng pháp này là mọi hàm đều có thể truy
cập biến toàn cục hoặc dữ liệu có thể phải truyền qua rất nhiều hàm trƣớc khi đến
đƣợc hàm thực sự sử dụng hoặc thao tác trên nó. Điều này dẫn đến sự khó kiểm soát
khi chƣơng trình quá lớn và khi phát triển, sửa đổi chƣơng trình.
Một khó khăn nữa đó là việc nhớ các hàm xây dựng sẵn khi số lƣợng hàm quá
nhiều.
Phƣơng pháp lập trình hƣớng đối tƣợng sẽ khắc phục đƣợc những nhƣợc điểm
của phƣơng pháp lập trình thủ tục. Phƣơng pháp lập trình này lấy đối tƣợng làm nền
tảng để xây dựng chƣơng trình. Đối tƣợng là sự gắn kết giữa dữ liệu của đối tƣợng và
các hàm (còn gọi là phƣơng thức) thao tác trên các dữ liệu này.
Đối tƣợng = Dữ liệu + Phƣơng thức
Khi viết chƣơng trình theo phƣơng pháp hƣớng đối tƣợng ta phải trả lời các câu
hỏi:
 Chƣơng trình liên quan tới những lớp đối tƣợng nào?
 Mỗi đối tƣợng cần có những dữ liệu và thao tác nào?
 Các đối tƣợng quan hệ với nhau nhƣ thế nào trong chƣơng trình?
Từ đó ta thiết kế các lớp đối tƣợng và tổ chức trao đổi thông tin giữa các đối
tƣợng, ra lệnh để đối tƣợng thực hiện các nhiệm vụ thích hợp.
Mục tiêu:
 Thực hiện thuần thục kỹ năng tạo lớp, tạo đối tƣợng;
 Vận dụng đƣợc kiến thức và kỹ năng về các phƣơng thức, các thành phần static;
 Vận dụng đƣợc kiến thức và kỹ năng về tham số và các phƣơng thức nạp chồng;

50
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
5.1 Lớp và đối tƣợng
5.1.1. Khái niệm
- Kiểu dữ liệu trong C# đƣợc định nghĩa là một lớp (class).
- Thể hiện riêng của từng lớp đƣợc gọi là đối tƣợng (object).
- Hai thành phần chính cấu thành một lớp (class) là thuộc tính / tính chất và
phƣơng thức (method) / hành động ứng xử của đối tƣợng.
Lớp đối tƣợng:
Định nghĩa các thuộc tính (đặc điểm) và hành động (phƣơng thức) chung cho
tất cả các đối tƣợng của cùng một loại.
Đối tƣợng:
Thể hiện cụ thể của một lớp đối tƣợng.
Ví dụ:
Lớp SINHVIEN có
- Thuộc tính: Họ tên, giới tính, ngày tháng năm sinh, điểm trung bình, đối
tƣợng ƣu tiên, ...
- Phƣơng thức: Học bài, làm bài thi, làm bài tập, ...
- Sinh viên Nguyễn Văn A, Lý Thị B là đối tƣợng thuộc lớp SINHVIEN
5.1.2 Tạo và sử dụng đối tƣợng lớp (class)
5.1.2.1. Định nghĩa lớp
class <Tên lớp>
{
<từ khóa truy xuất> thuộc tính;
<từ khóa truy xuất> phƣơng thức();
}
Các từ khoá truy xuất: Chỉ phạm vi hoạt động
 private (mặc định): Chỉ đƣợc truy xuất trong nội bộ lớp (thuộc tính
thƣờng sử dụng).
 protected: Truy xuất trong nội bộ lớp hoặc trong các lớp con, đƣợc sử
dụng cho lớp cơ sở (lớp cha)
51
 public: Truy xuất mọi nơi(phƣơng thức thƣờng sử dụng).
 static: Truy xuất không cần khởi tạo đối tƣợng của lớp.

Ví dụ 5.1 Xây dựng lớp HocSinh


class HOCSINH
{
private string hoten;
private int toan, van;
private float dtb;
public void Nhap()
{
// Cài đặt
}
public void Xuat()
{
//Cài đặt
}
}
5.1.2.2. Tạo đối tƣợng
Cú pháp:
<Tên lớp> tên đối tƣợng=new <Tên lớp>();
Ví dụ 5.2:
HOCSINH hsA = new HOCSINH();
5.1.2.3. Sử dụng đối tƣợng
Cú pháp:
Tên_đối_tƣợng.Tên_phƣơng_thức([tham số]);
Ví dụ 5.3:
hsA.Nhap();

52
hsA.Xuat();
Ví dụ 5.4:
Chƣơng trình nhập chiều dài, chiều rộng của hình chữ nhật và xuất ra
diện tích, chu vi của hình chữ nhật.
using System; namespace LopDoiTuongHCN {
classHCN {
protected float Dai, Rong; public float ChuVi()
{
return (Dai + Rong )*2;
}
public float DienTich()
{
return Dai* Rong;
}
public void Nhap()
{
Console.WriteLine("Nhap chieu dai: ");
Dai = float.Parse(Console.ReadLine()); Console.WriteLine("Nhap chieu rong:
");
Rong = float.Parse(Console.ReadLine());
}
public void Xuat()
{
Console.WriteLine("Hinh chu nhat: Dai = {0}, Rong = {1}", Dai, Rong);
}
}
class Application {
static void Main(string[] args)
{ HCN h;
h = new HCN(); h.Nhap () ; h.Xuat();

53
Console.WriteLine ("Chu vi hinh chu nhat: {0}", h.ChuVi());
Console.WriteLine ("Dien tich hinh chu nhat: {0}", h.DienTich());
Console.ReadLine();
}
}
Ví dụ 5.5:
* Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là
Vi_Du_5.5 nhƣ sau:

* Yêu cầu
- Thiết kế form gồm: lbl1, txtHoTen, lbl2, lblKQ, và các button (xem hình). -
Tạo class Chuoi nhƣ sau:
public class Chuoi {
// Thuộc tính ...
// Phƣơng thức ...
}

+ Trong phần thuộc tính, khai báo:


string tenchuongtrinh = "Chƣơng trình xử lý họ và tên!"; + Trong phần phƣơng
thức, khai báo các phƣơng thức sau:
// Phƣơng thức public string In() {
// In tên chƣơngtrình return tenchuongtrinh;
}
54
public string Ten(string hoten)
{
// Lấy tên
int lio=hoten.LastIndexOf(" ");
return hoten.Substring(lio+1, hoten.Length-lio-1);
}
public string HoLot(string hoten)
{
// Lấy họ và lót
int lio = hoten.LastIndexOf(" "); return hoten.Substring(0,lio);
}
public int CountOfWord(string hoten)
{
// Đếm số từ trong chuỗi
// Tạo ra hằng ký tự khoảng trắng const char Space =' ';
// Gán giá trị cho chuỗi string s = hoten.Trim();
// Thực hiện việc chia chuỗi thành mảng --> Đếm từ cWord =
s.Split(Space)).Length ;
// Gởi kết quả trả về return cWord;
}
** Tham khảothêmđoạn code sau:
// Đếm số từ trong chuỗi HoTen int count = 0;
string s = hoten.Trim();
for (int i = 0; i < s.Length - 1; i++)
if ((s.Substring(i,1)==" ")&&(s.Substring(i+1,1)!= " ")) count++;
if (s.Length > 0) count++;
return count;
public string Proper(string hoten)
{// Đổi thành chữ Hoa Đầu Từ trong chuỗi txtHoTen
// Tạo ra hằng ký tự khoảng trắng
55
const char Space = ' ';
// Gán giá trị cho chuỗi
string s = hoten.Trim();
// Đổi chuỗi
string kq = "Chuỗi rỗng!";
if (s.Length == 0)
return kq; else
{
kq = "";
string [] s1 = s.Split(Space); foreach (string tu in s1) { string ss =
tu[0].ToString(); kq += ss.ToUpper();
kq += tu.Substring(1,tu.Length-1); kq += " ";
}
return kq.Trim();
}
** Tham khảothêmđoạn code sau:
// Đổi thành chữ Hoa Đầu Từ trong chuỗi txtHoTen
string s = hoten.Trim();
string kq = "Chuỗi rỗng!"; if (s.Length > 0)
{
kq = s.Substring(0, 1).ToUpper(); for (int i = 1; i < s.Length; i++)
{
if ((s[i - 1].ToString() == " ") && (s[i].ToString() != " "))
{
string ss = s[i].ToString(); kq += ss.ToUpper();
}
else kq += s[i].ToString(); }
}
return kq;
- Sử dụng các phƣơng thức trên để thực hiện các việc sau đây (xuất kết quả trong
56
lblKQ): + Nhắp vào button In Lời giới thiệu, sẽ in lời giới thiệu.
+ Nhắp button Họ Lót, tách lấy họ lót của chuỗi trong txtHoTen. + Nhắp button
Tên, tách lấy tên của chuỗi trong txtHoTen.
+ Nhắp button Đếm từ, đếm số từ của chuỗi trong txtHoTen.
+ Nhắp button Hoa Đầu Từ, đổi thành chuỗi Hoa Đầu Từ của chuỗi trong
txtHoTen.
- Nhắp button Xóa thì xóa trống: TextBox txtHoTen, Label lblKQ đồng thời đƣa
con trỏ vào TextBox txtHoTen.
- Nhắp button Dừng thì dừng chƣơng trình. * Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Khai báo class: qua code, thêm đoạn code để đƣợc kết quả nhƣ sau
public Form1()
{
InitializeComponent(); }
public class Chuoi {
// Thuộc tính ... (1)
// Phƣơng thức ... (2)
}
+ Trong phần (1), khai báo thuộc tính (xem đề bài)
+ Trong phần (2), khai báo phƣơng thức (xem đề bài)
- Nhắp đúp vào button In Lời giới thiệu, thêm vào đoạn code: Chuoi s = new
Chuoi();
this.lblKQ.Text = s.In();
- Nhắp đúp vào button Họ lót, thêm vào đoạn code: Chuoi s = new Chuoi();
this.lblKQ.Text="Họ lót: " + s.HoLot(this.txtHoTen.Text); - Nhắp đúp vào
button Tên, thêm vào đoạn code:
Chuoi s = new Chuoi();
this.lblKQ.Text = "Tên là: " + s.Ten(this.txtHoTen.Text); - Nhắp đúp vào
button Đếm từ, thêm vào đoạn code:
Chuoi s = new Chuoi(); this.lblKQ.Text = "Tổng số từ là: " ;
this.lblKQ.Text += s.CountOfWord(this.txtHoTen.Text); - Nhắp đúp vào

57
button Hoa Đầu Từ, thêm vào đoạn code:
Chuoi s = new Chuoi(); this.lblKQ.Text = "Kết
quả là: " ; this.lblKQ.Text += s.Proper(this.txtHoTen.Text);
- Nhắp đúp vào button Xóa, thêm vào đoạn code: this.lblKQ.Text = "";
this.txtHoTen.ResetText(); this.txtHoTen.Focus();
- Nhắp đúp vào button Dừng, thêm vào đoạn code: Application.Exit();
* Bổ sung
- Nút button In Hoa: đổi thành chuỗi IN HOA của chuỗi trong txtHoTen .
- Nút button In Thƣờng: đổi thành chuỗi in thƣờng của chuỗi trong txtHoTen.
5.2 Properties - Method
5.2.1. Thuộc tính (Properties):
Thuộc tính là những thông tin có thể thay đổi đƣợc.
5.2.2 Thuộc tính truy cập
Thuộc tính Phạm vi truy cập
public Không hạn chế. Những thành viên đƣợc đánh dấu public
có thể đƣợc dùng bất kỳ các phƣơng thức của lớp, bao
gồm cả những lớp khác.
private Thành viên trong lớp đƣợc đánh dấu private chỉ đƣợc
dùng các phƣơng thức của lớp này mà thôi.
Protected Thành viên trong lớp đƣợc đánh dấu protected chỉ đƣợc
dùng các phƣơng thức của lớp này; và các phƣơng thức
của lớp dẫn xuất từ lớp này.
Internal Thành viên trong lớp đƣợc đánh dấu là internal đƣợc
dùng các phƣơng thức của bất kỳ lớp nào cùng khối hợp
ngữ với lớp này.
protected Thành viên trong lớp đƣợc đánh dấu là protected internal
internal đƣợc dùng các phƣơng thức của lớp này; các phƣơng
thức của lớp dẫn xuất từ lớp này; và các phƣơng thức của
bất kỳ lớp nào trong cùng khối hợp ngữ với lớp này.

5.2.3 Phƣơng thức (Method)


- Phƣơng thức (method) chính là các hàm (function) đƣợc tạo trong lớp (class). -
Tên của phƣơng thức thƣờng đƣợc đặt theo tên của hành động.

58
5.2.4. Tham số của phƣơng thức
a) Khái niệm:
- Các tham số theo sau tên phƣơng thức và đƣợc bọc bên trong dấu ngoặc tròn
(). - Mỗi tham số phải khai báo kèm theo kiểu dữ liệu.
- Trong C# có 2 dạng truyền tham số:
+ Truyền tham chiếu: dùng thêm từ khóa ref. + Truyền tham trị
b) Ví dụ:
* Truyền tham số cho phƣơng thức theo kiểu tham chiếu
public class Hoandoi
{
public void HoanVi(ref int a, ref int b)
{
int c = a ; a = b ;
b=c;
}
}
Khi đó: khi gọi hàm HoanVi ta phải truyền tham số dƣới dạng tham chiếu nhƣ
sau: HoanDoi s = new HoanDoi();
s.HoanVi(ref a, ref b);
* Truyền tham số cho phƣơng thức theo kiểu tham trị
public class HoanDoi
{
public void HoanVi(int a, int b)
{
int c = a ; a = b ;
b=c;
}
}
Ví dụ 5.6
* Khởi tạo một ứng dụng Windows Forms Application, lƣu với tên là Vi_Du_5.2

59
nhƣ sau:

* Yêu cầu
- Thiết kế form gồm: lbl1, txta, lbl2, txtb, và các button (xem hình).
- Tạo các class HoanDoi, trong class có hàm HoanVi cho phép hoán vị 2 giá trị số
nguyên.
- Nhắp button Hoán Đổi sẽ hoán đổi 2 giá trị trong txta và txtb
- Nhắp button Xóa sẽ xóa trống 2 TextBox và đƣa con trỏ vào ô txta. - Nhắp
button Dừng sẽ dừng chƣơng trình.
* Hƣớng dẫn
- Thiết kế form nhƣ yêu cầu.
- Khai báo class: qua code, thêm đoạn code để đƣợc kết quả nhƣ sau
public Form1()
{
InitializeComponent();
}
public class HoanDoi
{
public void HoanVi(ref int a,ref int b)
{
int c = a ; a = b ;
b=c;
}
}
- Nhắp đúp vào button Hoán đổi, thêm vào đoạn code:
int a = int.Parse(this.txta.Text); int b = int.Parse(this.txtb.Text);
60
HoanDoi s = new HoanDoi();
s.HoanVi(ref a, ref b);
this.txta.Text = a.ToString();
this.txtb.Text = b.ToString();
- Nhắp đúp vào button Xóa, thêm vào đoạn code: this.txta.ResetText();
this.txtb.ResetText(); this.txta.Focus();
- Nhắp đúp vào button Dừng, thêm vào đoạn code: Application.Exit();
c) Tham chiếu this
Khi một đối tƣợng thực thi một phƣơng thức của thể hiện (không phải là
phƣơng thức tĩnh) tham chiếu this tự động trỏ đến đối tƣợng này. Mọi phƣơng thức
của đối tƣợng đều có thể tham chiếu đến các thành phần của đối tƣợng thông qua
tham chiếu this. Có 3 trƣờng hợp sử dụng tham chiếu this:
- Tránh xung đột tên khi tham số của phƣơng thức trùng tên với tên biến dữ liệu
của đối tƣợng.
- Dùng để truyền đối tƣợng hiện tại làm tham số cho một phƣơng thức khác
(chẳng hạn gọi đệ quy)
- Dùng với mục đích chỉ mục.
Ví dụ 5.7
Trƣờng hợp tên tham số trùng với tên thuộc tính của đối tƣợng ta dùng từ khóa
this. Từ khoá this đƣợc dùng để tham chiếu đến chính bản thân của đối tƣợng đó
class ViDu
{
int a, b;
public void GanGiaTri(int a, int b)
{
this.a = a;
this.b = b;
}
public void Xuat()
{
Console.WriteLine("a={0}, b={1}", a, b);

61
}
}
5.3 Phƣơng thức tạo lập (constructor) của một đối tƣợng
Phƣơng thức tạo lập của một đối tƣợng có các tính chất sau:
■ Đƣợc gọi đến một cách tự động khi một đối tƣợng của lớp đƣợc tạo ra. Dùng để
khởi động các giá trị đầu cho các thành phần dữ liệu của đối tƣợng thuộc lớp.
■ Tên phƣơng thức giống với tên lớp và có mức độ truy cập là public.
■ Không có giá trị trả về.
■ Trƣớc khi phƣơng thức tạo lập chạy, đối tƣợng chƣa thực sự tồn tại trong bộ
nhớ, sau khi tạo lập hoàn thành, bộ nhớ lƣu trữ một thể hiện hợp lệ của lớp.
■ Khi ta không định nghĩa một phƣơng thức tạo lập nào cho lớp, trình biên dịch sẽ
tự động tạo một phƣơng thức tạo lập mặc định cho lớp đó và khởi tạo các biến
bằng các giá trị mặc định.
Thông thƣờng ta nên định nghĩa một phƣơng thức tạo lập cho lớp và cung cấp tham
số cho phƣơng thức tạo lập để khởi tạo các biến cho đối tƣợng của lớp.
Chú ý rằng, nếu lớp có phƣơng thức tạo lập có tham số thì khi khởi tạo đối tƣợng
(bằng toán tử new) ta phải truyền tham số cho phƣơng thức tạo lập theo cú pháp:
TênBiếnĐốiTƣợng = new TênLớp(DanhSáchĐốiSố);
Ví du 5.8:
Xây dựng một lớp Time trong đó có một phƣơng thức tạo lập nhận tham số có
kiểu DateTime (kiểu xây dựng sẵn của trình biên dịch) làm tham số khởi gán cho các
thành phần dữ liệu của đối tƣợng thuộc lớp Time.
using System;
public class Time
{
// Các biến thành viên
int Year;
int Month;
int Date;
int Hour;
int Minute;

62
int Second = 30;
public void DisplayCurrentTime( )
{
Console.WriteLine("Current time is: {0}/{1}/{2} {3}:{4}:{5}",Month, Date,
Year, Hour, Minute, Second);
}
public Time(System.DateTime dt)// Phương thức khởi tạo
{
Console.WriteLine("Ham constructor tu dong duoc goi!");
Year = dt.Year;
Month = dt.Month;
Date = dt.Day;
Hour = dt.Hour;
Minute = dt.Minute;
}
class DateTimeConstrcutorApp
{
static void Main( )
{
System.DateTime System.DateTime.Now;
Time t = new Time(currentTime); t.DisplayCurrentTime( );
Console.ReadLine();
}
}
5.4 Phƣơng thức tạo lập sao chép (copy constructor)
Phƣơng thức tạo lập sao chép khởi gán giá trị cho đối tƣợng mới bằng cách sao chép
dữ liệu của đối tƣợng đã tồn tại (cùng kiểu). Ví dụ, ta muốn truyền một đối tƣợng
Time t1 để khởi gán cho đối tƣợng Time t2 mới với mục đích làm cho t2 có giá trị
giống t1, ta sẽ xây dựng phƣơng thức tạo lập sao chép của lớp Time nhƣ sau:
public Time(Time existingTimeObject)

63
{
Year = existingTimeObject.Year;
Month =
Date =existingTimeObject.Month;
existingTimeObject.Date;
Hour = existingTimeObject.Hour;
Minute =
Second existingTimeObject.Minute;
=
existingTimeObject.Second;
}
Khi đó cú pháp khai báo t2 là:
Time t2 = new Time(t1).
Khi đó hàm copy constructor đƣợc gọi và gán giá trị của t1 cho t2.
5.5 Sử dụng các thành viên tĩnh (static member)
Dữ liệu và phƣơng thức của một lớp có thể là thành viên thuộc thể hiện của lớp
(đối tƣợng) hoặc thành viên tĩnh (có từ khóa static đứng trƣớc). Thành viên thể hiện
đƣợc kết hợp riêng với từng đối tƣợng của lớp. Nhƣ vậy, trong cùng một lớp, các đối
tƣợng khác nhau có những biến dữ liệu cùng tên, cùng kiểu nhƣng đƣợc cấp phát ở
các vùng nhớ khác nhau và giá trị của chúng cũng có thể khác nhau. Trong khi đó,
thành viên tĩnh (biến, phƣơng thức) đƣợc coi là phần chung của các đối tƣợng trong
cùng một lớp. Mọi đối tƣợng thuộc lớp đều có thể truy cập thành viên tĩnh. Nói cách
khác, các thành viên thể hiện đƣợc xem là toàn cục trong phạm vi từng đối tƣợng còn
thành viên tĩnh đƣợc xem là toàn cục trong phạm vi một lớp.
Việc truy cập đến thành viên tĩnh phải thực hiện thông qua tên lớp
Những thuộc tính và phƣơng thức trong một lớp có thể là những thành viên thể hiện
(instance members) hay những thành viên tĩnh (static members). Những thành viên
thể hiện hay thành viên của đối tƣợng liên quan đến thể hiện của một kiểu dữ liệu.
Trong khi thành viên tĩnh đƣợc xem nhƣ một phần của lớp. Chúng ta có thể truy cập
đến thành viên tĩnh của một lớp thông qua tên lớp đã đƣợc khai báo (không đƣợc truy
cập thành viên tĩnh thông qua đối tƣợng) theo cú pháp:

TênLớp.TênThànhViênTĩnh
Chú ý:
• Phương thức tĩnh thao tác trên các dữ liệu tĩnh và khống thể truy cập trực
tiếp các thành viên khống tĩnh.

64
Ngoài ra, ta có thể định nghĩa một phƣơng thức tạo lập tĩnh, phƣơng thức này
dùng để khởi gán giá trị cho biến tĩnh của lớp và sẽ chạy trƣớc khi thể hiện của đầu
tiên lớp đƣợc tạo. Phƣơng thức tạo lập tĩnh hữu dụng khi chúng ta cần cài đặt một số
công việc mà không thể thực hiện đƣợc thông qua chức năng khởi dựng và công việc
cài đặt này chỉ đƣợc thực hiện duy nhất một lần.
Ví dụ 5.9:
Biến thành viên tĩnh đƣợc dùng với mục đích theo dõi số thể hiện hiện tại của lớp.
using System; public class Cat {
private static int SoMeo = - 6; // bien tinh private string TenMeo ;
// Phuong thuc tao lap cua doi tuong public Cat( string T)
{
TenMeo = T ;
Console.WriteLine("WOAWMM {0} day!", TenMeo); SoMeo++;
}
// Phuong thuc tao lap tinh static Cat( )
{
Console.WriteLine("Bat dau lam thit meo !!!!"); SoMeo = 0;
}
public static void HowManyCats( )
{
Console.WriteLine("Dang lam thit {0} con meo!",SoMeo);
}
}
public class Tester
{
static void Main( )
{
Cat.HowManyCats( );
Cat tom = new Cat("Meo Tom" );
Cat.HowManyCats( );

65
Cat muop = new Cat("Meo Muop");
Cat.HowManyCats( );
//Tom.HowManyCats( ); ----- > Error
Console.ReadLine();
}
}
Trong ví dụ này, ta xây dựng lớp Cat với một biến tĩnh SoMeo để đếm số thể
hiện (số mèo) hiện có và một biến thể hiện TenMeo để lƣu tên của từng đối tƣợng
mèo. Nhƣ vậy, mỗi đối tƣơng tom, muop đều có riêng biến TenMeo và chúng dùng
chung biến SoMeo.
Ban đầu biến SoMeo đƣợc khởi gán giá trị -6, nhƣng khi đối tƣợng tom (đối tƣợng
đầu tiên của lớp Cat) đƣợc tạo ra, phƣơng thức tạo lập tĩnh static Cat() tự động thực
hiện và gán lại giá trị 0 cho biến tĩnh này.
Mỗi khi một đối tƣợng thuộc lớp Cat đƣợc tạo ra thì phƣơng thức tạo lập của
đối tƣợng này truy cập đến biến đếm SoMeo và tăng giá trị của biến này lên một đơn
vị. Nhƣ vậy, khi đối tƣợng tom đƣợc tạo ra, giá trị của biến này tăng lên thành 1, khi
đối tƣợng muop đƣợc tạo ra, giá trị của biến này tăng lên thành 2. Phƣơng thức tĩnh
HowManyCats() thực hiện nhiệm vụ xuất biến tĩnh SoMeo thông qua tên lớp bằng
câu lệnh:
Cat.HowManyCats( );
Nếu ta gọi lệnh sau thì trình biên dịch sẽ báo lỗi:
tom.HowManyCats( );
- Ghi chú:
Trong ngôn ngữ C# không cho phép truy cập đến các phƣơng thức tĩnh và các
biến thành viên tĩnh thông qua một thể hiện, nếu chúng ta cố làm điều đó thì trình
biên dịch C# sẽ báo lỗi, điều này khác với ngôn ngữ C++.
5.6 Đóng gói dữ liệu với thuộc tính (property)
Thuộc tính là một đặc tính mới đƣợc giới thiệu trong ngôn ngữ C# làm tăng sức
mạnh của tính đóng gói. Thuộc tính đơn giản là các phƣơng thức lấy giá trị (get) và
gán giá trị (set). Nó cho phép truy cập đến các thành phần dữ liệu của đối tƣợng ở
mức độ đọc hoặc ghi hoặc cả hai và che dấu cài đặt thực sự bên trong lớp. Một thuộc
tính thƣờng quản lý một biến dữ liệu của lớp và thuộc tính đó có thể là:
 Chỉ đọc (rread-only): chỉ có phƣơng thức get. Ta chỉ đƣợc đọc giá trị của thuộc
tính.
66
 Chỉ ghi (write-only): chỉ có phƣơng thức set. Ta chỉ đƣợc gán (ghi dữ liệu) giá
trị cho thuộc tính.
 Vừa đọc vừa ghi (read/write): có cả hai phƣơng thức get và set. Đƣợc phép đọc
và ghi giá trị.
Để khai báo một thuộc tính, chúng ta viết kiểu thuộc tính và tên theo sau bởi cặp {}.
Bên trong cặp {} chúng ta có thể khai báo các phƣơng thức get hay set.
Cú pháp định nghĩa một thuộc tính:
public KiểuTrảVề TênThuộcTính
{
// phƣơng thức lấy giá trị của thuộc tính
get
{
//...các câu lệnh
return BiểuThứcTrảVề; //trả về một giá trị
}
//phƣơng thức gán giá trị cho thuộc tính set
{
//...các câu lệnh BiếnThànhViên = value;
}
}
Phần thân của phƣơng thức get của thuộc tính tƣơng tự nhƣ phần thân phƣơng
thức của lớp. Chúng trả về giá trị (hoặc tham chiếu) nào đó, thƣờng là trả về giá trị
của biến thành viên mà thuộc tính quản lý. Khi ta truy cập đến thuộc tính thì phƣơng
thức get đƣợc gọi thực hiện.
Phƣơng thức set của thuộc tính dùng để gán giá trị cho biến thành viên mà thuộc tính
quản lý. Khi định nghĩa phƣơng thức set, ta phải sử dụng từ khóa value để biểu diễn
cho giá trị dùng để gán cho biến thành viên. Khi gán một giá trị cho thuộc tính thì
phƣơng thức set tự động đƣợc gọi và tham số ẩn value chính là giá trị dùng để gán.
Ví dụ 5.10:
class ViDu
{
private int a, b;
67
public void Nhap()
{}
public void Xuat()
{}
public int A
{
get{return a;}
set{a=value;}
}
public int B
{
get {return b;}
set { b = value; }
}
}
class Program
{
static void Main(string[] args)
{
ViDu vd = new ViDu();
vd.A = 5 + vd.B;
vd.Xuat();
}
}
Ví dụ 5.11: Xây dựng ứng dụng có đóng gói dữ liệu với thuộc tính Property
using System;
class Student
{
string _Ten ;

68
float _DiemToan, _DiemTin;
float _DiemTB;
}
// Phƣơng thức khởi tạo
public Student()
{
_Ten = "";
_DiemToan = 0;
_DiemTin = 0;
_DiemTB = 0;
}
// thuoc tinh ten - (read/write)
public string Ten
{
get {return _Ten;}
set {_Ten = value;}
}
//Thuoc tinh diem toan - (read/write)
public float DiemToan
{
get {return _DiemToan;}
set {
_DiemToan = value;
_DiemTB = (_DiemToan + DiemTin)/2;
}
}
//Thuoc tinh diem tin - (read/write)
public float DiemTin
{

69
get {return _DiemTin;}
set {
_DiemTin = value;
_DiemTB = (_DiemToan + DiemTin)/2;
}
}
//Thuoc tinh diem tin - (read only)
public float DiemTrungBinh
{
get {return _DiemTB;}
}
}
class Student_PropertyApp
{
static void Main(string[] args)
{
Student si = new Student();
si.Ten = "Hoa"; si.DiemToan = 5; si.DiemTin = 7;
//si.DiemTrungBinh = 6; -------- > loi
Console.WriteLine("Ten: {0}, diem Toan: {i}, diem Tin: {2}, diem trung
binh: {3}", si.Ten, si.DiemToan, si.DiemTin, si.DiemTrungBinh);
Console.ReadLine();
}
}
5.7 Toán tử
Trong C#, toán tử là một phƣơng thức tĩnh dùng để quá tải một phép toán nào
đó trên các đối tƣợng. Mục đích của toán tử là để viết mã chƣơng trình gọn gang, dễ
hiểu, thay vì phải gọi phƣơng thức.
Ta có thể quá tải các toán tử sau:
Toán học: +,-,*,?,%.

70
Cộng trừ 1 ngôi: ++, --, -.
Quan hệ so sánh: ==,!=,>,<,>=,<=.
Ép kiểu: ()
Cú pháp khai báo nguyên mẫu một toán tử T:
public static KiểuTrảVề operator T (CácThamSố)
{
///các câu lệnh trong thân toán tử
}
Chú ý:
 Tham số của toán tử phải là tham trị (không dùng các từ khóa ref, out).
 Không đƣợc quá tải toán tử = (gán), && , || (and, or logic), ?: (điều kiện),
checked, unchecked, new, typeof, as, is.
 [] không đƣợc xem là một toán tử.
 Khi quá tải các toán tử dạng: +, *, / , % thì các toán tử +=, -=, *=, /= , %= cũng
tự động đƣợc quá tải.
 Khi quá tải toán tử thì nên quá tải theo cặp đối ngẫu. Chẳng hạn, khi quá tải
toán tử == thì quá tải thêm toán tử !=...
 Khi quá tải toán tử ==, != thì nên phát triển thêm các phƣơng thức Equals(),
GetHashCode() để đảm bảo luật “hai đối tượng bằng nhau theo toán tử = =
hoặc phương thức Equals sẽ có cùng mã băm”.
 Khi định nghĩa toán tử ép kiểu ta phải chỉ ra đây là toán tử ép kiểu ngầm định
(implicit) hay tƣờng minh (explicit).
Cú pháp định nghĩa toán tử ép kiểu:
public static [implicit I explicit] operator KiểuTrảVềT (Type V)
trong đó Type V là biến cần ép sang kiểu KiểuTrảVềT.
Giả sử khi ta thiết kế lớp phân số (CPHANSO) có phƣơng thức cộng (Cong),
trừ (Tru), nhân (Nhan) và chia (Chia) 2 phân số.
 Khi đó, để cộng 2 phân số a và b lƣu vào c:
c = a.Cong(b);
Tƣơng tự cho trƣờng hợp nhân
c = a.Nhan(b);

71
 Cách này không thể hiện hết ý nghĩa là:
c = a + b;
c = a * b;
Vì vậy C# đã cung cấp cơ chế cài đặt phƣơng thức thông qua các ký hiệu phép
toán (operator).
public static TênLớp operator kýhiệu(TênLớptrái, TênLớpphải)
Ví dụ 5.12: Giả sử có lớp phân số (CPHANSO)
public static CPHANSO operator +(CPHANSO ps1, CPHANSO ps2)
{
//Cài đặt
}
Giả sử có 2 phân số a, b và phân số tổng c. Yêu cầu thực hiện nhƣ sau: c = a +
b;
Trong trƣờng hợp không dùng operator
class CPHANSO
{
private int tuso, mauso;
public CPHANSO(int t, int m)
{
tuso = t;
mauso = m;
}
public CPHANSO Cong(CPHANSO ps2)
{
int tu = tuso*ps2.mauso + ps2.tuso*mauso;
int mau = mauso*ps2.mauso;
CPHANSO c = new CPHANSO(tu, mau);
return c;
}
public void Xuat()

72
{
Console.WriteLine("{0}/{1}", tuso, mauso);
}
}
class Program
{
static void Main(string[] args)
{
CPHANSO a = new CPHANSO(3, 5);
a.Xuat();
CPHANSO b = new CPHANSO(1, 2);
b.Xuat();
CPHANSO c;
c = a.Cong(b);
Console.WriteLine("Ket qua: ");
c.Xuat();
}
}
Trong trƣờng hợp dùng operator
class CPHANSO
{
private int tuso, mauso;
public CPHANSO(int t, int m)
{
tuso = t; mauso = m;
}
public static CPHANSO operator +(CPHANSO ps1, CPHANSO ps2)
{
int tu = ps1.tuso*ps2.mauso + ps2.tuso*ps1.mauso;

73
int mau = ps1.mauso*ps2.mauso;
CPHANSO c = new CPHANSO(tu, mau);
return c;
}
public void Xuat()
{
Console.WriteLine("{0}/{1}", tuso, mauso);
}
}
class Program
{
static void Main(string[] args)
{
CPHANSO a = new CPHANSO(3, 5);
a.Xuat();
CPHANSO b = new CPHANSO(1, 2);
b.Xuat();
CPHANSO c;
c = a + b;
Console.WriteLine("Ket qua: ");
c.Xuat();
}
}

74
75
Bài tập
1. Tạo một class có tên là BAI_TAP_CLASS, có các hàm:
- Hàm TEN nhận vào một chuỗi chỉ họ và tên, giá trị trả lại của hàm là chuỗi chỉ
tên.
- Hàm NGTO nhận vào một số nguyên n, giá trị trả lại của hàm là true nếu n là số
nguyên tố; là false nếu n không là số nguyên tố.
2. Thiết kế form có:
- TextBox txtHoTen: nhập họ và tên.
- TextBox txtn: nhập số nguyên n.
- TextBox txtKQ: xuất kết quả.
- Button btnTEN: gọi hàm TEN xử lýchuỗi họ và tên, xuất kết quả vào txtKQ. -
Button btnNGTO: gọi hàm NGTO xử lý số nguyên n, xuất kết quả vào txtKQ. -
Button btnXoa: xóa trống tất cả các TextBox và đƣa con trỏ vào ô txtHoTen
- Button btnDung: dừng chƣơng trình.
3. Viết chƣơng trình xây dựng lớp TamGiac với dữ liệu là 3 cạnh của tam giác. Xây
dựng các thuộc tính (property) ChuVi, DienTich và các phƣơng thức kiểm tra kiểu
của tam giác (thƣờng, vuông, cân, vuông cân, đều).
4. Viết chƣơng trình xây dựng lớp HinhTruTron (hình trụ tròn) với dữ liệu chiều cao
và bán kính. Xây dựng các thuộc tính (property) DienTichDay (diên tích mặt đáy),
DienTichXungQuanh (diên tích mặt xung quanh), TheTich (thể tích).

-- oOo --

76
Bài 6
KẾ THỪA VÀ ĐA HÌNH
Mã bài: MĐ33-06
Giới thiệu:
Kế thừa là 1 cơ chế trong ngôn ngữ lập trình hƣớng đối tƣợng cho phép thể
hiện quan hệ đặc biệt hoá trong sơ đồ lớp bằng cách cho phép khai báo 1 lớp B là 1
lớp dẫn xuất từ lớp A (B là trƣờng hợp đặc biệt của A) khi đó B sẽ có tất cả các thuộc
tính và đặc điểm của A ngoài ra B có thể có thêm những thuộc tính mới, những hàm
kiểm tra ràng buộc mới, những hoạt động khởi tạo, cập nhật, cung cấp thông tin và xử
lý mới. Bài học này sẽ cung cấp cho học sinh những kỹ thuật xây dựng các lớp dẫn
xuất kế thừa từ lớp cơ sở, cũng nhƣ kỹ năng gọi các phƣơng thức của các lớp cơ sở và
điều khiển truy xuất các lớp có kế thừa. Ngoài ra còn vận dụng tính chất kế thừa vào
việc sử dụng lại thông qua các kiểu đa hình và phƣơng thức đa hình.
Mục tiêu:
 Trình bày đƣợc các kiến thức về tính kế thừa và đa hình trên C#;
 Sử dụng các kiến thức về lớp trừu tƣợng;
 Vận dụng đƣợc các kiến thức và kỹ năng về các phƣơng thức, các thành phần
static;
 Vận dụng đƣợc các kiến thức và kỹ năng về tham số và các phƣơng thức nạp
chồng;
 Nghiêm túc, sáng tạo trong quá trình tiếp thu lý thuyết và áp dụng làm các bài
tập.
Nội dung chính:
6.1 Sự kế thừa
6.1.1. Quan hệ chuyên biệt hóa và tổng quát hóa
Các lớp và thể hiện của lớp không tồn tại trong một không gian độc lập, chúng
tồn tại trong một mạng các quan hệ và phụ thuộc qua lại lẫn nhau.
Quan hệ tổng quát hóa và chuyên biệt hóa là quan hệ phân cấp và tƣơng hỗ lẫn
nhau (tƣơng hỗ vì chuyên biệt hóa là mặt đối lập với tổng quát hóa). Và những quan
hệ này là phân cấp vì chúng tạo ra cây quan hệ. Chẳng hạn, quan hệ is-a (là một) là
một sự chuyên biệt hóa. Ví dụ, khi ta nói “Sơn dƣơng là một loài động vật, đại bàng
cũng là một loài động vật ”, thì có nghĩa là: “Sơn dƣơng và đại bàng là những loại
động vật chuyên biệt, chúng có những đặc điểm chung của động vật và ngoài ra

77
chúng có những đặc điểm phân biệt nhau”. Và nhƣ vậy, động vật là tổng quát hóa của
sơn dƣơng và đại bàng; sơn dƣơng và đại bàng là chuyên biệt hóa của động vật.
Trong C#, quan hệ chuyên biệt hóa, tổng quát hoá thƣờng đƣợc thể hiện thông
qua sự kế thừa. Bởi vì, thông thƣờng, khi hai lớp chia sẻ chức năng, dữ liệu với nhau,
ta trích ra các phần chung đó và đƣa vào lớp cơ sở chung để có thể nâng cao khả năng
sử dụng lại các mã nguồn chung, cũng nhƣ dễ dàng quản lý mã nguồn.
6.1.2. Khái niệm kế thừa
 Kế thừa từ các lớp có từ trƣớc.
Lớp cơ sở LỚP CHA CSINHVIEN
 Ích lợi: có thể tận dụng lại: (Base class) (Super class)

- Các thuộc tính chung


-Các hàm có thao tác tƣơng tự Lớp dẫn xuất LỚP CON CSINHVIENCNT
(Derived class) (Sub class) T
6.1.3. Thực thi kế thừa
Kế thừa là cơ chế cho phép định nghĩa một lớp mới (còn gọi là lớp dẫn xuất,
drived class) dựa trên một lớp đã có sẵn (còn gọi là lớp cơ sở, base class). Lớp dẫn
xuất có hầu hết các thành phần giống nhƣ lớp cơ sở (bao gồm tất cả các phƣơng thức
và biến thành viên của lớp cơ sở, trừ các phƣơng thức private, phƣơng thức khởi tạo,
phƣơng thức hủy và phƣơng thức tĩnh). Nói cách khác, lớp dẫn xuất sẽ kế thừa hầu
hết các thành viên của lớp cơ sở.
Một điều cần chú ý rằng, lớp dẫn xuất vẫn đƣợc kế thừa các thành phần dữ liệu
private của lớp cơ sở nhƣng không đƣợc phép truy cập trực tiếp (truy cập gián tiếp
thông qua các phƣơng thức của lớp cơ sở).
Cú pháp định nghĩa lớp dẫn xuất:
class TênLớpCha
{
Thuộc tính và phương thức của lớp cha
};
class TênLớpDẫnXuất : TênLớpCha
{
Thuộc tính và phương thức bổ sung của lớp dẫn xuất
};
Có 2 cách để định nghĩa hành động bổ sung cho phƣơng thức đã có sẵn ở lớp
cha trong lớp dẫn xuất (phƣơng thức lớp dẫn xuất trùng tên với phƣơng thức
lớp cha)
78
 Dùng từ khóa new
 Dùng từ khóa virtual và override
Dùng từ khóa new
class COSO
{
protected kiểu data1;
protected kiểu data2;
public void Method1()
{}
public void Method2()
{}
}
class DANXUAT : COSO
{
private kiểu data3;
public new void Method1()
{}
public new void Method2()
{}
}
Dùng từ khóa virtual và override
class COSO
{
protected kiểu data1;
protected kiểu data2;
public virtual void Method1()
{}
public virtual void Method2()

79
{}
}
class DANXUAT : COSO
{
private kiểu data3;
public override void Method1()
{}
public override void Method2()
{}
}
Ví dụ 6.1:
Nếu ta định nghĩa lớp ClassA và ClassB kế thừa từ ClassA nhƣ sau thì câu lệnh
x = x -1 sẽ bị báo lỗi:
ClassA.x is inaccessible due to its protection level.
class ClassA
{
int x = 5;
public void XuatX()
{
Console.WriteLine("{0}", x);
}
}
class ClassB: ClassA
{
public void GiamX()
{
x = x - 1; // Loi.
}
}

80
Nếu sửa lại khai báo int x = 5; thành protected int x = 5; hoặc public int x = 5;
thì sẽ không còn lỗi trên vì thành phần protected hoặc public của lớp cơ sở có thể
đƣợc truy cập trực tiếp trong lớp dẫn xuất (nhƣng không đƣợc truy cập trong một
phƣơng thức không thuộc lớp cơ sở và lớp dẫn xuất).
6.1.4. Gọi phƣơng thức tạo lập của lớp cơ sở
Vì lớp dẫn xuất không thể kế thừa phƣơng thức tạo lập của lớp cơ sở nên một
lớp dẫn xuất phải thực thi phƣơng thức tạo lập riêng của mình. Nếu lớp cơ sở có một
phƣơng thức tạo lập mặc định (tức là không có phƣơng thức tạo lập hoặc phƣơng thức
tạo lập không có tham số) thì phƣơng thức tạo lập của lớp dẫn xuất đƣợc định nghĩa
nhƣ cách thông thƣờng. Nếu lớp cơ sở có phƣơng thức tạo lập có tham số thì lớp dẫn
xuất cũng phải định nghĩa phƣơng thức tạo lập có tham số theo cú pháp sau:
TênLớpCon(ThamSốLớpCon): TênLớpCơSở(ThamSốLớpCha)
{
// Khởi tạo giá trị cho các thành phần của lớp dẫn xuất
}
Chú ý: ThamSốLớpCon phải bao ThamSốLớpCha.
6.1.5. Định nghĩa phiên bản mới trong lớp dẫn xuất
Qua những phần trên chúng ta có nhận xét rằng, khi cần định nghĩa hai lớp mà
chúng có chung một vài đặc trƣng, chức năng thì những thành phần đó nên đƣợc đặt
vào một lớp cơ sở. Sau đó hai lớp này sẽ kế thừa từ lớp cơ sở đó và bổ sung thêm các
thành phần của riêng chúng. Ngoài ra, lớp dẫn xuất còn có quyền định nghĩa lại các
phƣơng thức đã kế thừa từ lớp cơ sở nhƣng không còn phù hợp với nó nữa.
Lớp dẫn xuất kế thừa hầu hết các thành viên của lớp cơ sở vì vậy trong bất kỳ
phƣơng thức nào của lớp dẫn xuất ta có thể truy cập trực tiếp đến các thành viên này
(mà không cần thông qua một đối tƣợng thuộc lớp cơ sở). Tuy nhiên, nếu lớp dẫn xuất
cũng có một thành phần X (biến hoặc phƣơng thức) nào đó trùng tên với thành viên
thuộc lớp cơ sở thì trình biên dịch sẽ có cảnh báo dạng nhƣ sau:
“keyword new is required on „LớpDẫnXuất.X‟ because
it hides inherited member on „LớpCơSở.X „”
bởi vì, trong lớp dẫn xuất, khi khai báo một thành phần trùng tên lớp thành
phần trong lớp cơ sở thì trình biên dịch hiểu rằng ngƣời dùng muốn che dấu các thành
viên của lớp cơ sở và yêu cầu ngƣời dùng đặt từ khóa new ngay câu lệnh khai báo
thành phần đó. Điều này có tác dụng che dấu thành phần kế thừa đó đối với các
phƣơng thức bên ngoài lớp dẫn xuất. Nếu phƣơng thức của lớp dẫn xuất muốn truy

81
cập đến thành phần X của lớp cơ sở thì phải sử dụng từ khóa base theo cú pháp:
base.X.
6.1.6. Điều khiển truy xuất
Một tham chiếu thuộc lớp cơ sở có thể trỏ đến một đối tƣợng thuộc lớp dẫn
xuất nhƣng nó chỉ đƣợc phép truy cập đến các thành phần đƣợc khai báo trong lớp cơ
sở. Với các lớp XeHoi, XeCar nhƣ trên, ta có thể định nghĩa hàm Main() nhƣ sau:
public static void Main()
{
XeCar c = new XeCar(150,"49A-4444", "Toyota", 24);
c.Xuat();
Console.WriteLine();
Console.WriteLine("Tham chieu cua lop co so XeHoi co
the tro den doi tuong thuoclop dan xuat XeCar");
Console.WriteLine("Nhung chi co the goi ham xuat tuong
ung voi XeHoi");
XeHoi h = c;
h.Xuat();
Console.ReadLine();
}
Kết quả chạy chƣơng trình:

Khi gọi lệnh


c.Xuat();
trình biên dịch gọi phƣơng thức Xuat() của lớp XeCar và xuất các thông tin:
hàng sản xuất (Toyota), biển số (49A-444), tốc độ tối đa (150 km/h), 24 chỗ ngồi.
Sau đó một đối tƣợng h thuộc lớp cơ sở XeHoi trỏ đến đối tƣợng c thuộc lớp
dẫn xuất :
XeHoi h = c;
82
Khi gọi lệnh
h.Xuat();
trình biên dịch sẽ thực hiện phƣơng thức Xuat() của lớp XeHoi nên chỉ xuất các
thông tin: hàng sản xuất (Toyota), biển số (49A-444), tốc độ tối đa (150 km/h).
6.2. Đa hình
6.2.1 Kiểu đa hình
Hai đặc điểm mạnh nhất của kế thừa đó là khả năng sử dụng lại mã chƣơng
trình và đa hình (polymorphism). Đa hình là ý tƣởng “sử dụng một giao diện chung
cho nhiều phƣơng thức khác nhau”, dựa trên phƣơng thức ảo (virtual method) và cơ
chế liên kết muộn (late binding). Nói cách khác, đây là cơ chế cho phép gởi một loại
thông điệp tới nhiều đối tƣợng khác nhau mà mỗi đối tƣợng lại có cách xử lý riêng
theo ngữ cảnh tƣơng ứng của chúng.
Đây là một kịch bản thực hiện tính đa hình:
Lớp của các đối tƣợng nút nhấn (button), nhãn (label), nút chọn (option button),
danh sách sổ xuống (combobox)… đều kế thừa từ lớp Window và đều có các phƣơng
thức vẽ (hiển thị) riêng của mình lên form. Một form có thể có nhiều đối tƣợng nhƣ
trên và đƣợc lƣu trong một danh sách (không cần biết các đối tƣợng trong danh sách
là ListBox hay Button… miễn là đối tƣợng đó là một thể hiện Window). Khi form
đƣợc mở, nó có thể yêu cầu mỗi đối tƣợng Window tự vẽ lên form bằng cách gởi
thông điệp vẽ đến từng đối tƣợng trong danh sách và các đối tƣợng này sẽ thực hiện
chức năng vẽ tƣơng ứng. Khi đó ta muốn form xử lý tất cả các đối tƣợng Window
theo đặc trƣng đa hình.
Để thực hiện đƣợc đa hình ta phải thực hiện các bƣớc sau:
1. Lớp cơ sở đánh dấu phƣơng thức ảo bằng từ khóa virtual hoặc abstract.
2. Các lớp dẫn xuất định nghĩa lại phƣơng thức ảo này (đánh dấu bằng từ khóa
override).
3. Vì tham chiếu thuộc lớp cơ sở có thể trỏ đến một đối tƣợng thuộc lớp dẫn
xuất và có thể truy cập hàm ảo đã định nghĩa lại trong lớp dẫn xuất nên khi thực thi
chƣơng trình, tùy đối tƣợng đƣợc tham chiếu này trỏ tới mà phƣơng thức tƣơng ứng
đƣợc gọi thực hiện. Nếu tham chiếu này trỏ tới đối tƣợng thuộc lớp cơ sở thì phƣơng
thức ảo của lớp cơ sở đƣợc thực hiện. Nếu tham chiếu này trỏ tới đối tƣợng thuộc lớp
dẫn xuất thì phƣơng thức ảo đã đƣợc lớp dẫn xuất định nghĩa lại đƣợc thực hiện.

83
6.2.2 Lớp Object
Tất cả các lớp trong C# đều đƣợc dẫn xuất từ lớp Object. Lớp Object là lớp gốc
trên cây phân cấp kế thừa, nó cung cấp một số phƣơng thức mà các lớp con có thể ghi
đè (override) nhƣ:
Phƣơng thức Ý nghĩa
Equals() Kiểm tra hai đối tƣợng có tƣơng đƣơng nhau
không
GetHashCode() Cho phép đối tƣợng cung cấp làm Hash riêng
để sử dụng trong kiểu tập hợp.
GetType() Trả về kiểu đối tƣợng.
ToString() Trả về kiểu đối tƣợng.
Finalize() Xóa đối tƣợng trong bộ nhớ.
MemberwiseClone() Tạo copy của đối tƣợng.

Trong những ví dụ trƣớc ta đã thực hiện việc ghi đè lên phƣơng thức ToString()
và Equals() của lớp Object.
6.3 Lớp trừu tƣợng(abstract)
Trong các ví dụ về phƣơng thức ảo trên, nếu lớp dẫn xuất không định nghĩa lại
phƣơng thức ảo của lớp cơ sở thì nó đƣợc kế thừa nhƣ một phƣơng thức thông
thƣờng. Tức là lớp dẫn xuất không nhất thiết phải định nghĩa lại phƣơng thức ảo.
Để bắt buộc tất cả các lớp dẫn xuất phải định nghĩa lại (override) một phƣơng
thức của lớp cơ sở ta phải đặt từ khóa abstract trƣớc phƣơng thức đó và phƣơng thức
đó đƣợc gọi là phƣơng thức trừu tƣợng. Trong phần thân của phƣơng thức trừu tƣợng
không có câu lệnh nào, nó chỉ tạo một tên phƣơng thức và đánh dấu rằng nó phải
đƣợc thi hành trong lớp dẫn xuất. Lớp chứa phƣơng thức trừu tƣợng đƣợc gọi là lớp
trừu tƣợng.
Cú pháp khai báo phƣơng thức trừu tƣợng:
abstract public void TênPhƣơngThức( );
Phƣơng thức trừu tƣợng phải đƣợc đặt trong lớp trừu tƣợng. Lớp trừu tƣợng có
từ khóa abstract đứng trƣớc.
Ví dụ 6.2:
Xây dựng lớp HinhHoc với phƣơng thức tính chu vi, diện tích là phƣơng thức
trừu tƣợng hoặc phƣơng thức ảo. Sau đó định nghĩa các lớp HinhChuNhat (hình chữ
84
nhật), HinhTron (hình tròn) kế thừa từ lớp HinhHọc với các thành phần dữ liệu và
phƣơng thức tính chu vi, diện tích cụ thể của từng loại đối tƣợng.
// lop hinh hoc (truu tuong)
abstract public class HinhHoc
{
abstract public double DienTich();
virtual public double ChuVi() { return 0;}
}
// lop hinh tron ke thua tu lop hinh hoc
public class HinhTron : HinhHoc
{
double _bankinh;
public double BanKinh
{
get{ return _bankinh;}
set{ _bankinh = value;}
}
public override double DienTich()
{
return _bankinh*_bankinh*3.1416;
}
public override double ChuVi()
{
return _bankinh*2*3.1416;
}
}
// lop hinh chu nhat ke thua tu lop hinh hoc

public class HinhChuNhat : HinhHoc


{
85
double _dai, _rong;
public double ChieuDai
{
get{ return _dai;}
set{ _dai = value;}
}
public double ChieuRong
{
get{ return _rong;}
set{ _rong = value;}
}
public override double DienTich(){ return _dai*_rong;}
public override double ChuVi(){return (_dai+_rong )*2;
}
}
class Tester
{
static void Main(string[] args)
{
HinhHoc h;
HinhTron t = new HinhTron();
t.BanKinh = 5;
Console.WriteLine("Thong tin ve hinh tron");
h = t;
Console.WriteLine("Chu vi hinh tron: {0} ",
h.ChuVi());
Console.WriteLine("Dien tich hinh tron:{0} ",
h.DienTich());
HinhChuNhat n = new HinhChuNhat();

86
n.ChieuDai = 4;
n.ChieuRong = 3;
h = n;
Console.WriteLine("Thong tin ve hinh chu nhat ");
Console.WriteLine("Chu vi hinh chu nhat:{0}",
h.ChuVi());
Console.WriteLine("Dien tich hinh chu nhat:{0}",
h.DienTich());
Console.ReadLine();
}
}
Trong lớp HinhHoc ở ví dụ trên, ta khai báo phƣơng thức tính diện tích là một
phƣơng thức trừu tƣợng (không có phần cài đặt mã của phƣơng thức vì chƣa biết đối
tƣợng hình thuộc dạng nào nên không thể tính diện tích của nó). Nhƣ vậy, lớp
HinhHoc là một lớp trừu tƣợng.
abstract public double DienTich();
Trong lớp trên, ta cũng khai báo một phƣơng thức tính chu vi là phƣơng thức
ảo với mục đích minh họa rằng trong lớp trừu tƣợng có thể có phƣơng thức ảo (hoặc
bất cứ một thành phần nào khác của một lớp). Vì đây là một phƣơng thức không trừu
tƣợng nên phải có phần cài đặt mã bên trong thân phƣơng thức.
Các lớp HinhChuNhat, HinhTron kế thừa từ lớp HinhHoc nên chúng buộc phải
cài đặt lại phƣơng thức tính diện tích.
Trong hàm Main(), ta tạo ra đối tƣợng n thuộc lớp HinhChuNhat và đối tƣợng t
thuộc lớp HinhTron. Khi tham chiếu h thuộc lớp HinhHoc trỏ tới đối tƣợng n (tƣơng
tự với đối tƣợng t), nó có thể gọi đƣợc phƣơng thức tính diện tích của lớp
HinhChuNhat (tƣơng tự lớp HinhTron), điều này chứng tỏ phƣơng thức trừu tƣợng
cũng có thể dùng với mục đích đa hình.
Chú ý: Phân biệt giữa từ khóa new và override
• Từ khóa override dùng để định nghĩa lại (ghi đè) phƣơng thức ảo (virtual)
hoặc phƣơng thức trừu tƣợng (abstract) của lớp cơ sở, nó đƣợc dùng với mục đích đa
hình.
• Từ khóa new để che dấu thành viên của lớp cơ sở trùng tên với thành viên của
lớp dẫn xuất.
87
88
Bài tập
1. Hãy xây dựng cây phân cấp các lớp đối tƣợng sau: XeToyota, XeDream,
XeSpacy, Xe_BMW, XeFiat, XeDuLich, Xe_May, Xe?
2. Hãy xây dựng các lớp đối tƣợng trong câu hỏi 1, thiết lập các quan hệ kế thừa
dựa trên cây kế thừa mà bạn xây dựng. Mỗi đối tƣợng chỉ cần một thuộc tính là
myNane để cho biết tên của nó (nhƣ XeToyota thì myName là “Toi la Toyota”...).
Các đối tƣợng có phƣơng thức Who() cho biết giá trị myName của nó. Hãy thực thi sự
đa hình trên các lớp đó. Cuối cùng tạo một lớp Tester với hàm Main() để tạo một
mảng các đối tƣợng Xe, đƣa từng đối tƣợng cụ thể vào mảng đối tƣợng Xe, sau đó
cho lặp từng đối tƣợng trong mảng để nó tự giới thiệu tên (bằng cách gọi hàm Who()
của từng đối tƣợng).
3. Xây dựng các lớp đối tƣợng hình học nhƣ: điểm, đoạn thẳng, đƣờng tròn, hình
chữ nhật, hình vuông, tam giác, hình bình hành, hình thoi. Mỗi lớp có các thuộc tính
riêng để xác định đƣợc hình vẽ biểu diễn của nó nhƣ đoạn thẳng thì có điểm đầu,
điểm cuối.... Mỗi lớp thực thi một phƣơng thức Draw() phủ quyết phƣơng thức
Draw() của lớp cơ sở gốc của các hình mà nó dẫn xuất. Hãy xây dựng lớp cơ sở của
các lớp trên và thực thi đa hình với phƣơng thức Draw(). Sau đó tạo lớp Tester cùng
với hàm Main() để thử nghiệm đa hình giống nhƣ bài tập 1 ở trên.

89
Bài 7
THỰC THI GIAO DIỆN
Mã bài: MĐ33-07
Giới thiệu:
Giao diện là một dạng của lớp trừu tƣợng đƣợc sử dụng với mục đích hỗ trợ
tính đa hình. Trong giao diện không có bất cứ một cài đặt nào, chỉ có nguyên mẫu của
các phƣơng thức, chỉ mục, thuộc tính mà một lớp khác khi kế thừa nó thì phải có cài
đặt cụ thể cho các thành phần này (tức là lớp kế thừa giao diện tuyên bố rằng nó hỗ
trợ các phƣơng thức, thuộc tính, chỉ mục đƣợc khai báo trong giao diện).
Khi một lớp kế thừa một giao diện ta nói rằng lớp đó thực thi (implement) giao
diện.
Mục tiêu:
 Trình bày đƣợc các kiến thức về thực thi giao diện;
 Có đƣợc kiến thức và kỹ năng về các thành phần và các mở rộng của giao diện;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
7.1 Thực thi giao diện (Interface)
Giao diện là một dạng của lớp trừu tƣợng đƣợc sử dụng với mục đích hỗ trợ
tính đa hình. Trong giao diện không có bất cứ một cài đặt nào, chỉ có nguyên mẫu của
các phƣơng thức, chỉ mục, thuộc tính mà một lớp khác khi kế thừa nó thì phải có cài
đặt cụ thể cho các thành phần này (tức là lớp kế thừa giao diện tuyên bố rằng nó hỗ
trợ các phƣơng thức, thuộc tính, chỉ mục đƣợc khai báo trong giao diện).
Khi một lớp kế thừa một giao diện ta nói rằng lớp đó thực thi (implement) giao
diện.
 Thực thi giao diện
Ta dùng từ khóa interface để khai báo một giao diện với cú pháp sau:
[MứcĐộTruyCập] interface TênGiaoDiện [:CácGiaoDiệnCơSở]
{
Nội dung
}
MứcĐộTruyCập: public hoặc internal.
CácGiaoDiệnCơSở: danh sách các interface khác mà nó kế thừa.
90
Về mặt cú pháp, một giao diện giống nhƣ một lớp chỉ có phƣơng thức trừu
trƣợng.
Nó có thể chứa khai báo của phƣơng thức, thuộc tính, chỉ mục, sự kiện (events)
nhƣng không đƣợc chứa biến dữ liệu. Khi kế thừa một giao diện ta phải thực thi mọi
phƣơng thức, thuộc tính,… của giao diện.
Chú ý:
• Mặc định, tất cả các thành phần khai báo trong giao diện đều là public. Nếu
có từ khóa public đứng trƣớc sẽ bị báo lỗi. Các thành phần trong giao diện chỉ là các
khai báo, không có phần cài đặt mã.
• Một lớp có thể kế thừa một lớp khác đồng thời kế thừa nhiều giao diện.
 Hủy đối tƣợng
Ngôn ngữ C# cung cấp cơ chế thu dọn rác tự động (garbage collection) và do
vậy không cần phải khai báo tƣờng minh các phƣơng thức hủy. Tuy nhiên, khi làm
việc với các đoạn mã không đƣợc quản lý thì cần phải khai báo tƣờng minh các
phƣơng thức hủy để giải phóng các tài nguyên. C# cung cấp ngầm định một phƣơng
thức để thực hiện điều khiển công việc này, phƣơng thức đó là Finalize() hay còn gọi
là bộ kết thúc. Phƣơng thức Finalize() này sẽ đƣợc gọi một cách tự động bởi cơ chế
thu dọn khi đối tƣợng bị hủy.
Phƣơng thức Finalize() chỉ giải phóng các tài nguyên mà đối tƣợng nắm giữ, và
không tham chiếu đến các đối tƣợng khác. Nếu với những đoạn mã bình thƣờng tức là
chứa các tham chiếu kiểm soát đƣợc thì không cần thiết phải tạo và thực thi phƣơng
thức Finalize(). Ta chỉ làm điều này khi xử lý các tài nguyên không kiểm soát đƣợc.
Ta không bao giờ gọi một phƣơng thức Finalize() của một đối tƣợng một cách
trực tiếp, ngoại trừ gọi phƣơng thức này của lớp cơ sở khi ở bên trong phƣơng thức
Finalize() của lớp đang định nghĩa. Trình thu dọn sẽ tự động thực hiện việc gọi
Finalize() cho chúng ta.
a) Cách Finalize thực hiện
Bộ thu dọn duy trì một danh sách những đối tƣợng có phƣơng thức Finalize().
Danh sách này đƣợc cập nhật mỗi lần khi đối tƣợng cuối cùng đƣợc tạo ra hay
bị hủy. Khi một đối tƣợng trong danh sách kết thúc của bộ thu dọn đƣợc chọn đầu
tiên. Nó sẽ đƣợc đặt vào hàng đợi (queue) cùng với những đối tƣợng khác đang chờ
kết thúc. Sau khi phƣơng thức Finalize() của đối tƣợng thực thi bộ thu dọn sẽ gom lại
đối tƣợng và cập nhật lại danh sách hàng đợi, cũng nhƣ là danh sách kết thúc đối
tƣợng.

91
b) Bộ hủy của C#
Ta khai báo một phƣơng thức hủy trong C# nhƣ sau:
Class1()
{
}
Tuy nhiên, trong ngôn ngữ C# thì cú pháp khai báo trên là một shortcut liên kết
đến một phƣơng thức kết thúc Finalize() đƣợc kết với lớp cơ sở, do vậy khi viết
~Class1()
{
// Thực hiện một số công việc
}
Cũng tương tự như viết :
Class1.Finalize()
{
// Thực hiện một số công việc
base.Finalize();
}
Do sự tƣơng tự nhƣ trên nên khả năng dẫn đến sự lộn xộn nhầm lẫn là không
tránh khỏi, nên ta phải tránh viết các phƣơng thức hủy và viết các phƣơng thức
Finalize() tƣờng minh nếu có thể đƣợc.
c) Phƣơng thức Dispose
Nhƣ chúng ta đã biết thì việc gọi một phƣơng thức kết thúc Finalize() trong C#
là không hợp lệ, vì phƣơng thức này đƣợc thực hiện một cách tự động bởi bộ thu dọn
rác. Nếu muốn xử lý các tài nguyên không kiểm soát (nhƣ xử lý các handle của tập
tin), đóng hay giải phóng nhanh chóng bất cứ lúc nào thì ta nên thực thi giao diện
IDisposable. Giao diện IDisposable yêu cầu những lớp thực thi nó định nghĩa một
phƣơng thức tên là Dispose() để thực hiện công việc dọn dẹp. Ý nghĩa của phƣơng
thức Dispose() là cho phép chƣơng trình thực hiện các công việc dọn dẹp hay giải
phóng tài nguyên mong muốn mà không phải chờ cho đến khi phƣơng thức Finalize()
đƣợc gọi.
Khi chúng ta cung cấp một phƣơng thức Dispose() thì phải ngƣng bộ thu dọn
gọi phƣơng thức Finalize() trong đối tƣợng của chúng ta. Để ngƣng bộ thu dọn, chúng
ta gọi một phƣơng thức tĩnh của lớp GC (garbage collector) là GC.SuppressFinalize()
92
và truyền tham số là tham chiếu this của đối tƣợng. Và sau đó phƣơng thức Finalize()
sử dụng để gọi phƣơng thức Dispose() nhƣ đoạn mã sau:
public void Dispose()
{
// Thực hiện công việc dọn dẹp
// Yêu cầu bộ thu dọn GC trong thực hiện kết thúc
GC.SuppressFinalize( this );
}
public override void Finalize()
{
Dispose();
base.Finalize();
}
Ví dụ 7.1:
using System;
public class Mang: IDisposable
{
int []Data;
public int Size;
public Mang(int n)
{
Data = new int[n];
Size = n;
}
public void Dispose()
{
// Thuc hien viec don dep
Console.WriteLine("Huy mang");
Data =new int [0];
Size = 0;
93
// yeu cau bo thu don GC thuc hien ket thuc
GC.SuppressFinalize( this );
}
public void Nhap()
{
for( int i =0; i < Size; i++)
{
Console.WriteLine("nhap phan tu thu {0}
",i);
Data[i] =
int.Parse(Console.ReadLine());
}
}
public void Xuat()
{
for(int i = 0; i < Size; i++)
{
Console.Write("{0} \t",Data[i]);
}
}
}
class App
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Test()
{

94
Mang A = new Mang(5);
A.Nhap();
A.Xuat();
A.Dispose();
}
static void Main(string[] args)
{
Test();
Console.ReadLine();
}
}
d) Phƣơng thức Close
Khi xây dựng các đối tƣợng, chúng ta muốn cung cấp cho ngƣời sử dụng
phƣơng thức Close(), vì phƣơng thức Close() có vẻ tự nhiên hơn phƣơng thức
Dispose() trong các đối tƣợng có liên quan đến xử lý tập tin. Ta có thể xây dựng
phƣơng thức Dispose() với thuộc tính là private và phƣơng thức Close() với thuộc
tính public. Trong phƣơng thức Close() đơn giản là gọi thực hiện phƣơng thức
Dispose().
e) Câu lệnh using
Khi xây dựng các đối tƣợng chúng ta không thể chắc chắn đƣợc rằng ngƣời sử
dụng có thể gọi hàm Dispose(). Và cũng không kiểm soát đƣợc lúc nào thì bộ thu dọn
GC thực hiện việc dọn dẹp. Do đó để cung cấp khả năng mạnh hơn để kiểm soát việc
giải phóng tài nguyên thì C# đƣa ra cú pháp chỉ dẫn using, cú pháp này đảm bảo
phƣơng thức Dispose() sẽ đƣợc gọi sớm nhất có thể đƣợc. Ý tƣởng là khai báo các đối
tƣợng với cú pháp using và sau đó tạo một phạm vi hoạt động cho các đối tƣợng này
trong khối đƣợc bao bởi dấu ({}). Khi khối phạm vi này kết thúc, thì phƣơng thức
Dispose() của đối tƣợng sẽ đƣợc gọi một cách tự động.
using System.Drawing;
class Tester
{
public static void Main()
{

95
using ( Font Afont = new Font(“Arial”,10.0f))
{
// Đoạn mã sử dụng AFont
.......
}// Trình biên dịch sẽ gọi Dispose để giải phóng AFont
Font TFont = new Font(“Tahoma”,12.0f);
using (TFont)
{
// Đoạn mã sử dụng TFont
.......
}// Trình biên dịch gọi Dispose để giải phóng TFont
}
}
7.1.1. Thực thi nhiều giao diện
Các lớp có thể thực thi nhiều giao diện, đây là cách để thực hiện đa kế thừa
trong C#.
Ví dụ 7.2:
Tạo một giao diện tên là Istoreable với các phƣơng thức Write() để lƣu nội
dung của đối tƣợng vào file và phƣơng thức Read() để đọc dữ liệu từ file. Sau đó ta
tạo lớp Document thực thi giao diện Istorable để các đối tƣợng thuộc lớp này có thể
đọc từ cơ sở dữ liệu hoặc lƣu trữ vào cơ sở dữ liệu. Việc mở file đƣợc thực hiện thông
qua đối tƣợng fs thuộc lớp FileStream, việc ghi và đọc file thông qua đối tƣợng thuộc
các lớp StreamWriter và StreamReader. Đồng thời lớp Document cũng thực thi một
giao diện khác tên là Iencryptable, giao diện này có hai phƣơng thức là mã hóa
(Encrypt( ))và giải mã (Decrypt( )):
using System;
using System.IO;
// Khai bao giao dien
interface IStorable
{
// mac dinh cac khai bao phuong thuc la public. Khong cai dat gi

96
void Read(string FileName);
void Write(string FileName);
string Data { get; set; } }
interface IEncryptable
{
void Encrypt( );
void Decrypt( );
}
// Lop Document thuc thi giao dien IStorable
public class Document : IStorable, IEncryptable
{
string S;
public Document(string str)
{
S = str;
}

// thuc thi phuong thuc Read cua giao dien IStorable


public void Read( string FileName)
{
//Mo fiel de doc
FileStream fs = new
FileStream(FileName,FileMode.Open);
//tao luong doc du lieu
StreamReader sr = new StreamReader(fs);
//Doc tung dong cho den khi gia tri doc duoc la
null
string text;
S= "";

97
while((text = sr.ReadLine())!=null)
{
S = S + text;
}
//Dong luong va dong file
sr.Close();
fs.Close();
}
// thuc thi phuong thuc Write cua giao dien IStorable
public void Write(string FileName)
{
//Mo file de ghi du lieu
FileStream fs ;
fs = new
fileStream(FileName,FileMode.OpenOrCreate);
//Tao luong ghi du lieu vao file
StreamWriter sw = new StreamWriter(fs);
//ghi chuoi S ra file
sw .WriteLine(S);
//dong luong va dong file
sw .Close();
fs.Close();
}
// thuc thi thuoc tinh Data cua giao dien IStorable
public string Data
{
get { return S; }
set { S = value; }
}

98
// thuc thi phuong thuc Encrypt cua giao dien IEncryptable
public void Encrypt()
{
string KQ ="";
for (int i = 0 ; i< S.Length; i++)
KQ = KQ + (char)((int)S[i] + 5);
S = KQ;
}
// thuc thi phuong thuc Encrypt cua giao dien IEncryptable
public void Decrypt()
{
string KQ ="";
for (int i = 0 ; i< S.Length; i++)
KQ= KQ + (char)((int)S[i] - 5);
S = KQ;
}
}
// Thu nghiem chuong chinh
public class Tester
{
static void Main( )
{
string FileName = "F:\\datafile.txt";
Document doc = new Document("THIEU NU la viet tat cua tu THIEU NU
TINH");
doc.Write(FileName);
doc.Read(FileName);
Console.WriteLine("Du lieu trong file: {0}",
doc.Data);
Console.WriteLine("Du lieu sau khi ma hoa:");
99
doc.Encrypt();
Console.WriteLine(doc.Data);
Console.WriteLine("Du lieu sau khi giai ma:");
doc.Decrypt();
Console.WriteLine(doc.Data);
Console.ReadLine();
}
}
Vì giao diện là một dạng lớp cơ sở nên ta có thể cho một tham chiếu kiểu giao
diện trỏ tới đối tƣợng thuộc lớp thực thi giao diện và gọi những phƣơng thức cần thiết.
Chẳng hạn, trong hàm Main() trên ta có thể thay câu lệnh: doc.Read(FileName);
bằng hai câu lệnh sau:
IStorable isDoc = doc; //(IStorable) doc;
isDoc.Read(FileName);
7.1.2. Mở rộng giao diện
Ta cũng có thể mở rộng giao diện bằng cách bổ sung những thành viên hay
phƣơng thức mới. Chẳng hạn, ta có thể mở rộng giao tiếp IStorable thành
IStorableAndCompressible bằng cách kế thừa từ giao tiếp Istorable bổ sung các
phƣơng thức nén file và giải nén file:
interface IStorableAndCompressible : IStorable
{
// bo sung them phuong thuc nen va giai nen
void Compress( );
void Decompress( );
}
7.1.3. Kết hợp giao diện
Thay vì lớp Document thực thi hai giao diện IStorable, IEncryptable , ta có thể
tạo kết hợp hai giao diện này thành một giao diện mới là IStorableAndEncryptable.
Sau đó lớp Document thực thi giao diện mới này:
interface IstorableAndEncryptable: IStorable, IEncryptable
{

100
//Có thể bổ sung thêm các phương thức, thuộc tính… mới
}
public class Document : IStorableAndEncryptable
{

}
7.2 Kiểm tra đối tƣợng có hỗ trợ giao diện hay không bằng toán tử is
Ta có thể hỏi một đối tƣợng có hỗ trợ giao tiếp hay không để gọi các phƣơng
thức thích hợp bằng cách dùng toán tử is. Nếu đối tƣợng không hỗ trợ giao diện
mà ta cố tình ép kiểu thì có thể xảy ra biệt lệ (lỗi khi thực thi chƣơng trình).
Ví dụ 7.3: Kiểm tra đối tƣợng doc có hỗ trợ giao diện IStorable hay không, nếu
có thì ép sang kiểu IStorable và gọi phƣơng thức Read().
static void Main( )
{
Document doc = new Document("Test Document");
// chi ep sang kieu Istorable neu doi tuong co ho tro
if (doc is IStorable)
{
IStorable isDoc = (IStorable) doc;
isDoc.Read( );
}
}
7.3 Thực thi các giao diện tƣờng minh: Icomparer, IComparable (giao diện so
sánh) và ArrayList
• Thuộc name space System.Conllections
• Khi thi công giao diện IComparer cần cài đặt hàm mô tả sự so sánh hai đối
tƣợng obj1 và obj2: int Compare(Objerct obj1, Object obj2)
• Khi thi công giao diện IComparable cần cài đặt hàm mô tả sự so sánh của đối
tƣợng hiện tại với một đối tƣợng obj2 khác. int CompareTo(Object obj2)
• ArrayList có các chức năng của cả mảng và danh sách liên kết nhƣ:
Một vấn đề hạn chế của kiểu dữ liệu mảng là kích thƣớc cố định. Nếu chúng ta

101
không biết trƣớc số lƣợng đối tƣợng trong một mảng sẽ đƣợc lƣu giữ, thì sẽ khó khăn
vì có thể chúng ta khai báo kích thƣớc của mảng quá nhỏ (vƣợt quá kích thƣớc lƣu trữ
của mảng) hoặc là kích thƣớc quá lớn (dẫn đến lãng phí bộ nhớ). Chƣơng trình của
chúng ta có thể hỏi ngƣời dùng về kích thƣớc, hoặc thu những input từ trong một web
site.Tuy nhiên việc xác định số lƣợng của đối tƣợng trong những session có tính chất
tƣơng tác động là không thích hợp. Việc sử dụng mảng có kích thƣớc cố định là
không thích hợp cũng nhƣ là chúng ta không thể đoán trƣớc đƣợc kích thƣớc của
mảng cần thiết.
Lớp ArrayList là một kiểu dữ liệu mảng mà kích thƣớc của nó đƣợc gia tăng
một cách động theo yêu cầu. ArrayList cung cấp một số phƣơng thức và những thuộc
tính cho những thao tác liên quan đến mảng. Một vài phƣơng thức và thuộc tính quan
trọng của ArrayList đƣợc liệt kê trong bảng nhƣ sau
Tên Loại Ý nghĩa
Count Thuộc Số phần tử hiện có
tính
Add Hàm Thêm một phần tử vào cuối

BinarySearch Hàm Tìm nhị phân


Contains Hàm Kiểm một phần tử có thuộc Array List hay
không
Clear Hàm Xóa các phần tử
IndexOf Hàm Trả về vị trí đầu tiên của phần tử cần tìm
LastIndexOf Hàm Trả về vị trí cuối cùng của phần tử cần tìm
Insert Hàm Chèn 1 phần tử vào 1 vị trí nào đó
Remove Hàm Loại bỏ 1 phần tử
RemoveAt Hàm Loại bỏ 1 phần tử tại một vị trí nào đó
Reverse Hàm Đảo chiều
Sort Hàm Sắp xếp
• ArrayList cho phép sắp xếp bất cứ đối tƣợng nào cài đặt giao diện
Icomparable.
Ví dụ 7.4 :
Khai báo và cấp phát đối tƣợng ArrayList AL:
ArrayList AL = new ArrayList();
102
Thêm một phần tử có giá trị 5 vào cuối AL:
AL.Add(5);
Chèn giá trị 6 vào đầu AL:
AL.Insert(0, 6);
Xuất phần tử thứ 2 trong AL:
Console.WriteLine(“{0}”, AL[1]);

103
Bài tập
1. Hãy viết một giao diện khai báo một thuộc tính ID chứa chuỗi giá trị. Viết một lớp
Employee thực thi giao diện đó.
2. Đọan mã nguồn sau đây có lỗi hãy sử lỗi và hãy cho biết tại sao có lỗi này. Sau khi
sửa lỗi hãy viết một lớp Circle thực thi giao diện này?
public interface Idimensions
{
long width; long height; double Area(); double Circumference(); int Side();
}
3. Chƣơng trình sau đây có lỗi hãy sử lỗi, biên dịch và chạy lại chƣơng trình? Giải
thích tại sao chƣơng trình có lỗi.
using System;
interface IPoint {
// Property signatures: int x {
get;
set;
}
int y {
get;
set;
}
}
class MyPoint : IPoint
{
private int myX;
private int myY;
public MyPoint(int x, int y)
{
myX =x;
myY = y;

104
}
// Property implementation: public int x{
get
{
return myX;
}
set
{
myX = value;
}
}
public int y
{
get
{
return myY;
}
set
{
myY = value;
}
}
}
Class MainClass
{
private static void PrintPoint(IPoint p)
{
Console.WriteUne("x={0}, y={1}", p.x, p.y);
}

105
public static void Main()
{
MyPoint p = new MyPoint(2,3); Console.Write("My Point: ");
PrintPoint(p);
IPoint p2 = new IPoint();
PrintPoint(p2);
}
}
4. Xây dựng một giao diện IDisplay có khai báo thuộc tính Name kiểu chuỗi. Hãy
viết hai lớp Dog và Cat thực thi giao diện IDisplay, cho biết thuộc tính Name là tên
của đối tƣợng.

106
Bài 8
CƠ CHẾ ỦY QUYỀN - SỰ KIỆN
Mã bài: MĐ33-08
Giới thiệu:
Trong lập trình chúng ta thƣờng đối diện với tình huống là khi chúng ta
muốn thực hiện một hành động nào đó, nhƣng hiện tại thì chƣa xác định đƣợc
chính xác phƣơng thức hay sự kiện trong đối tƣợng. Ví dụ nhƣ một nút lệnh
button biết rằng nó phải thông báo cho vài đối tƣợng khi nó đƣợc nhấn, nhƣng
nó không biết đối tƣợng hay nhiều đối tƣợng nào cần đƣợc thông báo. Tốt hơn
việc nối nút lệnh với đối tƣợng cụ thể, chúng ta có thể kết nối nút lệnh đến một
cơ chế ủy quyền và sau đó thì chúng ta thực hiện việc ủy quyền đến phƣơng
thức cụ thể khi thực thi chƣơng trình.
Trong thời kỳ đầu của máy tính, chƣơng trình đƣợc thực hiện theo trình tự
xử lý từng bƣớc tuần tự cho đến khi hoàn thành, và nếu ngƣời dùng thực hiện
một sự tƣơng tác thì sẽ làm hạn chế sự điều khiển hoạt động khác của chƣơng
trình cho đến khi sự tƣơng tác với ngƣời dùng chấm dứt.
Tuy nhiên, ngày nay với mô hình lập trình giao diện ngƣời dùng đồ họa
(GUI: Graphical User Interface) đòi hỏi một cách tiếp cận khác, và đƣợc biết
nhƣ là lập trình điều khiển sự kiện (event-driven programming). Chƣơng trình
hiện đại này đƣa ra một giao diện tƣơng tác với ngƣời dùng và sau đó thì chờ
cho ngƣời sử dụng kích hoạt một hành động nào đó. Ngƣời sử dụng có thể thực
hiện nhiều hành động khác nhau nhƣ: chọn các mục chọn trong menu, nhấn một
nút lệnh, cập nhật các ô chứa văn bản,...Mỗi hành động nhƣ vậy sẽ dẫn đến một
sự kiện (event) đƣợc sinh ra. Một số các sự kiện khác cũng có thể đƣợc xuất
hiện mà không cần hành động trực tiếp của ngƣời dùng. Các sự kiện này xuất
hiện do các thiết bị nhƣ đồng hồ của máy tính phát ra theo chu kỳ thời gian, thƣ
điện tử đƣợc nhận, hay đơn giản là báo một hành động sao chép tập tin hoàn
thành,...
Một sự kiện đƣợc đóng gói nhƣ một ý tƣởng “chuyện gì đó xảy ra‟ và chƣơng
trình phải đáp ứng lại với sự kiện đó. Cơ chế sự kiện và ủy quyền gắn liền với nhau, bởi
vì khi một sự kiện xuất hiện thì cần phải phân phát sự kiện đến trình xử lý sự kiện tƣơng
ứng. Thông trƣờng một trình xử lý sự kiện đƣợc thực thi trong C# nhƣ là một sự ủy
quyền.
Ủ y quyền cho phép một lớp có thể yêu cầu một lớp khác làm một công việc nào
đó, và khi thực hiện công việc đó thì phải báo cho lớp biết. Ủy quyền cũng co thể đƣợc
sử dụng để xác nhận những phƣơng thức chỉ đƣợc biết lúc thực thi chƣơng trình, và
chúng ta sẽ tìm hiểu kỹ vấn đề này trong phần chính của chƣơng.
Mục tiêu:
 Trình bày đƣợc kiến thức về cơ chế ủy quyền trong C# và cách sử lý sự kiện;
 Sử dụng cơ chế ủy quyền để thi hành với mảng ủy quyền;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính:
8.1 Ủy quyền (delegate)
Trong ngôn ngữ C#, ủy quyền là lớp đối tƣợng đầu tiên (first-class
object), đƣợc hỗ trợ đầy đủ bởi ngôn ngữ lập trình. Theo kỹ thuật thì ủy quyền
là kiểu dữ liệu tham chiếu đƣợc dùng để đóng gói một phƣơng thức với tham số
và kiểu trả về xác định. Chúng ta có thể đóng gói bất cứ phƣơng thức thích hợp
nào vào trong một đối tƣợng ủy quyền. Trong ngôn ngữ C++ và những ngôn
ngữ khác, chúng ta có thể làm đƣợc điều này bằng cách sử dụng con trỏ hàm
(function pointer) và con trỏ đến hàm thành viên. Không giống nhƣ con trỏ hàm
nhƣ trong C/C++, ủy quyền là hƣớng đối tƣợng, kiểu dữ liệu an toàn (type-safe)
và bảo mật.
Một điều thú vị và hữu dụng của ủy quyền là nó không cần biết và cũng
không quan tâm đến những lớp đối tƣợng mà nó tham chiếu tới. Điều cần quan
tâm đến những đối tƣợng đó là các đối mục của phƣơng thức và kiểu trả về phải
phù hợp với đối tƣợng ủy quyền khai báo.
Để tạo một ủy quyền ta dùng từ khóa delegate theo sau là kiểu trả về tên
phƣơng thức đƣợc ủy quyền và các đối mục cần thiết:
public delegate int WhichIsFirst(object obj1, object obj2);
Khai báo trên định nghĩa một ủy quyền tên là WhichlsFirst, nó sẽ đóng
gói bất cứ phƣơng thức nào lấy hai tham số kiểu object và trả về giá trị int.
Một khi mà ủy quyền đƣợc định nghĩa, chúng ta có thể đóng gói một phƣơng
thức thành viên bằng việc tạo một thể hiện của ủy quyền này, truyền vào trong
một phƣơng thức có khai báo kiểu trả về và các đối mục cần thiết.
Lƣu ý:
Từ phần này về sau chúng ta quy ƣớc có thể sử dụng qua lại giữa hai từ uỷ
quyền và delegate với nhau.
8.1.1 Sử dụng ủy quyền để xác nhận phƣơng thức lúc thực thi
Ủ y quyền nhƣ chúng ta đã biết là đƣợc dùng để xác định những loại phƣơng
thức có thể đƣợc dùng để xử lý các sự kiện và để thực hiện callback trong
chƣơng trình ứng dụng. Chúng cũng có thể đƣợc sử dụng để xác định các
phƣơng thức tĩnh và các instance của phƣơng thức mà chúng ta không biết trƣớc
cho đến khi chƣơng trình thực hiện.
Giả sử minh họa nhƣ sau, chúng ta muốn tạo một lớp chứa đơn giản gọi là
Pair lớp này lƣu giữ và sắp xếp hai đối tƣợng đƣợc truyền vào cho chúng. Tạm
thời lúc này chúng ta cũng không thể biết loại đối tƣợng mà một Pair lƣu giữ.
Nhƣng bằng cách tạo ra các phƣơng thức bên trong các đối tƣợng này thực hiện
việc sắp xếp và đƣợc ủy quyền, chúng ta có thể ủy quyền thực hiện việc sắp thứ
tự cho chính bản thân của đối tƣợng đó.
Những đối tƣợng khác nhau thì sẽ sắp xếp khác nhau. Ví dụ, một Pair chứa
các đối tƣợng đếm có thể đƣợc sắp xếp theo thứ tự số, trong khi đó một Pair nút
lệnh button có thể đƣợc sắp theo thứ tự alphabe tên của chúng. Mong muốn của
ngƣời tạo ra lớp Pair là những đối tƣợng bên trong của Pair phải có trách nhiệm
cho biết thứ tự của chúng cái nào là thứ tự đầu tiên và thứ hai. Để làm đƣợc điều
này, chúng ta phải đảm bảo rằng các đối tƣợng bên trong Pair phải cung cấp một
phƣơng thức chỉ ra cho chúng ta biết cách sắp xếp các đối tƣợng.
Chúng ta định nghĩa phƣơng thức yêu cầu bằng việc tạo một ủy quyền, ủy
quyền này định nghĩa ký pháp và kiểu trả về của phƣơng thức đối tƣợng (nhƣ
button) để cung cấp và cho phép Pair xác định đối tƣợng nào đến trƣớc đầu tiên
và đối tƣợng nào là thứ hai.
Lớp Pair định nghĩa một ủy quyền, WhichIsFirst. Phƣơng thức Sort sẽ lấy
một tham số là thể hiện của WhichIsFirst. Khi một đối tƣợng Pair cần biết thứ tự
của những đối tƣợng bên trong của nó thì nó sẽ yêu cầu ủy quyền truyền vào hai
đối tƣợng chứa trong nó nhƣ là tham số. Trách nhiệm của việc xác định thứ tự
của hai đối tƣợng đƣợc trao cho phƣơng thức đóng gói bởi ủy quyền.
Để kiểm tra thực hiện cơ chế ủy quyền, chúng ta sẽ tạo ra hai lớp, lớp Cat và
lớp Student. Hai lớp này có ít điểm chung với nhau, ngoại trừ cả hai thực thi
những phƣơng thức đƣợc đóng gói bởi WhichIsFirst. Do vậy cả hai đối tƣợng
này có thể đƣợc lƣu giữ bên trong của đối tƣợng Pair.
Trong chƣơng trình thử nghiệm này chúng ta sẽ tạo ra hai đối tƣợng Student
và hai đối tƣợng Cat và lƣu chúng vào mỗi một đối tƣợng Pair. Sau đó chúng ta
sẽ tạo những đối tƣợng ủy quyền để đóng gói những phƣơng thức của chúng,
những phƣơng thức này phải phù hợp với ký pháp và kiểu trả về của ủy quyền.
Sau cùng chúng ta sẽ yêu cầu những đối tƣợng Pair này sắp xếp những đối
tƣợng Student và Cat, ta làm từng bƣớc nhƣ sau:
Bắt đầu bằng việc tạo phƣơng thức khởi dựng Pair lấy hai đối tƣợng và đƣa
chúng vào trong từng mảng riêng:
public class Pair
{
// đưa vào 2 đối tượng theo thứ tự
public Pair( object firstObjectr, object secondObject)
{
thePair[0] = firstObject

thePair[1] = secondObject;
}
// biến lưu giữ hai đối tượng
private object[] thePair = new object[2];
}
Tiếp theo là chúng ta phủ quyết phương thức ToString() để chứa giá trị mới
của hai đối tượng mà Pair nắm giữ:
public override string ToStringO
{
// xuất thứ tự đối tượng thứ nhất trước đối tượng thứ hai return
thePair[0].ToString() +"," + thePair[1].ToString();
}
Bây giờ thì chúng ta đã có hai đối tƣợng bên trong của Pair và chúng ta có
thể xuất giá trị của chúng ra màn hình. Tiếp tục là chúng ta sẽ thực hiện việc sắp
xếp và in kết quả sắp xếp. Hiện tại thì không xác định đƣợc loại đối tƣợng mà
chúng ta có, do đó chúng ta sẽ ủy quyền quyết định thứ tự sắp xếp cho chính bản
thân các đối tƣợng mà Pair lƣu giữ bên trong. Do vậy, chúng ta yêu cầu rằng
mỗi đối tƣợng đƣợc lƣu giữ bên trong Pair thực hiện việc kiểm tra xem đối
tƣợng nào sắp trƣớc. Phƣơng thức này lấy hai tham số đối tƣợng và trả về giá trị
kiểu liệt kê: theFirstComeFirst nếu đối tƣợng đầu tiên đƣợc đến trƣớc và
theSecondComeFirst nếu giá trị thứ hai đến trƣớc.
Những phƣơng thức yêu cầu sẽ đƣợc đóng gói bởi ủy quyền WhichIsFirst
đƣợc định nghĩa bên trong lớp Pair:
public delegate comparison WhichIsFirst( object objl, object obj2);
Giá trị trả về là kiểu comparison đây là kiểu liệt kê:
public enum comparison {
theFirstComesFirst = 1, theSecondComesFirst = 2
}
Bất cứ phƣơng thức tĩnh nào lấy hai tham số đối tƣợng object và trả về kiểu
comparison có thể đƣợc đóng gói bởi ủy quyền vào lúc thực thi.
Lúc này chúng ta định nghĩa phƣơng thức Sort cho lớp Pair:
public void Sort( WhichIsFirst theDelegateFunc)
{
if (theDelegateFunc(thePair[0], thePair[1]) = =
comparison.theSecondComeFirst)
{
object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] =
temp;
}
}
Phƣơng thức này lấy một tham số: một ủy quyền có kiểu WhichIsFirst với
tên là theDelegateFunc. Phƣơng thức Sort giao phó trách nhiệm quyết định thứ
tự đến trƣớc sau của hai đối tƣợng bên trong Pair đến phƣơng thức đƣợc đóng
gói bởi ủy quyền. Bên trong thân của Sort, phƣơng thức ủy quyền đƣợc gọi và
trả về một giá trị, giá trị này là một trong hai giá trị liệt kê của comparison.
Nếu giá trị trả về là theSecondComesFirst, đối tƣợng bên trong của Pair sẽ
đƣợc hoán đổi vị trí, trƣờng hợp ngƣợc lại thì không làm gì cả.
Hãy tƣởng tƣợng chúng ta đang sắp xếp những Student theo tên. Chúng ta
viết một phƣơng thức trả về theFirstComesFirst nếu tên của sinh viên đầu tiên
đến trƣớc và the- SecondComesFirst nếu tên của sinh viên thứ hai đến trƣớc.
Nếu chúng ta đƣa vào là “Amy, Beth” thì phƣơng thức trả về kết quả là
theFirstComesFirst. Và ngƣợc lại nếu chúng ta truyền “Beth, Amy” thì kết quả
trả về là theSecondComesFirst. Khi chúng ta nhận đƣợc kết quả
theSecondComesFirst, phƣơng thức Sort sẽ đảo hai đối tƣợng này trong mảng,
và thiết lập là Amy ở vị trí đầu còn Beth ở vị trí thứ hai.
Tiếp theo chúng ta sẽ thêm một phƣơng thức ReverseSort, phƣơng thức này
đặt các mục trong mảng theo thứ tự đảo ngƣợc lại:
public void ReverseSort( WhichIsFirst theDeleagteFunc)
{
if ( theDelegateFunc( thePair[0], thePair[1]) = =
comparison.theFirstComesFirst)
{
object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] =
temp;
}
}
Việc thực hiện cũng tƣơng tự nhƣ phƣơng thức Sort. Tuy nhiên, phƣơng thức
thực hiện việc hoán đổi nếu phƣơng thức ủy quyền xác định là đối tƣợng trƣớc
tới trƣớc. Do vậy, kết quả thực hiện của phƣơng thức là đối tƣợng thứ hai sẽ đến
trƣớc. Lúc này nếu chúng ta truyền vào là “Amy, Beth”, phƣơng thức ủy quyền
sẽ trả về theFirstComesFirst, và phƣơng thức ReverseSort sẽ hoán đổi vị trí của
hai đối tƣợng này, thiết lập Beth đến trƣớc. Điều này cho phép chúng ta sử dụng
cùng phƣơng thức ủy quyền tƣơng tự nhƣ Sort, mà không cần yêu cầu đối tƣợng
hỗ trợ phƣơng thức trả về giá trị đƣợc sắp ngƣợc.
Lúc này điều cần thiết là chúng ta tạo ra vài đối tƣợng để sắp xếp. Ta tạo hai
lớp đối tƣợng đơn giản nhƣ sau: lớp đối tƣợng Student và lớp đối tƣợng Cat.
Gán cho đối tƣợng Student một tên vào lúc tạo:
public class Student {
public Student (string name)
{
this.name = name;
}
}
Lớp đối tƣợng Student này yêu cầu hai phƣơng thức, một là phƣơng thức phủ
quyết ToString (), và một phƣơng thức khác đƣợc đóng gói nhƣ là phƣơng thức
ủy quyền.
Lớp Student phải phủ quyết phƣơng thức ToString() để cho phƣơng thức
ToString() của lớp Pair sử dụng một cách chính xác. Việc thực thi này thì không
có gì phức tạp mà chỉ đơn thuần là trả về tên của sinh viên:

public override string ToStringO


{
return name;
}
Student cũng phải thực thi một phƣơng thức hỗ trợ cho Pair.Sort() có thể ủy
quyền xác định thứ tự của hai đối tƣợng xem đối tƣợng nào đến trƣớc:
public static comparison WhichStudentComesFirst(Object o1, Object o2)
{
Student si = (Student) o1;
Student s2 = (Student) o2; return ( String.Compare( si.name, s2.name)
<0 ? comparison.theFirstComesFirst :
comparison.theSecondComesFirst);
}
String.Compare là phƣơng thức của .NET trong lớp String, phƣơng thức này so
sánh hai chuỗi và trả về một giá trị nhỏ hơn 0 nếu chuỗi đầu tiên nhỏ hơn chuỗi
thứ hai và lớn hơn 0 nếu chuỗi thứ hai nhỏ hơn, và giá trị là 0 nếu hai chuỗi
bằng nhau. Phƣơng thức này cũng đã đƣợc trình bày trong chƣơng 10 về chuỗi.
Theo lý luận trên thì giá trị trả về là theFirstComesFirst chỉ khi chuỗi thứ nhất
nhỏ hơn, nếu hai chuỗi bằng nhau hay chuỗi thứ hai lớn hơn, thì phƣơng thức
này sẽ trả về cùng giá trị là theSecondComesFirst.
Ghi chú rằng phƣơng thức WhichStudentComesFirst lấy hai tham số kiểu đối
tƣợng và trả về giá trị kiểu liệt kê comparison. Điều này để làm tƣơng ứng và
phù hợp với phƣơng thức đƣợc ủy quyền Pair.WhichlsFirst.
Lớp thứ hai là Cat, để phục vụ cho mục đích của chúng ta, thì Cat sẽ đƣợc sắp
xếp theo trọng lƣợng, nhẹ đến trƣớc nặng. Ta có khai báo lớp Cat nhƣ sau:
public class Cat
{
public Cat( int weight)
{
this.weight = weight;
}
// sắp theo trọng lượng
public static comparison WhichCatComesFirst(Object o1, Object o2)
{
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2; return cl.weight > c2.weight ?
theSecondComesFirst : theFirstComesFirst;
}
public override string ToString()
{
return weight.ToString();
}
// biến lưu giữ trọng lượng private int weight;
}
Cũng tƣơng tự nhƣ lớp Student thì lớp Cat cũng phủ quyết phƣơng thức
ToString() và thực thi một phƣơng thức tĩnh với cú pháp tƣơng ứng với phƣơng
thức ủy quyền. Và chúng ta cũng lƣu ý là phƣơng thức ủy quyền của Student và
Cat là không cùng tên với nhau. Chúng ta không cần thiết phải làm cùng tên vì
chúng ta sẽ gán đến phƣơng thức ủy quyền lúc thực thi. Ví dụ minh họa 11.1 sau
trình bày cách một phƣơng thức ủy quyền đƣợc gọi.
Ví dụ 8.1:
Làm việc với ủy quyền.
namespace Programming_CSharp
{
using System;
// khai báo kiểu liệt kê public enum comparison {
theFirstComesFirst =1,
theSecondComesFirst = 2
}
// lớp Pair đơn giản lưu giữ 2 đối tượng public class Pair
{
// khai báo ủy quyền
public delegate comparison WhichIsFirst( object obji, object obj2);
// truyền hai đối tượng vào bộ khởi dựng
public Pair( object firstObject, object secondObject)
{
thePair[0] = firstObject; thePair[1] = secondObject;
}
// phương thức sắp xếp thứ tự của hai đối tượng // theo bất cứ tiêu chuẩn
nào của đối tượng public void Sort( WhichlsFirst theDelegateFunc)
{
if (theDelegateFunc(thePair[0], thePair[1]) = =
comparison.theSecondComesFirst)
{
object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] =
temp;
}
}
// phương thức sắp xếp hai đối tượng theo // thứ tự nghịch đảo lại tiêu
chuẩn sắp xếp public void ReverseSort( WhichlsFirst theDelegateFunc)
{
if (theDelegateFunc( thePair[0], thePair[1]) = =
comparison.theFirstComesFirst)
{
object temp = thePair[0]; thePair[0] = thePair[1]; thePair[1] =
temp;
}
// yêu cầu hai đối tƣợng đƣa ra giá trị của nó

public override string ToString()


{
return thePair[0].ToString() + ", "+ thePair[1].ToString();
}
// mảng lưu 2 đối tượng
private object[] thePair = new object[2];
}
//lớp đối tượng Cat
public class Cat
{
public Cat(int weight)
{
this.weight = weight;
}
// sắp theo thứ tự trọng lượng
public static comparison WhichCatComesFirst(Object o1, Object o2)
{
Cat cl = (Cat) o1;
Cat c2 = (Cat) o2;
return cl.weight > c2.weight ?
comparison.theSecondComesFirst : comparison.theFirstComesFirst;
}
public override string ToString()
{
return weight.ToString();
}
// biến lưu trọng lượng private int weight;
}
// khai báo lớp Student public class Student {
public Student( string name)
{
this.name = name;
}
// sắp theo thứ tự chữ cái
public static comparison WhichStudentComesFirst( Object oi, Object o2)
{
Student si = (Student) oi;
Student s2 = (Student) o2; return (String.Compare( si.name, s2.name) <0 ?
comparison.theFirstComesFirst : comparison.theSecondComesFirst);
}
public override string ToString()
{
return name;
}
// biến lưu tên private string name;
}
public class Test {
public static void Main()
{
// tạo ra hai đối tượng Student và Cat // đưa chúng vào hai đối tượng
Pair Student Thao = new Student("Thao");
Student Ba = new Student("Ba");
Cat Mun = new Cat(5);
Cat Ngao = new Cat(2);
Pair studentPair = new Pair(Thao, Ba);
Pair catPair = new Pair(Mun, Ngao);
Console.WriteLine("Sinh vien \t\t\t: {0}", studentPair.ToString());
Console.WriteLine("Meo \t\t\t: {0}", catPair.ToString());
// tạo ủy quyền
Pair.WhichlsFirst theStudentDelegate = new
Pair.WhichIsFirst( Student.WhichStudentComesFirst);
Pair.WhichlsFirst theCatDelegate = new
Pair.WhichIsFirst( Cat.WhichCatComesFirst);
// sắp xếp dùng ủy quyền studentPair.Sort( theStudentDelegate);
Console.WriteLine("Sau khi sap xep studentPair\t\t:{0}",
studentPair.ToString());
studentPair. ReverseSort(theStudentDelegate);
Console.WriteLine("Sau khi sap xep nguoc studentPair\t\t:{0}",
studentPair.ToString()); catPair.Sort( theCatDelegate);
Console.WriteLine("Sau khi sap xep catPair\t\t:{0}",
catPair.ToString()); catPair.ReverseSort(theCatDelegate);
Console.WriteLine("Sau khi sap xep nguoc catPair\t\t:{0}",
catPair.ToString());
}
}
}
Kết quả:
Sinh vien : Thao, Ba
Meo : 5, 2
Sau khi sap xep studentPair : Ba, Thao
Sau khi sap xep nguoc studentPair : Thao, Ba
Sau khi sap xep catPair : 2, 5
Sau khi sap xep nguoc catPair : 5,2
Trong đoạn chƣơng trình thử nghiệm trên chúng ta tạo ra hai đối tƣợng
Student và hai đối tƣợng Cat sau đó đƣa chúng vào hai đối tƣợng chứa Pair theo
từng loại. Bộ khởi dựng của lớp Student lấy một chuỗi đại diện cho tên của sinh
viên và bộ khởi dựng của lớp Cat thì lấy một số int đại diện cho trọng lƣợng của
mèo.
Student Thao = new Student("Thao");
Student Ba = new Student("Ba");
Cat Mun = new Cat("5");
Cat Ngao = new Cat("2");
Pair studentPair = new Pair(Thao, Ba);
Pair catPair = new Pair(Mun, Ngao);
Console.WriteLine("Sinh vien \t\t\t: {0}", studentPair.ToStringO);
Console.WriteLine("Meo \t\t\t: {0}", catPair.ToStringO);
Sau đó chƣơng trình in nội dung chứa bên trong của hai đối tƣợng chứa Pair, và
chúng ta có thể thấy thứ tự nhƣ sau:
Sinh vien : Thao, Ba
Meo : 5, 2
Thứ tự xuất hiện của nó chính là thứ tự đƣa vào. Tiếp theo chúng ta khởi tạo hai
đối tƣợng ủy quyền:
Pair.WhichlsFirst theStudentDelegate = new
Pair.WhichIsFirst( Student.WhichStudentComesFirst);
Pair.WhichlsFirst theCatDelegate = new Pair.WhichIsFirst(
Student.WhichCatComesFirst);
Ủy quyền đầu tiên theStudentDelegate đƣợc tạo ra bằng cách truyền vào một
phƣơng thức tĩnh tƣơng ứng của lớp Student. Đối tƣợng ủy quyền thứ hai,
theCatDelegate đƣợc một phƣơng thức tĩnh của lớp Cat.
Bây giờ ta đã có các đối tƣợng ủy quyền, chúng ta truyền ủy quyền đầu tiên cho
phƣơng thức Sort của đối tƣợng Pair, và sau đó là phƣơng thức ReverseSort. Kết
quả đƣợc xuất ra màn hình:
Sau khi sap xep studentPair : Ba, Thao Sau khi sap xep nguoc
studentPair : Thao, Ba Sau khi sap xep catPair : 2, 5
Sau khi sap xep nguoc catPair : 5, 2
8.1.2 Uỷ quyền tĩnh
Nhƣ chúng ta đã thấy trong ví dụ minh hoạ 11.1 trƣớc thì hai thể hiện
phƣơng thức ủy quyền đƣợc khai báo bên trong lớp gọi (chính xác là trong hàm
Main của Test). Điều này có thể không cần thiết ta có thể sử dụng khai báo ủy
quyền tĩnh từ hai lớp Student và Cat. Do vậy ta có thể bổ sung lớp Student bằng
cách thêm vào:
public static readonly Pair.WhichlsFirst OrderStudents = new
Pair.WhichlsFirst(Student.WhichStudentComesFirst);
Ý nghĩa của lệnh trên là tạo một ủy quyền tĩnh tên là OrderStudents và có thuộc
tính chỉ đọc readonly. Việc thêm vào thuộc tính readonly để ghi chú rằng một
khi trƣờng đã đƣợc tạo ra thì không đƣợc bổ sung sau đó.
Tƣơng tự nhƣ vậy chúng ta có thể tạo ủy quyền tĩnh cho Cat nhƣ sau: public
static readonly Pair.WhichlsFirst OderCats = new Pair.WhichIsFirst(
Cat.WhichCatComesFirst);
Bây giờ thì đã có hai trƣờng tĩnh hiện diện bên trong các lớp Student và Cat,
mỗi cái sẽ gắn với phƣơng thức tƣơng ứng bên trong lớp. Sau đó chúng ta có thể
thực hiện ủy quyền mà không cần khai báo thể hiện ủy quyền cục bộ. Việc
chuyển ủy quyền đƣợc thực hiện trong lệnh in đậm nhƣ sau:
studentPair.Sort( theStudentDelegate);
Console.WriteLine("Sau khi sap xep studentPair\t\t:{0}",
studentPair.ToString()); studentPair.ReverseSort(Student.OrderStudents);
Console.WriteLine("Sau khi sap xep nguoc studentPair\t\t:{0}",
studentPair.ToStringO); catPair.Sort( theCatDelegate);
Console.WriteLine("Sau khi sap xep catPair\t\t:{0}", catPair.ToString());
catPair.ReverseSort(Cat.OrderCats);
Console.WriteLine("Sau khi sap xep nguoc catPair\t\t:{0}",
catPair.ToString());
Kết quả thực hiện tƣơng tự nhƣ trong ví dụ 8.1
8.1.3 Sử dụng ủy quyền nhƣ thuộc tính
Đối với ủy quyền tĩnh thì chúng bắt buộc phải đƣợc tạo thể hiện, do tính chất
tĩnh, mà không cần biết là chúng có đƣợc sử dụng hay không, nhƣ lớp Student
và Cat trong ví dụ bên trên. Chúng ta có thể phát triển những lớp này tốt hơn
bằng cách thay thế ủy quyền tĩnh từ trƣờng thành thuộc tính.
Với lớp Student ta có thể chuyển khai báo:
public static readonly Pair.WhichlsFirst OrderStudent =
new Pair.WhichIsFirst( Student.WhichStudentComesFirst);
thành khai báo nhƣ sau:
public static Pair.WhichlsFirst OrderStudents {
get
{
return new Pair.WhichIsFirst( WhichStudentComesFirst);
}
}
Tương tự như vậy chúng ta thực hiện thay thế với lớp Cat:
public static Pair.WhichlsFirst OderCats
{
get
{
return new Pair.WhichIsFirst( WhichCatComesFirst);
}
}
Khi truyền cho phƣơng thức thì không thay đổi: studentPair.Sort(
Student.OderStudents); catPair.Sort( Cat.OrderCats);
Khi thuộc tính OrderStudents đƣợc truy cập thì ủy quyền đƣợc tạo ra: return new
Pair.WhichIsFirst( WhichCatComesFirst);
Điều quan trọng ở đây là ủy quyền sẽ không đƣợc tạo cho đến khi nào nó
đƣợc yêu cầu. Việc này cho phép lớp gọi (nhƣ lớp Test) quyết định khi nào cần
thiết sử dụng một ủy quyền nhƣng vẫn cho phép việc tạo ủy quyền là trách
nhiệm của lớp Student hay lớp Cat.
8.1.4 Thiết lập thứ tự thi hành với mảng ủy quyền
Ủ y quyền có thể giúp chúng ta tạo ra một hệ thống trong đó ngƣời sử dụng
có thể quyết định đến thứ tự của các hoạt động khi thực thi. Giả sử chúng ta có
một hệ thống xử lý ảnh trong đó các ảnh có thể đƣợc thao tác bởi một phƣơng
pháp đƣợc định nghĩa tốt nhƣ là: làm mờ, làm đậm, xoay, lọc ảnh,... Giả sử rằng,
thứ tự khi sử dụng các hiệu ứng này đƣợc áp dụng cho ảnh là quan trọng. Ngƣời
sử dụng muốn lựa chọn những hiệu ứng này từ menu, anh ta chọn tất cả các hiệu
ứng tùy thích, và sau đó yêu cầu bộ xứ lý ảnh thực hiện lần lƣợt các hiệu ứng mà
anh ta đã xác định.
Chúng ta có thể tạo những ủy quyền cho mỗi hoạt động và sau đó thêm chúng
vào một tập hợp đƣợc sắp, nhƣ là một mảng chẳng hạn, theo một thứ tự mà ta
muốn chúng thực thi. Một khi tất cả các ủy quyền đƣợc tạo ra và đƣa vào tập
hợp, chúng ta dễ dàng lặp lần lƣợt qua các thành phần của mảng, và thực thi lần
lƣợt từng phƣơng thức ủy quyền.
Chúng ta bắt đầu bằng việc xây dựng một lớp Image thể hiện một ảnh sẽ đƣợc
xử lý bởi lớp ImageProcessor:
public class Image {
public Image()
{
Console.WriteLine("An image created");
}
}
Chúng ta có thể tƣởng tƣợng rằng việc xuất ra chuỗi nhƣ vậy tƣơng ứng với việc
tạo một ảnh .gif hay .jpeg hay đại loại nhƣ vậy.
Sau đó lớp ImageProcessor khai báo một ủy quyền. Dĩ nhiên là chúng ta có thể
định nghĩa một ủy quyền riêng trả về bất cứ kiểu dữ liệu nào hay lấy bất cứ tham
số nào mà chúng ta muốn. Trong ví dụ này chúng ta định nghĩa một ủy quyền có
thể đóng gói bất cứ phƣơng thức không có giá trị trả về và cũng không nhận bất
cứ tham số nào hết: public delegate void DoEffect();
Tiếp tục lớp ImageProcessor khai báo một sô phƣơng thức, và từng phƣơng thức
này phù hợp với ký pháp và kiểu trả về đƣợc khai báo bởi ủy quyền:
public static void Blur()
{
Console.WriteLine("Blurring image");
}
public static void Filter()
{
Console.WriteLine("Filtering image");
}
public static void Sharpen()
{
Console.WriteLine("Sharpening image");
}
public static void Rotate()
{
Console.WriteLine("Rotating image");
}
Lớp ImageProcessor cần thiết có một mảng để lƣu giữ các ủy quyền mà ngƣời
sử dụng chọn, một biến lƣu giữ số hiệu ứng đƣợc chọn và dĩ nhiên là có một
biến ảnh để xử lý:
DoEffect[] arrayOfEffects;
Image image;
int numEffectsRegistered = 0;
ImageProcessor cũng cần một phương thức để thêm các ủy quyền vào trong
mảng: public void AddToEffects( DoEffect theEffect)
{
if (numEffectsRegistered >=0)
{
throw new Exception("Too many members in array");
}
arrayOfEffects[numEffectsRegistered + + ] = theEffect;
}
Ngoài ra còn cần một phƣơng thức thật sự gọi các ủy quyền này:
public void ProcessImage()
{
for (int i = 0; i < numEffectsRegistered; i++)
{
arrayOfEffects[i]();
}
}
Cuối cùng, chúng ta khai báo những ủy quyền tĩnh, để các client gọi, và chặn
chúng lại để xử lý những phƣơng thức:
public DoEffect BlurEffect = new DoEffect(Blur); public DoEffect
SharpenEffect = new DoEffect(Sharpen);
public DoEffect FilterEffect = new DoEffect(Filter); public DoEffect
RotateEffect = new DoEffect(Rotate);
Việc chọn các thao tác diễn ra trong quá trình tƣơng tác ở thành phần giao diện
ngƣời sử dụng. Trong ví dụ này chúng ta mô phỏng bằng cách chọn các hiệu
ứng, thêm chúng vào trong mảng, và Processlmage.
Ví dụ 8.2:
Sử dụng mảng ủy quyền.
Programming_Csharp
{
using System;
// khai báo lớp ảnh
public classImage {
public Image()
{
Console.WriteLine("An image created");
}
}
// lớp xử lý ảnh
public class ImageProcessor
{
// khai báo ủy quyền
public delegate void DoEffect();
// tạo các ủy quyền tĩnh
public DoEffect BlurEffect = new DoEffect(Blur); public DoEffect
SharpenEffect = new DoEffect(Sharpen); public DoEffect FilterEffect
= new DoEffect(Filter); public DoEffect RotateEffect = new
DoEffect(Rotate);
// bộ khởi dựng khởi tạo ảnh và mảng public ImageProcessor(Image
image)
{
this.image = image; arrayOfEffects = new DoEffect[10];
}
// thêm hiệu ứng vào trong mảng
public void AddToEffects( DoEffect theEffect)
{
if (numEffectsRegistered >=0)
{
throw new Exception("Too many members in array");
}
arrayOfEffects[numEffectsRegistered + + ] = theEffect;
}
// các phương thức xử lý ảnh public static void Blur()
{
Console.WriteLine("Blurring image");
}
public static void Filter()
{
Console.WriteLine("Filtering image");
}
public static void Sharpen()
{
Console.WriteLine("Sharpening image");
}
public static void Rotate()
{
Console.WriteLine("Rotating image");
}
// gọi các ủy quyền để thực hiện hiệu ứng public void

ProcessImage()
{
for (int i = 0; i < numEffectsRegistered; i++)
{
arrayOfEffects[i]();
}
}
// biến thành viên
private DoEffect[] arrayOfEffects;
private Image image;
private int numEffectsRegistered = 0;
}
// lớp Test để kiểm chứng chương trình public class Test
{

public static void Main()


{
Image thelmage = new Image();
// do không có GUI để thực hiện chúng ta sẽ chọn lần // lượt các hành
động và thực hiện
ImageProcessor theProc = new ImageProcessor(thelmage);
theProc.AddToEffects(theProc.BlurEffect);
theProc.AddToEffects(theProc.FilterEffect);
theProc.AddToEffects(theProc.RotateEffect);
theProc.AddToEffects(theProc.SharpenEffect);
theProc.ProcessImage();
}
}
}
Kết quả:
An image created Blurring image Filtering image Rotate image Sharpening
image
Trong ví dụ trên, đối tƣợng ImageProcessor đƣợc tạo ra và những hiệu
ứng đƣợc thêm vào. Nếu ngƣời dùng chọn làm mờ trƣớc khi lọc ảnh, thì đơn
giản là đƣợc đƣa vào mảng ủy quyền theo thứ tự tƣơng ứng. Tƣơng tự nhƣ vậy,
bất cứ hành động lựa chọn nào của ngƣời dùng mong muốn, ta đƣa thêm nhiều
ủy quyền vào trong tập hợp.
Chúng ta có thể tƣởng tƣợng việc hiển thị thứ tự hành động này trong một danh
sách listbox và cho phép ngƣời sử dụng sắp xếp lại phƣơng thức, di chuyển
chúng lên xuống trong danh sách. Khi các hành động này đƣợc sắp xếp lại thì
chúng ta chỉ cần thay đổi thứ tự trong tập hợp. Ngoài ra ta cũng có thể đƣa các
hoạt động này vào trong cơ sở dữ liệu rồi sau đó đọc chúng lúc thực hiện.
Ủ y quyền dễ dàng cung cấp động cho ta các phƣơng thức đƣợc gọi theo một
thứ tự xác định Multicasting.
Cơ chế multicasting cho phép gọi hai phƣơng thức thực thi thông qua một ủy
quyền đơn. Điều này trở nên quan trọng khi xử lý các sự kiện, sẽ đƣợc thảo luận
trong phần cuối của chƣơng.
Mục đích chính là có một ủy quyền có thể gọi thực hiện nhiều hơn một
phƣơng thức. Điều này hoàn toàn khác với việc có một tập hợp các ủy quyền, vì
mỗi trong số chúng chỉ gọi đƣợc duy nhất một phƣơng thức. Trong ví dụ trƣớc,
tập hợp đƣợc sử dụng để lƣu giữ các ủy quyền khác nhau. Tập hợp này cũng có
thể thêm một ủy quyền nhiều hơn một lần, và sử dụng tập hợp để sắp xếp lại các
ủy quyền và điều khiển thứ tự hành động đƣợc gọi.
Với Multicasting chúng ta có thể tạo một ủy quyền đơn và cho phép gọi nhiều
phƣơng thức đƣợc đóng. Ví dụ, khi một nút lệnh đƣợc nhấn chúng ta có thể
muốn thực hiện nhiều hơn một hàh động. Để làm đƣợc điều này chúng ta có thể
đƣa cho button một tập hợp các ủy quyền, nhƣng để sáng rõ hơn và dễ dàng hơn
là tạo một ủy quyền Multicast.
Bất cứ ủy quyền nào trả về giá trị void là ủy quyền multicast, mặc dù vậy
ta có thể đối xử với nó nhƣ là ủy quyền bình thƣờng cũng không sao. Hai ủy
quyền Multicast có thể đƣợc kết hợp với nhau bằng phép toán cộng (+). Kết quả
là một ủy quyền Multicast mới và gọi đến tất cả các phƣơng thức thực thi
nguyên thủy của cả hai bên. Ví dụ, giả sử Writer và Logger là ủy quyền trả về
giá trị void, dòng lệnh theo sau sẽ kết hợp chúng lại với nhau và tạo ra một ủy
quyền Multicast mới:
myMulticastDelegate = Writer + Logger;
Chúng ta cũng có thể thêm những ủy quyền vào trong ủy quyền Multicast bằng
toán tử cộng bằng (+=). Phép toán này sẽ thêm ủy quyền ở phía bên phải của
toán tử vào ủy quyền Multicast ở bên trái. Ví dụ minh họa nhƣ sau, giả sử có
Transmitter và myMulticastDelegate là những ủy quyền, lệnh tiếp theo sau đây
sẽ thực hiện việc thêm ủy quyền Transmitter vào trong myMulticastDelegate:
myMulticastDelegate += Transmitter;
Để hiểu rõ ủy quyền Multicast đƣợc tạo ra và sử dụng, chúng ta sẽ từng bƣớc
tìm hiểu thông qua ví dụ 11.3 bên dƣới, trong ví dụ minh họa này chúng ta sẽ
tạo ra một lớp có tên gọi là MyClassWithDelegate lớp này định nghĩa một
delegate, delegate này lấy một tham số là chuỗi và không có giá trị trả về:
void delegate void StringDelegate( string s);
Sau đó chúng ta định một lớp gọi là MylmplementingClass lớp này có ba
phƣơng thức, tất cả các phƣơng thức này đều trả về giá trị void và nhận một
chuỗi làm tham số: WriteString, LogString, và Transmitting. Phƣơng thức đầu
tiên viết một chuỗi xuất ra màn hình tiêu chuẩn, chuỗi thứ hai mô phỏng viết
vào một log file, và phƣơng thức thứ ba mô phỏng việc chuyển một chuỗi qua
Internet. Chúng ta tạo thể hiện delegate để gọi những phƣơng thức tƣơng ứng:
Writer("String passed to Writer\n");
Logger("String passed to Logger\n");
Transmitter("String passed to Transmitter\n");
Để xem cách kết hợp các delegate, chúng ta tạo một thể hiện delegate khác:
MyClassWithDelegate.StringDelegate myMulticastDelegate;
và gán cho delegate này kết quả của phép cộng hai delegate cho trƣớc:
myMulticastDelegate = Writer + Logger;
Tiếp theo chúng ta thêm vào delegate này một delegate nữa bằng cách sử dụng
toán tử (+=): myMulticastDelegate += Transmitter;
Cuối cùng, chúng ta thực hiện việc xóa deleagate bằng sử dụng toán tử (-=):
DelegateCollector -= Logger;
Sau đây là toàn bộ ví dụ minh họa.
Ví dụ 8.3: Kết hợp các delegate.
namespace Programming_Csharp
{
using System;
public class MyClassWithDelegate
{
// khai báo delegate
public delegate void StringDelegate(string s);
}
public class MylmplementingClass
{
public static void WriteString( string s)
{
Console.WriteLine("Writing string {0}", s);
}
public static void LogString( string s)
{
Console.WriteLine("Logging string {0}", s);
}
public static void TransmitString( string s)
{
Console.WriteLine("Transmitting string {0}", s);
}
}
public class Test
{
public static void Main()
{
// định nghĩa 3 StringDelegate
MyClassWithDelegate.StringDelegate Writer, Logger, Transmitter;
// định nghĩa một StringDelegate khác thực hiện Multicasting
MyClassWithDelegate.StringDelegate myMulticastDelegate;
// tạo thể hiện của 3 delegate đầu tiên và truyền vào phương thức thực thi
Writer = new MyClassWithDelegate.StringDelegate(
MylmplementingClass.WriteString);
Logger = new MyClassWithDelegate.StringDelegate(
MylmplementingClass.LogString);
Transmitter = new MyClassWithDelegate.StringDelegate(
MylmplementingClass.TransmitString);
// gọi phương thức delegate Writer Writer("String passed to Writer\n");
// gọi phương thức delegate Logger Logger("String passed to Logger\n");
//gọi phương thức delegate Transmitter Transmitter("String passed to
Transmitter\n");
// thông báo người dùng rằng đã kết hợp hai delegate vào
// trong một multicast delegate
Console.WriteLine("myMulticastDelegate = Writer + Logger");
// kết hợp hai delegate myMulticastDelegate = Writer + Logger;
// gọi phương thức delegate, hai phương thức sẽ được thực hiện
myMulticastDelegate("First string passed to Collector");
// bảo với người sử dụng rằng đã thêm delegate thứ 3 vào
// trong Multicast delegate
Console.WriteLine("\nmyMulticastDeleagte += Transmitter");
// thêm delegate thứ ba vào myMulticastDelegate += Transmitter;
// gọi thực thi Multicast delegate, cùng một lúc ba // phương thức sẽ cùng
được gọi thực hiện myMulticastDelegate("Second string passed to
Collector");
// bảo với người sử dụng rằng xóa delegate Logger
Console.WriteLine("\nmyMulticastDelegate -= Logger");
// xóa delegate Logger myMulticastDelegate -= Logger;
// gọi lại delegate, lúc này chỉ còn thực hiện hai phương thức
myMulticastDelegate("Third string passed to Collector");
}// end Main
}// end class
}// end namespace
Kết quả:
Writing string String passed to Writer
Logging string String passed to Logger
Transmitting string String passed to Transmitter
myMulticastDelegate = Writer + Logger
Writing string First string passed to Collector
Logging string First string passed to Collector
myMulticastDelegate += Transmitter
Writing string Second string passed to Collector
Logging string Second string passed to Collector
Transmitting string Second string passed to Collector
myMulticastDelegate -= Logger
Writing string Third string passed to Collector
Transmitting string Third string passed to Collector
Trong ví dụ trên, những thể hiện delegate đƣợc định nghĩa và ba delegate
đầu tiên Writer, Logger, và Transmitter đƣợc gọi ra. Delegate thứ tƣ
myMulticastDelegate đƣợc gán bằng cách kết hợp hai delegate đầu, và khi nó
đƣợc gọi, thì dẫn đến là cả hai delegate cũng đƣợc gọi. Khi delegate thứ ba đƣợc
thêm vào, và kết quả là khi myMulticastDelegate đƣợc gọi thì tất cả ba phƣơng
thức delegate cũng đƣợc thực hiện. Cuối cùng, khi Logger đƣợc xóa khỏi
delegate, và khi myMulticastDelegate đƣợc gọi thì chỉ có hai phƣơng thức thực
thi.
Multicast delegate đƣợc thể hiện tốt nhất trong việc ứng dụng xử lý các sự kiện.
Khi một sự kiện ví dụ nhƣ một nút lệnh đƣợc nhấn, thì một multicast delegate
tƣơng ứng sẽ gọi đến một loạt các phƣơng thức xử lý sự kiện để đáp ứng lại với
các sự kiện này.
8.2 Sự kiện
Trong môi trƣờng giao diện đồ họa (Graphical User Interfaces: GUIs),
Windows hay trong trình duyệt web đòi hỏi các chƣơng trình phải đáp ứng các
sự kiện. Một sự kiện có thể là một nút lệnh đƣợc nhấn, một mục trong menu
đƣợc chọn, hành động sao chép tập tin hoàn thành,...Hay nói ngắn gọn là một
hành động nào đó xảy ra, và ta phải đáp ứng lại sự kiện đó. Chúng ta không thể
đoán trƣớc đƣợc khi nào thì các sự kiện sẽ xuất hiện. Hệ thống sẽ chờ cho đến
khi nhận đƣợc sự kiện, và sẽ chuyển vào cho trình xử lý sự kiện thực hiện.
Trong môi trƣờng giao diện đồ họa, bất cứ thành phần nào cũng có thể đƣa ra sự
kiện. Ví dụ, khi chúng ta kích vào một nút lệnh, nó có thể đƣa ra sự kiện Click.
Khi chúng ta thêm một mục vào danh sách, nó sẽ đƣa ra sự kiện ListChanged.
Cơ chế publishing và subscribing
Trong ngôn ngữ C#, bất cứ đối tƣợng nào cũng có thể publish một tập hợp
các sự kiện để cho các lớp khác có thể đăng ký. Khi một lớp publish đƣa ra một
sự kiện, thì tất cả các lớp đã đăng ký sẽ đƣợc nhận sự cảnh báo.
Ghi chú:
Tác giả Gamma (Addison Wesley, 1995) mô tả cơ chế này nhƣ sau:
„"Định nghĩa một đến nhiều sự phụ thuộc giữa những đối tƣợng do đó khi một
đối tƣợng thay đổi trạng thái, tất cả các đối tƣợng khác phụ thuộc vào nó sẽ
đƣợc cảnh báo và cập nhật một cách tự động".
Với cơ chế này, đối tƣợng của chúng ta có thể nói rằng “Ở đây có những
thứ mà tôi có thể thông báo cho bạn” và những lớp khác có thể đăng ký đáp rằng
“Vâng, hãy báo cho tôi biết khi chuyện đó xảy ra”. Ví dụ, một nút lệnh có thể
cảnh báo cho bất cứ thành phần nào khi nó đƣợc nhấn. Nút lệnh này đƣợc gọi là
publisher bởi vì nó phân phát sự kiện Click và những lớp khác là các lớp
subscriber vì chúng đăng ký nhận sự kiện Click này.
Sự kiện và delegate
Những sự kiện trong C# đƣợc thực thi với những delegate. Lớp publisher
định nghĩa một delegate và những lớp subscriber phải thực thi. Khi một sự kiện
xuất hiện thì phƣơng thức của lớp subscriber đƣợc gọi thông qua delegate.
Một phƣơng thức đƣợc dùng để xử lý các sự kiện thì đƣợc là trình xử lý sự kiện
(event handler). Chúng ta có thể khai báo trình xử lý sự kiện này nhƣ chúng ta
đã làm với bất cứ delegate khác.
Theo quy ƣớc, những trình xử lý sự kiện trong .NET Framework trả về giá trị
void và lấy hai tham số. Tham số đầu tiên là nguồn dẫn đến sự kiện, đây chính
là đối tƣợng publisher. Và tham số thứ hai là đối tƣợng dẫn xuất từ lớp
EventArgs. Yêu cầu chúng ta phải thực hiện trình xử lý sự kiện theo mẫu nhƣ
trên. EventArgs là lớp cơ sở cho tất cả các dữ liệu về sự kiện, lớp EventArgs
thừa kế tất cả các phƣơng thức của nó từ Object, và thêm vào một trƣờng public
static empty thể hiện một sự kiện không có trạng thái (cho phép sử dụng hiệu
quả những sự kiện không trạng thái). Lớp dẫn xuất từ EventArgs chứa những
thông tin về sự kiện.
Sự kiện là thuộc tính của lớp phát ra sự kiện. Từ khóa event điều khiển
cách thuộc tính sự kiện đƣợc truy cập bởi các lớp subscriber. Từ khóa event
đƣợc thiết kế để duy trì cho cách thể hiện publish/ subscribe.
Giả sử chúng ta muốn tạo một lớp Clock dùng những sự kiện để cảnh báo những
lớp subscriber bất cứ khi nào đồng hồ hệ thống thay đổi giá trị trong một giây.
Gọi sự kiện này là OnSecondChange.
Chúng ta khai báo sự kiện và kiểu delegate xử lý sự kiện của nó nhƣ sau:
[attributes] [modifiers] event type member- name

Ví dụ khai báo nhƣ sau:


public event SecondChangeHandler OnSecondChange;
Trong ví dụ này ta không dùng thuộc tính, modifier ở đây là abstract, new,
override, static, virtual, hay là một trong bốn access modifier, trong trƣờng hợp
này public. Modifier đƣợc theo sau bởi từ khóa event.
Trƣờng type trong trƣờng hợp ví dụ này là delegate mà chúng ta muốn liên hệ
với sự kiện, ở đây là SecondChangeHandler.
Tên thành viên là tên của sự kiện, trong trƣờng hợp này là OnSecondChange.
Thông thƣờng, tên sự kiện bắt đầu với từ On.
Tóm lại, trong sự khai báo này OnSecondChange là sự kiện đƣợc thực thi bởi
delegate có kiểu là SecondChangeHandler.
Ta có khai báo cho delegate này nhƣ sau:
public delegate void SecondChangeHandler( object clock,
TimelnfoEventArgs timelnformation);
Nhƣ đã nói trƣớc đây, theo quy ƣớc một trình xử lý sự kiện phải trả về giá
trị void và phải lấy hai tham số: nguồn phát ra sự kiện (trong trƣờng hợp này là
clock) và một đối tƣợng dẫn xuất từ EventArgs, là TimelnfoEventArgs. Lớp
TimelnfoEventArgs đƣợc định nghĩa nhƣ sau:
public class TimelnfoEventArgs : EventArgs
{
public TimeInfoEventArgs(int hour, int minute, int second)
{
this.hour = hour; this.minute = minute; this.second = second;
}
public readonly int hour; public readonly int minute; public readonly
int second;
}
Đối tƣợng TimelnfoEventArgs sẽ có thông tin về giờ phút giây hiện thời. Nó
định nghĩa một bộ khởi tạo, ba phƣơng thức, một biến nguyên readonly.
Ngoài việc thêm vào một sự kiện và delegate, lớp đối tƣợng Clock có ba biến
thành viên là : hour, minute, và second. Cuối cùng là một phƣơng thức Run():
public void Run()
{
for(;;)
{
// ngừng 10 giây Thread.Sleep( 10 );
// lấy thời gian hiện hành
System.DateTime dt = System.DateTime.Now;
// nếu giây thay đổi cảnh báo cho subscriber if ( dt.Second !=
second)
{
// tạo TimelnfoEventArgs để truyền // cho subscriber
TimelnfoEventArgs timelnformation =
new TimeInfoEventArgs( dt.Hour, dt.Minute, dt.Second);
// nếu có bất cứ lớp nào đăng ký thì cảnh báo if (
OnSecondChange != null)
{
OnSecondChange( this, timelnformation);
}
}
// cập nhật trạng thái this.second = dt.Second; this.minute =
dt.Minute; this.hour = dt.Hour;
}
}
Phƣơng thức Run tạo vòng lặp vô hạn để kiểm tra định kỳ thời gian hệ thống.
Nếu thời gian thay đổi từ đối tƣợng Clock hiện hành, thì nó sẽ cảnh báo cho tất
cả các subscriber và sau đó cập nhật lại những trạng thái của nó.
Bƣớc đầu tiên là ngừng 10 giây:
Thread.Sleep(10);
Ở đây chúng ta sử dụng phƣơng thức tĩnh của lớp Thread từ System.Threading
của .NET. Sử dụng phƣơng thức Sleep() để kéo dài khoảng cách giữa hai lần
thực hiện vòng lặp.
Sau khi ngừng 10 mili giây, phƣơng thức sẽ kiểm tra thời gian hiện hành:
System.DateTime dt = System.DateTime.Now;
Cứ khoảng 100 lần kiểm tra, thì một giây sẽ đƣợc gia tăng. Phƣơng thức ghi
nhận sự thay đổi và cảnh báo đến những subscriber của nó. Để làm đƣợc điều
này, đầu tiên phải tạo ra một đối tƣợng TimelnfoEventArgs:
if ( dt.Second != second)
{
// tạo TimelnfoEventArgs để truyền cho các subscriber TimelnfoEventArgs
timelnformation =
new TimeInfoEventArgs( dt.Hour, dt.Minute, dt.Second);
}
Và để cảnh báo cho những subscriber bằng cách kích hoạt sự kiện
OnSecondChange:
// cảnh báo cho các subscriber if ( OnSecondChange != null)
{
OnSecondChange( this, timelnformation);
}
Nếu một sự kiện không có bất cứ lớp subscriber nào đăng ký thì nó ƣớc lƣợng
giá trị là null. Phần kiểm tra bên trên xác định giá trị của sự kiện có phải là null
hay không, để đảm bảo rằng có tồn tại lớp đăng ký nhận sự kiện trƣớc khi gọi sự
kiện OnSecondChange.
Chúng ta lƣu ý rằng OnSecondChange lấy hai tham số: nguồn phát ra sự kiện và
đối tƣợng dẫn xuất từ lớp EventArgs. Ở đây chúng ta có thể thấy rằng tham
chiếu this của lớp clock đƣợc truyền bởi vì clock là nguồn phát ra sự kiện. Tham
số thứ hai là đối tƣợng Timelnfo- EventArgs đƣợc tạo ra ở dòng lệnh bên trên.
Một sự kiện đƣợc phát ra thì sẽ gọi bất cứ phƣơng thức nào đƣợc đăng ký với
lớp Clock thông qua delegate, chúng ta sẽ kiểm tra điều này sau.
Một khi mà sự kiện đƣợc phát ra, chúng ta sẽ cập nhật lại trạng tháicủa lớp
Class: this.second = dt.Second; this.minute = dt.Minute; this.hour = dt.Hour;
Sau cùng là chúng ta xây dựng những lớp có thể đăng ký vào các sự kiện này.
Chúng ta sẽ tạo hai lớp. Lớp đầu tiên là lớp DisplayClock. Chức năng chính của
lớp này không phải là lƣu giữ thời gian mà chỉ để hiển thị thời gian hiện hành ra
màn hình console. Để đơn giản chúng ta chỉ tạo hai phƣơng thức cho lớp này.
Phƣơng thức thứ nhất có tên là Subscribe, phƣơng thức chịu trách nhiệm đăng
ký một sự kiện OnSecondChange của lớp Clock. Phƣơng thức thứ hai đƣợc tạo
ra là trình xứ lý sự kiện TimeHasChanged:
public class DisplayClock
{
public void Subscrible(Clock theClock)
{
theClock.OnSecondChange + =
new Clock.SecondChangeHandler(TimeHasChanged);
}
public void TimeHasChanged( object theClock, TimeInfoEventArgs ti)
{
Console.WriteLine("Current Time: {0]:{1}:{2}", ti.hour.ToString(),
ti.minute.ToString(), ti.Second.ToString());
}
}
Khi phƣơng thức đầu tiên Subscribe đƣợc gọi, nó sẽ tạo ra một delegate
SecondChange- Handler mới, và truyền vào phƣơng thức xử lý sự kiện
TimeHasChanged của nó. Sau đó nó sẽ đăng ký delegate với sự kiện
OnSecondChange của Clock.
Lớp thứ hai mà chúng ta tạo cũng sẽ đáp ứng sự kiện này, tên là
LogCurrentTime. Thông thƣờng lớp này ghi lại sự kiện vào trong tập tin, nhƣng
với mục đích minh họa của chúng ta, nó sẽ ghi ra màn hình console:
public class LogCurrentTime
{
public void Subscribe(Clock theClock)
{
theClock.OnSecondChange + =
new Clock.SecondChangeHandler(WriteLogEntry);
}
// thông thường phương thức này viết ra file
// nhưng trong minh họa này chúng ta chỉ xuất // ra màn hình console
mà thôi
public void WriteLogEntry( object theClock, TimelnfoEventArgs ti)
{
Console.WriteLine("Logging to file: {0}:{1}:{2}",
ti.hour.ToString(), ti.minute.ToString(), ti.second.ToString());
}
}
Ghi chú rằng những sự kiện đƣợc thêm vào bằng cách sử dụng toán tử +=. Điều
này cho phép những sự kiện mới đƣợc thêm vào sự kiện OnSecondChange của
đối tƣợng Clock mà không có phá hủy bất cứ sự kiện nào đã đƣợc đăng ký. Khi
LogCurrentTime đăng ký một sự kiện OnSecondChange, chúng ta không muốn
việc đăng ký này làm mất đi sự đăng ký của lớp DisplayClock trƣớc đó.
Tất cả phần còn lại cần thực hiện là tạo ra một lớp Clock, tạo mộ đối tƣợng
DisplayClock và bảo nó đăng ký sự kiện. Sau đó chúng ta tạo ra một đối tƣợng
LogCurrentTime và cũng đăng ký sự kiện tƣơng tự. Cuối cùng thì thực thi
phƣơng thức Run của Clock. Tất cả phần trên đƣợc trình bày trong ví dụ 8.4.
Ví dụ 8.4:
Làm việc với những sự kiện.
namespace Programming_CSharp
{
using System;
using System.Threading;
// lớp lưu giữ thông tin về sự kiện, trong trường hợp
// này nó chỉ lưu giữ những thông tin có giá trị lớp clock public class
TimelnfoEventArgs : EventArgs
{
public TimeInfoEventArgs(int hour, int minute, int second)
{
this.hour = hour; this.minute = minute; this.second = second;
}
public readonly int hour; public readonly int minute; public readonly
int second;
}
// khai báo lớp Clock lớp này sẽ phát ra các sự kiện public class Clock {
// khai báo delegate mà các subscriber phải thực thi public delegate
void SecondChangeHandler(object clock,
TimelnfoEventArgs timelnformation);
// sự kiện mà chúng ta đưa ra
public event SecondChangeHandler OnSecondChange;
// thiết lập đồng hồ thực hiện, sẽ phát ra mỗi sự kiện trong mỗi giây
public void Run()
{
for(;;)
{
// ngừng 10 giây Thread.Sleep( 10 );
// lấy thời gian hiện hành
System.DateTime dt = System.DateTime.Now;
// nếu giây thay đổi cảnh báo cho subscriber
if ( dt.Second != second)
{
// tạo TimelnfoEventArgs để truyền
// cho subscriber
TimelnfoEventArgs timelnformation =
new TimeInfoEventArgs( dt.Hour, dt.Minute, dt.Second);
// nếu có bất cứ lớp nào đăng ký thì cảnh báo
if ( OnSecondChange != null)
{
OnSecondChange( this, timelnformation);
}
}
// cập nhật trạng thái this.second = dt.Second; this.minute =
dt.Minute; this.hour = dt.Hour;
}
}
private int hour;
private int minute;
private int second;
}
// lớp DisplayClock đăng ký sự kiện của clock.
// thực thi xử lý sự kiện bằng cách hiện hời gian hiện hành public class
DisplayClock {
public void Subscrible(Clock theClock)
{
theClock.OnSecondChange + =
new Clock.SecondChangeHandler(TimeHasChanged);
}
public void TimeHasChanged( object theClock, TimeInfoEventArgs ti)
{
Console.WriteLine("Current Time: {0}:{1}:{2}",
ti.hour.ToString(), ti.minute.ToString(), ti.second.ToString());
}
}
// lớp đăng ký sự kiện thứ hai public class LogCurrentTime
public void Subscribe(Clock theClock)
{
theClock.OnSecondChange + =
new Clock.SecondChangeHandler(WriteLogEntry);
}
// thông thường phương thức này viết ra file // nhưng trong minh họa này
chúng ta chỉ xuất // ra màn hình console mà thôi
public void WriteLogEntry( object theClock, TimelnfoEventArgs ti)
{
Console.WriteLine("Logging to file: {0}:{1}:{2}", ti.hour.ToString(),
ti.minute.ToString(), ti.second.ToString());
}
}
// lớp Test minh họa sử dụng sự kiện public class Test {
public static void Main()
{
// tạo ra đối tượng clock Clock theClock = new Clock();
// tạo đối tượng DisplayClock đăng ký // sự kiện và xử lý sự kiện
DisplayClock dc = new DisplayClock(); dc.Subscribe(theClock);
// tạo đối tượng LogCurrent và yêu cầu đăng // ký và xử lý sự kiện
LogCurrentTime lct = new LogCurrentTime();
lct.Subscribe(theClock);
// bắt đầu thực hiện vòng lặp và phát sinh sự kiện
// trong mỗi giây đồng hồ
theClock.Run();
}
}
}
Kết quả thực hiện có thể nhƣ sau:
Current Time: 11:54:20 Logging to file: 11:54:20 Current Time: 11:54:21
Logging to file: 11:54:21 Current Time: 11:54:22 Logging to file: 11:54:22
Điều quan trọng chính của ví dụ minh họa trên là việc tạo ra hai lớp đối
tƣợng DisplayClock và lớp LogCurrentTime. Cả hai lớp này đều đăng ký một sự
kiện Clock.OnSecondChange của lớp thứ ba là lớp Clock
Lợi ích của cơ chế publish/subscribe là bất cứ lớp nào cũng có thể đƣợc cảnh
báo khi một sự kiện xuất hiện. Những lớp subscriber không cần biết cách mà
Clock làm việc, và Clock cũng không cần biết cách mà các lớp subscriber đáp
ứng với sự kiện mà nó đƣa ra.
Publisher và subscriber đƣợc phân tách bởi delegate, đây là một sự mong đợi
cao, nó làm cho mã lệnh linh họat và mạnh mẽ hơn. Lớp Clock có thể thay đổi
cách dò thời gian mà không làm ảnh hƣởng đến bất cứ lớp subscriber nào. Các
lớp subscriber có thể thay đổi cách mà chúng đáp ứng với sự thay đổi của thời
gian mà không tác động với Clock. Cả hai lớp này hoạt động độc lập với nhau,
và làm cho đoạn chƣơng trình dễ duy trì hơn.
Bài 9
CÁC LỚP CƠ SỞ CỦA .NET
Mã bài: MĐ33-09
Giới thiệu:
Cho đến lúc này thì chúng ta đã tìm hiểu khá nhiều các lớp đối tƣợng mà
ngôn ngữ C# cung cấp cho chúng ta. Và hiện tại chúng ta đã có thể viết đƣợc
các chƣơng trình C# thuần túy dùng Console làm giao diện kết xuất. Đối với
việc tìm hiểu bất cứ ngôn ngữ lập trình nào thì việc viết các chƣơng trình mà
giao diện càng đơn giản thì càng tốt. Trong bài học này chúng ta sẽ tìm hiểu
cách xây dựng các ứng dụng Windows thông qua Visual C#.
Trong bài này chúng ta sẽ tìm hiểu các lớp cơ sở mà .NET cung cấp, các lớp
này đơn giản giúp chúng ta thực hiện tốt các thao tác nhập xuất, các thao tác
truy cập hệ thống, thực thi các phép toán học,...
Mục tiêu:
 Trình bày đƣợc kiến thức về các lớp cơ sở.NET;
 Trình bày đƣợc các kiến thức về lớp đối tƣợng trong.NET;
 Sử dụng thành thạo các lớp cơ sở;
 Nghiêm túc, tỉ mỉ trong học lý thuyết và làm bài tập.
Nội dung chính
9.1 Lớp đối tƣợng trong .NET Framework
NET Framework chứa số lƣợng nhiều những kiểu dữ lớp, những kiểu liệt
kê, những cấu trúc, những giao diện và nhiều kiểu dữ liệu khác nữa. Thật vậy,
có hàng ngàn số lƣợng các kiểu nhƣ trên. Những lớp này điều cho phép chúng ta
sử dụng trong chƣơng trình C#.
Chúng ta sẽ tìm hiểu một vài kiểu dữ liệu thƣờng sử dụng trong chƣơng này.
Các lớp đƣợc trình bày thông qua các ví dụ minh họa đơn giản. Từ những ví dụ
minh họa cách sử dụng các lớp cơ sở này chúng ta có thể mở rộng để tạo ra các
chƣơng trình phức tạp hơn.
Common Language Specification (CLR)
Những lớp bên trong Framework đƣợc viết với ngôn ngữ đƣợc xác nhận
là chung nhất (CLR). CLR đã đƣợc đề cập vào phần đầu của sách khi chúng ta
thảo luận về MS.NET trong bài 1.
CLS là một tập hợp các luật hay các quy tắc mà tất cả các ngôn ngữ thực
hiện bên trong .NET platform phải tuân thủ theo. Tập hợp luật này cũng bao
gồm kiểu dữ liệu hệ thống chung, các kiểu dữ liệu cơ bản mà chúng ta đƣợc tìm
hiểu trong chƣơng 3 - Nền tảng ngôn ngữ C#. Bằng cách đƣa vào các tập luật
này, môi trƣờng thực thi chung sẽ có thể thực thi một chƣơng trình mà không
quan tâm đến cú pháp của ngôn ngữ đƣợc sử dụng.
Lợi ích theo sau của CLS là mã nguồn đƣợc viết trong một ngôn ngữ có thể
đƣợc gọi sử dụng bởi một ngôn ngữ khác Bởi vì thông thƣờng bên trong
Framework với CLS, chúng có thể sử dụng không chỉ ngôn ngữ C# mà còn bất
cứ ngôn ngữ tƣơng thích với CLS nhƣ là Visual Basic.NET và JScript.NET.
Kiểu dữ liệu trong namespace
Mã nguồn bên trong Framework đƣợc tổ chức bên trong namespace. Có hàng
trăm namespace bên trong Framework đƣợc sử dụng để tổ chức hàng ngàn lớp
đối tƣợng và các kiểu dữ liệu khác.
Một vài namespace thì đƣợc lƣu trữ bên trong namespace khác. Ví dụ chúng ta
đã sử dụng kiểu dữ liệu DateTime đƣợc chứa trong namespace System. Kiểu
Random cũng đƣợc chứa trong namespace System. Nhiều kiểu dữ liệu phục vụ
cho thao tác nhập xuất cũng đƣợc lƣu trữ trong một namespace chức trong
namespace System là namespace System.IO. Nhiều kiểu dữ liệu thƣờng dùng để
làm việc với dữ liệu XML thì đƣợc đặt bên trong namespace System.XML.
Chúng ta có thể tìm hiểu các namespace này trong các tài liệu trực tuyến của
Microsoft nhƣ MSDN Online chẳng hạn.
Tiêu chuẩn ECMA
Không phải tất cả kiểu dữ liệu bên trong namespace thì cần thiết phải tƣơng
thích với tất cả những ngôn ngữ khác. Hơn thế nữa, những công cụ phát triển
đƣợc tạo bởi những công ty khác cho ngôn ngữ C# có thể không bao hàm phải
tƣơng thích với mã nguồn thông thƣờng. Khi ngôn ngữ C# đƣợc hình thành.
Microsoft xác nhận đƣa ra một số lƣợng lớn các kiểu dữ liệu cho cùng một bảng
tiêu chuẩn cho C# để chuẩn hóa. Bằng cách xác nhận những kiểu dữ liệu theo
một tiêu chuẩn, điều này xem nhƣ việc mở cánh cửa cho những nhà phát triển
khác tạo ra các công cụ và trình biên dịch C# cùng sử dụng những namespace và
kiểu dữ liệu. Khi đó những mã nguồn bên trong những công cụ của Microsoft
tƣơng thích với bất cứ công cụ của các công ty khác.
Những lớp đối tƣợng đƣợc chuẩn hóa thì đƣợc định vị bên trong namespace
System. Những namespace khác chứa những lớp không đƣợc chuẩn hóa. Nếu
một lớp không phải là một phần của tiêu chuẩn, nó sẽ không đƣợc hỗ trợ trong
tất cả hệ điều hành và môi trƣờng thực thi mà chúng đƣợc viết để hỗ trợ C#. Ví
dụ, Microsoft thêm vào một vài namespace với SDK của nó nhƣ
Microsoft.VisualBasic, Microsoft.CSharp, MicrosoftJscript và
Microsoft.Win32. Những namespace này không phải là một phần của tiêu chuẩn
ECMA. Do đó chúng có thể không có giá trị trong tất cả môi trƣờng phát triển.
Tìm hiểu những lớp Framework
Nhƣ chúng ta đã biết là có hàng ngàn những lớp và những kiểu dữ liệu khác
bên trong thƣ viện cơ sở. Có thể sẽ tốn vài cuốn sách có kích thƣớc nhƣ giáo
trình này để nói toàn bộ về chúng. Trƣớc khi chúng ta tìm hiểu những lớp cơ
bản, bạn có thể xem tổng quan tài liệu trực tuyến để biết thêm các lớp cơ cở. Tất
cả các lớp và những kiểu dữ liệu khác đƣợc trình bày trong chƣơng này điều là
một phần của tiêu chuẩn đƣợc xác nhận bởi ECMA.
Lưu ý: Không những chúng ta có thể sử dụng những kiểu dữ liệu bên trong
những lớp thƣ viện mà chúng ta còn có thể mở rộng những kiểu dữ liệu này.
9.2 Lớp Timer
Chúng ta bắt đầu với ví dụ đầu tiên 9.1. Ví dụ minh họa này hết sức đơn
giản và đƣợc thiết kế không đƣợc tốt.
Ví dụ 9.1: Hiển thị thời gian.
// Timer01.cs: Hiển thị ngày và thời gian
// nhấn Ctrl+C để thoát
namespace Programming_Csharp
{
using System; public class Tester {
public static void Main()
{
while (true)
{
Console.WriteLine("\n {0}", DateTime.Now);
}
}
}
}

Kết quả:
12/24/200 3:21:20 PM
Nhƣ chúng ta có thể thấy, kết quả chƣơng trình đƣợc thực thi vào lúc 3:21
vào ngày 24 tháng 12. Danh sách này thể hiện một đồng hồ xuất hiện ở dòng
lệnh, và chúng dƣờng nhƣ là đƣợc
cập nhật trong mỗi giây đồng hồ. Thật vậy, nó thông thƣờng đƣợc cập nhật
nhiều hơn một lần, do đó chúng ta lƣu ý là giây đồng hồ thay đổi chỉ khi giá trị
xuất hiện thật sự khác nhau. Chƣơng trình sẽ chạy mãi đến khi nào ta nhấn thoát
bằng Ctrl + C.
Trong chƣơng trình ta sử dụng kiểu dữ liệu DateTime, đây là một cấu trúc đƣợc
chứa trong namespace System bên trong thƣ viện cơ sở. Cấu trúc này có một
thuộc tính tĩnh là Now trả về thời gian hiện hành. Có nhiều dữ liệu thành viên và
những phƣơng thức đƣợc thêm vào trong cấu trúc DateTime. Chúng ta có thể
tìm hiểu thêm về DateTime trong thƣ viện trực tuyến về các lớp cơ sở của .NET
Framework.
Cách tốt nhất để hiện thị ngày giờ trên màn hình là sử dụng Timer. Một Timer
cho phép một xử lý (hình thức của một delegate) đƣợc gọi tại một thời gian xác
định hay sau một chu kỳ nào đó trôi qua. Framework chứa một lớp Timer bên
trong namespace System.Timers. Lớp này đƣợc sử dụng trong ví dụ 12.2 theo
sau:
Ví dụ 9.2: Sử dụng Timer.
// Timer02.cs: hiểu thị ngày giờ sử dụng Timer
// nhấn Ctrl+C hay 'q' và Enter để thoát
namespace Programming_CSharp
{
using System;
using System.Timers; public class Tester
{
public static void Main()
{
Timer myTimer = new Timer();
// khai báo hàm xử lý
myTimer.Elapsed += new ElapsedEventHandler( DisplayTimeEvent);
// khoảng thời gian delay myTimer.Interval = 1000; myTimer.Start();
// thực hiện vòng lặp để chờ thoát while ( Console.Read() != 'q')
{
; // không làm gì hết!
}
}
public static void DisplayTimeEvent( object source, ElapsedEventArgs
t)
{
Console.Write("\n{0}", DateTime.Now);
}
}
}
Kết quả:
15/9/2014 9:45:20 PM
Kết quả thực hiện cũng giống nhƣ ví dụ trƣớc. Tuy nhiên, chƣơng trình
này thực hiện tốt hơn nhiều so với chƣơng trình ban đầu. Thay vì cập nhật
không ngừng ngày giờ đƣợc hiển thị, chƣơng trình này chỉ cập nhật sau khoảng
1 giây. Chúng ta hãy xem kỹ cách mà Timer làm việc. Một đối tƣợng Timer mới
đƣợc tạo ra, thuộc tính Interval đƣợc thiết lập. Tiếp theo phƣơng thức sẽ đƣợc
thực hiện sau khoảng thời gian interval đƣợc gắn với Timer. Trong trƣờng hợp
này là phƣơng thức DisplayTimeEvent sẽ đựơc thực thi, phƣơng thức đƣợc định
nghĩa ở bên dƣới.
Khi Timer thực hiện phƣơng thức Start thì nó sẽ bắt đầu tính interval. Một
thuộc tính thành viên khác của Timer là AutoReset mà chúng ta cũng cần biết
là: nếu chúng ta thay đổi giá trị mặc định của nó từ true sang false, thì sự kiện
Timer chỉ thực hiện duy nhất một lần. Khi AutoReset có giá trị true hay ta thiết
lập giá trị true thì Timer sẽ kích hoạt sự kiện và thực thi phƣơng thức mỗi một
thời gian đƣợc đƣa ra (interval).
Trong chƣơng trình này vẫn chứa một vòng lặp thực hiện đến khi nào
ngƣời dùng nhấn ký tự „q‟ và Enter. Nếu không chƣơng trình thực hiện tiếp tục
vòng lặp. Không có gì thực hiện trong vòng lặp này, nếu muốn chúng ta có thể
thêm vào trong vòng lặp những xứ lý khác. Chúng ta cũng không cần thiết phải
gọi phƣơng thức DisplayTimeEvent trong vòng lặp bởi vì nó sẽ đƣợc gọi tự
động vào khoảng thời gian xác định interval.
Timer trong chƣơng trình này dùng để thể hiện ngày giờ trên màn hình.
Timer và những sự kiện của Timer cũng có thể đƣợc sử dụng cho nhiều chƣơng
trình khác. Nhƣ chúng ta có thể tạo Timer để tắt một chƣơng trình khác vào một
thời điểm đƣa ra. Chúng ta cũng có thể tạo chƣơng trình backup thƣờng xuyên
để sao chép những dữ liệu quan trọng theo một định kỳ thời gian nào đó. Hay
chúng ta cũng có thể tạo một chƣơng trình tự động log off một ngƣời sử dụng
hay kết thúc một chƣơng trình sau một khoảng thời gian mà không có bất cứ
hoạt động nào xảy ra. Nói chung là có rất nhiều cách sử dụng Timer này, và lớp
Timer này rất hữa ích.
9.3 Lớp về thƣ mục và hệ thống
Đôi khi chúng ta cần biết thông tin hệ thống của máy mà chƣơng trình
đang thực hiện, điều này không khó khăn gì, .NET hỗ trợ một số lớp cơ bản để
thực hiện việc này. Trong ví dụ minh họa 9.3 bên dƣới sẽ trình bày cách lấy các
thông tin về máy tính và môi trƣờng của nó. Việc thực hiện này thông qua sử
dụng lớp Environment, trong lớp này chứa một số dữ liệu thành viên tĩnh và
chúng ta sẽ thú vị với thông tin của chúng.
Ví dụ 9.3: Sử dụng lớp Environment.
// env01.cs: hiển thị thông tin của lớp Environment namespace
Programming_CSharp
{
Using System;
class Tester {
public static void Main()
{
// các thuộc tính
Console.WriteLine("**************************");
Console.WriteLine("Command: {0}", Environment.CommandLine);
Console.WriteLine("Curr Dir: {0}", Environment.CurrentDirectory);
Console.WriteLine("Sys Dir: {0}", Environment.SystemDirectory);
Console.WriteLine("Version: {0}", Environment.Version);
Console.WriteLine("OS Version: {0}", Environment.OSVersion);
Console.WriteLine("Machine: {0}", Environment.MachineName);
Console.WriteLine("Memory: {0}", Environment.WorkingSet);
// dùng một vài các phƣơng thức Console
WriteLine("**************************"); string [] args =
Environment.GetCommandLineArgs(); for( int i = 0; i < args.Length;
i+ + )
{
Console.WriteLine("Arg {0}: {1}", i, args[i]);
}
Console WriteLine("**************************"); string []
drivers = Environment.GetLogicalDrives(); for( int i = 0; i <
drivers.Length; i+ + )
{
Console.WriteLine("Drive {0}: {1}", i, drivers[i]);
}
Console WriteLine("**************************");
Console.WriteLine("Path: {0}",
Environment.GetEnvironmentVariable("Path"));
Console WriteLine("**************************");
}
}
}
Kết quả thực hiện với máy tính của tác giả (kết quả sẽ khác với máy tính của
bạn:)

**************************
Command: D:\Working\ConsoleApplication1\bin\Debug\Env01.exe Curr Dir:
D:\Working\ConsoleApplication1\bin\Debug Sys Dir: C:\WINDOWS\System32
Version: 1.0.3705.0
OS Version: Microsoft Windows NT 5.1.2600.0 Machine: MUN Memory: 4 57
52 32 **************************
Arg 0: D:\Working\ConsoleApplication1\bin\Debug\Env01.exe
**************************
Drive 0: A:\
Drive 1: C:\
Drive 2: D:\
Drive 3: E:\
**************************
Path:
C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\
WINDOWS;C:\WINDO
WS\COMMAND;C:\NC
Nhƣ chúng ta thấy thì những thành viên tĩnh của lớp Environment cung
cấp cho ta những thông tin hệ thống và môi trƣờng. Đầu tiên là lệnh thực hiện
đƣợc đƣa ra chính là chƣơng trình đang thực thi tức là chƣơng trình Env01.exe,
thuộc tính đƣợc dùng để lấy là Command- Line. Thƣ mục hiện hành chính là thƣ
mục chứa chƣơng trình thực thi thông qua thuộc tính CurrentDirectory. Tƣơng
tự nhƣ vậy các thuộc tính hệ thống nhƣ: thƣ mục hệ thống, phiên bản OS, tên
máy tính, bộ nhớ cũng đƣợc lấy ra.
Tiếp theo là hai phƣơng thức của lớp Environment trả về mảng chuỗi ký tự, bao
gồm phƣơng thức lấy đối mục dòng lệnh GetCommandLineArgs và phƣơng
thức nhận thông tin về ở đĩa logic trong máy tính GetLogicalDrives. Hai vòng
lặp đơn giản là để xuất giá trị từng thành phần trong mảng ra.Cuối cùng là
phƣơng thức GetEnvironmentVariable nhận biến môi trƣờng và những giá trị
của chúng trong hệ thống hiện thời.
9.4 Lớp Math
Từ đầu tới giờ chúng ta chỉ thực hiện các phép toán cơ bản nhƣ cộng, trừ,
nhân, chia, chia dƣ. Còn rất nhiều các phép toán mạnh hơn và cũng thƣờng sử
dụng mà chúng chƣa đƣợc đề cập tới. C# cung cấp một tập hợp các phép toán
toán học bên trong những lớp cơ sở. Chúng đƣợc chứa bên trong của namespace
System.Math. Bảng 9.1 sau liệt kê những hàm toán học.
Lớp Math là lớp sealed, do đó chúng ta không thể xây dựng một lớp mới mà kế
thừa từ lớp này đƣợc. Thêm vào đó tất cả những lớp và dữ liệu thành viên đều là
tĩnh, do vậy chúng ta không thể tạo một đối tƣợng có kiểu Math. Thay vào đó
chúng ta sẽ sử dụng những thành viên và phƣơng thức với tên lớp.
Lớp Math
Phương thức Mô tả
Abs Trả về trị tuyệt đối của một số
Ceiling Trả về giá trị nhỏ nhất hay bằng giá trị đƣa ra
Exp Trả về giá trị e với mũ đƣa ra
Floor Trả về giá trị lớn nhất hay bằng giá trị đƣa ra
IEEERemainder Trả về phần dƣ của phép chia hai hai số thực.
Phép chia này theo tiêu chuẩn của IEEE cho
phép toán dấu chấm động nhị phân.
Log Trả về giá trị logarit của giá trị đƣa ra
Log10 Trả về giá trị logarit cơ số 10 của số đƣa ra
Max Trả về số lớn trong hai số
Min Trả về số nhỏ trong hai số
Pow Trả về kết quả xy
Round Trả về giá trị đƣợc làm tròn
Sign Trả về giá trị dấu của một số. -1 nếu số âm và 1
nếu số dƣơng
Sqrt Trả về căn bậc hai của một số
Acos Trả về giá trị một góc mà cosin bằng với giá trị
Asin Trả về giá trị một góc mà sin bằng với giá trị
đƣa ra
Atan Trả
đƣa về
ra giá trị của một góc mà tang bằng với góc
đƣa ra
Atan2 Trả về giá trị của một góc mà tang bằng với
tang của điểm (x,y) đƣa ra
Cos Trả về giá trị cosin của một góc đƣa ra
Cosh Trả về giá trị hyperbolic cosin của góc đƣa ra
Sin Trả về giá trị sin của góc đƣa ra

Trả về giá trị hyperbolic của góc đƣa ra


Tan Trả về giá trị tang của góc
Tanh Trả về giá trị hyperbolic tang của góc.

Hình 9.1: Phương thức của Math.


Ngoài ra lớp Math này cũng đƣa vào hai hằng số: PI và số E, PI trả về giá trị pi
trong toán học nhƣ là 3.14159265358979323846 Giá trị E trả về giá trị
2.7182818284590452354.
Hầu hết các hàm toán học trong bảng 9.1 trên dễ hiểu và dễ sử dụng. Ví dụ 9.4
sau minh họa việc sử dụng một số hàm toán học nhƣ sau:
Ví dụ 9.4: Sử dụng một vài hàm toán học.
using System;
namespace Programming_CSharp
{
public class Tester
{
public static void Main()
{
int val2; char ch;
for(double ctr = 0.0; ctr <= 10; ctr += .2)
{
val2 = (int) System.Math.Round((10*System.Math.Sin(ctr)));
for( int ctr2 = -10; ctr2 < = 10; ctr2+ + )
{
if (ctr2 == val2) ch = 'x'
else
ch = ' ';
Console.Write("{0}", ch);
}
Console.WriteLine(" ");
}
}
}
}
Vòng lặp đầu tiên thực hiện thông qua một biến lặp là một giá trị double, mỗi
bƣớc lặp tăng . 2. Giá trị Sin đƣợc lấy thông qua việc sử dụng hàm Math.Sin. Do
giá trị Sin từ -1 đến 1 và để cho dễ hiển thị hơn, giá trị này đƣợc chuyển từ -10
đến 10. Để chuyển thành giá trị này thì ta nhân với 10 rồi sau đó thực hiện việc
làm tròn bằng cách dùng hàm Round của Math.
Kết quả của phép nhân và làm tròn là một giá trị từ -10 đến 10 đƣợc gán cho
val2. Vòng lặp for thứ hai thực hiện việc xuất một ký tự ra màn hình.
9.5 Lớp thao tác tập tin
Khả năng để viết thông tin vào trong một tập tin hay đọc thông tin từ
trong một tập tin có thể làm cho chƣơng trình của chúng ta có nhiều hiệu quả
hơn. Hơn nữa, có rất nhiều lần khi chúng ta muốn có khả năng làm việc với
những tập tin hiện hữu. Phần tiếp sau chúng ta sẽ tìm hiểu những đặc tính cơ
bản của việc thao tác trên tập tin. Điều này sẽ đƣợc theo sau bởi một khái niệm
quan trọng của tập tin là luồng (stream).
Sao chép một tập tin
Một lớp tập tin tồn tại bên trong lớp cơ sở gọi là File, lớp này đƣợc định vị bên
trong namespace System.OI. Lớp File chứa một số các phƣơng thức tĩnh đƣợc
sử dụng để thao tác trên tập tin. Thật vậy, tất cả các phƣơng thức bên trong lớp
File điều là tĩnh. Bảng 9.2 liệt kê những phƣơng thức chính của lớp File.

Phƣơng thức Mô tả

AppendText Nôí văn bản vào một tập tin


Copy Tạo ra một tập tin mới từ tập tin hiện hữu
CreateText Tạo ra tập tin lƣu giữ text
Delete Xóa tập tin ở vị trí xác định. Tập tin phải hiện hữu
nếu không sẽ phát sinh ra ngoại lệ.
Exists Kiểm tra xem tập tin có thực sự hiện hữu ở vị trí nào
đó.
GetAttributes Lấy thông tin thuộc tính của tập tin. Thông tin này
bao gồm: tập tin có bị nén hay không, tên thƣ mục,
có thuộc tính ẩn, thuộc tính chỉ đọc, tập tin hệ
GetCreationTime Trả về ngày giờ tập tin đƣợc tạo ra
thống...
GetLastAccessTi Trả về ngày giờ tập tin đƣợc truy cập lần cuối
GetLastWriteTime
me Trả về ngày giờ tập tin đƣợc viết lần cuối
Move Cho phép tập tin đƣợc di chuyển vào vị trí mới và
đổi tên tập tin.
Open Mở một tập tin tại vị trí đƣa ra. Bằng việc mở tập tin
này chúng ta có thể viết thông tin hay đọc thông tin
từ nó.
OpenRead Mở một tập tin hiện hữu để đọc
OpenText Mở một tập tin để đọc dạng text
OpenWrite Mở một tập tin xác định để viết
SetAttributes Thiết lập thuộc tính cho tập tin
SetCreationTime Thiết lập ngày giờ tạo tập tin
SetLa Thiết lập lại ngày giờ mà tập tin đƣợc truy cập lần
stAccessTime cuối
SetLastWriteTime Thiết lập ngày giờ mà tập tin đƣợc cập nhật lần
Bảng 9.2cuối.
: Một số phƣơng thức chính thao tác tập tin.
Chƣơng trình 12.5 sau minh họa việc sao chép một tập tin.
Ví dụ 9.5: Sao chép một tập tin.
// file : filecopy.cs: sao chép một tập tin
namespace Programming_CSharp
{
using System;
using System.IO;
public class Tester
{
public static void Main()
{
string[] CLA = Environment.GetCommandLineArgs();
if ( CLA.Length < 3)
{
Console.WriteLine("Format: {0} orig-file new-file", CLA[0]);
}
else
{
string origfile = CLA[1]; string newfile = CLA[2];
Console.Write("Copy...");
try
{
File.Copy(origfile, newfile);
}
catch (System.IO.FileNotFoundException)
{
Console.WriteLine("\n{0} does not exist!", origfile); return;
}
catch (System.IO.IOException)
{
Console.WriteLine("\n{0} already exist!", newfile); return;
}
catch (Exception e)
{
Console.WriteLine("\nAn exception was thrown trying to copy
file");
Console.WriteLine();
return;
}
Console.WriteLine("...Done");
}
}
}
}
Chƣơng trình thực hiện bằng cách sau khi biên dịch ra tập tin filecopy.exe ta gọi
thực hiện tập tin này với tham số dòng lệnh nhƣ sau: filecopy.exe filecopy.cs
filecopy.bak Tập tin filecopy.cs đã hiện hữu và tập tin filecopy.bak thì chƣa hiện
hữu cho đến khi lệnh này thực thi. Sao khi thực thi xong chƣơng trình tập tin
filecopy.bak đƣợc tạo ra. Nếu chúng ta thực hiện chƣơng trình lần thứ hai cũng
với các tham số nhƣ vậy, thì chúng ta sẽ nhận đƣợc kết quả xuất nhƣ sau:
Copy...
filecopy.bak already exists!
Nếu chúng ta thực hiện chƣơng trình mà không có bất cứ tham số nào, hay chỉ
có một tham số, chúng ta sẽ nhận đƣợc kết quả nhƣ sau :
Format d:\working\filecopy\filecopy.exe orig-file new-file
Cuối cùng, điều tệ nhất xảy ra là chúng ta thực hiện sao chép nhƣng tập tin
nguồn không tồn tại:
Copy...
filecopy.cs does not exist!
Nhƣ chúng ta ta thấy tất cả các kết quả có thể có của chƣơng trình minh họa 9.5
trên. Chƣơng trình thực hiện việc sao chép một tập tin và nó kiểm tra tất cả các
tình huống có thể có và thực hiện việc xử lý các ngoại lệ. Điều này cho thấy
chƣơng trình vừa đáp ứng đƣợc mặt logic của lập trình vừa đáp ứng đƣợc việc
xử lý các ngoại lệ.
9.6 Làm việc với tập tin dữ liệu
Phần trƣớc chúng ta đã thực hiện công việc nhƣ lấy thông tin của tập tin và
sao chép tập tin sang một tập tin mới. Việc thực hiện quan trọng của tập tin là
đọc và viết những thông tin từ tập tin. Trong phần này chúng ta sẽ tìm hiểu về
luồng nhập xuất và cách tạo mới một tập tin để ghi hay mở một tập tin đã tồn tại
để đọc thông tin.
Luồng nhập xuất
Thuật ngữ tập tin thì nói chung là liên quan đến những thông tin lƣu trữ bên
trong ỗ đĩa hoặc trong bộ nhớ. Khi làm việc với tập tin, chúng ta bao hàm với
việc sử dụng một luồng.

Nhiều ngƣời nhầm lẫn về sự khác nhau giữa tập tin và luồng. Một luồng
đơn giản là luồng của thông tin, chứa thông tin sẽ đƣợc chuyển qua, còn tập tin
thì để lƣu trữ thông tin.
Một luồng đƣợc sử dụng để gởi và nhận thông tin từ bộ nhớ, từ mạng, web, từ
một chuỗi,...Một luồng còn đƣợc sử dụng để đi vào và ra với một tập tin dữ liệu.
Thứ tự của việc đọc một tập tin
Khi đọc hay viết một tập tin, cần thiết phải theo một trình tự xác định. Đầu
tiên là phải thực hiện công việc mở tập tin. Nếu nhƣ tạo mới tập tin, thì việc mở
tập tin cùng lúc với việc tạo ra tập tin đó. Khi một tập tin đã mở, cần thiết phải
tạo cho nó một luồng để đặt thông tin vào trong một tập tin hay là lấy thông tin
ra từ tập tin. Khi tạo một luồng, cần thiết phải chỉ ra thông tin trực tiếp sẽ đƣợc
đi qua luồng. Sau khi tạo một luồng gắn với một tập tin, thì lúc này chúng ta có
thể thực hiện việc đọc ghi các dữ liệu trên tập tin. Khi thực hiện việc đọc thông
tin từ một tập tin, chúng ta cần thiết phải kiểm tra xem con trỏ tập tin đã chỉ tới
cuối tập tin chƣa, tức là chúng ta đã đọc đến cuối tập tin hay chƣa. Khi hoàn
thành việc đọc ghi thông tin trên tập tin thì tập tin cần phải đƣợc đóng lại.
Tóm lại các bƣớc cơ bản để làm việc với một tậo tin là:
• Bƣớc 1: Mở hay tạo mới tập tin
• Bƣớc 2: Thiết lập một luồng ghi hay đọc từ tập tin
• Bƣớc 3: Đọc hay ghi dữ liệu lên tập tin
• Bƣớc 4: Đóng lập tin lại
Các phương thức cho việc tạo và mở tập tin
Có nhiều kiểu luồng khác nhau. Chúng ta sẽ sử dụng những luồng khác nhau
và những phƣơng thức khác nhau phụ thuộc vào kiểu dữ liệu bên trong của tập
tin. Trong phần này, việc đọc/ghi sẽ đƣợc thực hiện trên tập tin văn bản. Trong
phần kế tiếp chúng ta học cách đọc và viết thông tin trên tập tin nhị phân. Thông
tin nhị phân bao hàm khả năng mạnh lƣu trữ giá trị số và bất cứ kiểu dữ liệu nào
khác.
Để mở một tập tin trên đĩa cho việc đọc và viết tập tin văn bản, chúng ta cần
phải sử dụng cả hai lớp File và FileInfo. Một vài phƣơng thức có thể sử dụng
trong những lớp này. Các phƣơng thức này bao gồm:
AppendTex Mở một tập tin để và tập tin này có thể đƣợc thêm văn bản vào
t Tạo luồng StreamWriter sử dụng để thêm vào trong văn bản.
trong nó.
Create Tạo mới một tập tin
CreateText Tạo và mở một tập tin văn bản. Tạo ra một luồng
Open Mở một tập tin để đọc/viết. Mở một FileStream
StreamWriter.
OpenRead Mở một tập tin để đọc
OpenText Mở một tập tin văn bản để đọc. Tạo ra StreamReader để sử
OpenWrite Mở một tập tin cho việc đọc và ghi.
dụng.

Làm thế nào để chúng ta có thể biết đƣợc khi nào sử dụng lớp File chính xác
hơn là sử dụng lớp FileInfo nếu chúng cùng chứa những phƣơng thức tƣơng tự
với nhau. Thật ra hai lớp này có nhiều sự khác biệt. Lớp File chứa tất cả các
phƣơng thức tĩnh, thêm vào đó lớp File tự động kiểm tra permission trên một tập
tin. Trong khi đó nếu muốn dùng lớp FileInfo thì phải tạo thể hiện của lớp này.
Nếu muốn mở một tập tin chỉ một lần thì tốt nhất là sử dụng lớp File, còn nếu
chúng ta tổ chức việc sử dụng tập tin nhiều lần bên trong chƣơng trình, tốt nhất
là ta dùng lớp FileInfo. Hoặc nếu không chắc chắn cách sử dụng thì chúng ta có
thể sử dụng lớp FileInfo.
Viết vào một tập tin văn bản
Cách tốt nhất để nắm vững cách thức làm việc với tập tin là chúng ta sẽ bắt
tay vào tìm hiểu chƣơng trình. Trong phần này chúng ta sẽ tìm hiểu một ví dụ
minh họa việc tạo ra một tập tin văn bản rồi sau đó viết lại thông tin vào nó.
Ví dụ 9.6: Viết dữ liệu vào tập tin văn bản.
//writing.cs:: viết vào một tập tin văn bản
namespace Programming_Csharp
{
using System; using System.IO;
public classTester
{
public static void Main(String[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Phai nhap ten tap tin.");
}
else
{
StreamWriter myFile = File.CreateText( args[0]);
myFile.WriteLine("Khong co viec gi kho");
myFile.WriteLine("Chi so long khong ben");
myFile.WriteLine("Dao nui va lap bien");
myFile.WriteLine("Quyet chi at lam nen"); for(int i=0; i < 10;
i++)
{
myFile.Write("{0} ",i);
}
myFile.Close();
}
}
}
}
Khi chạy chƣơng trình trên chúng ta phải cung cấp tên của tập tin đƣợc
tạo mới, nếu không cung cấp thì chƣơng trình sẽ không thực hiện việc tạo tập
tin. Giả sử chúng ta có cung cấp một tham số dòng lệnh cho chƣơng trình thì
một tập tin văn bản mới đƣợc tạo ra có nội dung nhƣ sau:
Khong co viec gi kho
Chi so long khong ben
Dao nui va lap bien
Quyet chi at lam nen
0123456789
Trong chƣơng trình trên không thực hiện việc xử lý ngoại lệ. Điều này dẫn đến
chƣơng trình có thể phát sinh ra những ngoại lệ và những ngoại lệ này không
đƣợc xử lý. Đây là cách lập trình không tốt, nên yêu cầu ngƣời đọc nên thêm các
xử lý ngoại lệ vào chƣơng trình trên, ngoại lệ này cũng tƣơng tự nhƣ ngoại lệ
trong ví dụ trƣớc.
Nhƣ chúng ta thấy hàm Main có tham số và tham số này sẽ đƣợc nhận thông
qua dòng lệnh, trong các ví dụ trƣớc, chƣơng trình nhận tham số dòng lệnh
thông qua lớp Environment, còn ở đây ta khai báo tham số dòng lệnh trực tiếp
cho chƣơng trình. Dòng lệnh đầu tiên của hàm Main() là kiểm tra số tham số
nhập vào, nếu không có tham số nào chƣơng trình sẽ xuất ra thông báo là không
có tên tập tin và kết thúc chƣơng trình.
Trong trƣờng hợp cung cấp tham số đầy đủ chƣơng trình sẽ thực hiện việc tạo
mới tập tin. Phƣơng thức CreateText của lớp File đƣợc gọi để tạo ra một đối
tƣợng StreamWriter mới gọi là myFile. Tham số đƣợc truyền cho hàm là tên của
tập tin sẽ đƣợc tạo. Kết quả cuối cùng của dòng lệnh này là tạo ra một tập tin
văn bản mới. Dữ liệu s4 đƣợc đƣa vào tập tin thông qua StreamWriter với thể
của nó là myFile.
Ghi chú:Nếu một tập tin hiện hữu với cùng tên nhƣ tập tin mà chúng ta tạo ra
thì tập tin cũ sẽ đƣợc viết chồng, tức là dữ liệu bên trong tập tin cũ sẽ bị xóa
mất.
Hình sau minh họa việc thực hiện tạo tập tin và đƣa dữ liệu vào

Disk file
arg[0]

Hình 9.3 : Mô tả thực hiện tạo tập tin và đưa dữ liệu và


Khi một luồng đƣợc thiết lập và chỉ đến một tập tin, chúng ta có thể viết
vào trong luồng và nó sẽ đƣợc viết vào tập tin: myFile.WriteLine("Khong co
viec gi kho");
Dòng lệnh trên viết một chuỗi vào trong tập tin, việc viết này giống nhƣ là viết
ra màn hình console. Nhƣng ở đây là đƣợc viết ra thiết bị khác, tức là ra tập tin.
Sau khi thực hiện toàn bộ công việc, cần thiết phải đóng luồng lại bằng cách gọi
phƣơng thức Close.
Đọc tập tin văn bản
Đọc dữ liệu từ tập tin văn bản cũng tƣơng tự nhƣ việc viết thông tin vào nó.
Ví dụ minh họa tiếp sau đây thực hiện việc đọc tập tin mà chúng ta đã tạo ra từ
chƣơng trình minh họa 9.7 trƣớc. Đây là chƣơng trình đọc tập tin văn bản.
Ví dụ 9.7: Đọc một tập tin văn bản.
using System;
using System.IO;
namespace Programming_CSharp
{
public class Tester
{
public static void Main(string[] args)
{
if ( args.Length < 1)
{
Console.WriteLine("Phai nhap ten tap tin");
}
else
{ string buffer;
StreamReader myFile = File.OpenText( args[0]); while ( (buffer =
myFile.ReadLine()) ! = null)
{
Console.WriteLine(buffer);
}
myFile.Close();
}
}
}
}
Nếu chúng ta nhập đúng tên của tập tin vừa tạo trong ví dụ trƣớc thì kết quả
chƣơng trình thực hiện nhƣ sau:
Kết quả:
Khong co viec gi kho
Chi so long khong ben
Dao nui va lap bien
Quyet chi at lam nen
0123456789
Nếu chƣơng trình đƣợc cung cấp đầy đủ tên tập tin thì nó sẽ bắt đầu thực hiện
việc đọc thông tin. Đầu tiên là khai báo một chuỗi dùng làm chỗ lƣu thông tin
đọc tử tập tin ra. Để mở tập tin văn bản thì phƣơng thức OpenText đƣợc gọi.
Phƣơng thức này đƣợc truyền vào tên của tập tin văn bản cần mở. Một lần nữa
một luồng tên myFile đƣợc tạo ra gắn với tập tin đã cho, luồng này có kiểu là
StreamReader. Phƣơng thức ReadLine() của myFile thực hiện việc đọc từng
dòng trong tập tin và sau đó đƣợc xuất ra màn hình console. Việc đọc này kết
hợp với việc kiểm tra là đã đọc đến cuối tập tin chƣa, nếu đọc đến cuối tập tin,
tức là hàm ReadLine() trả về chuỗi rỗng, lúc này chƣơng trình sẽ kết thúc việc
đọc. Cuối cùng hàm Close đƣợc gọi để đóng tập tin lại, và chƣơng trình chấm
dứt.
Trong chƣơng trình này cũng không xử lý ngoại lệ, nếu tập tin không tồn tại
chƣơng trình sẽ phát sinh ra ngoại lệ và ngoại lệ này không đƣợc xử lý. Đề nghị
ngƣời đọc nên đƣa vào các đoạn xử lý ngoại lệ tƣơng tự nhƣ ví dụ 12.6
Viết thông tin nhị phân vào tập tin
Nếu chúng ta sử dụng một tập tin văn bản, thì khi chúng ta lƣu dữ liệu kiểu số
thì phải thực hiện việc chuyển đổi sang dạng chuỗi ký tự để lƣu vào trong tập tin
vănbản và khi lấy ra ta cũng lấy đƣợc giá trị chuỗi ký tự do đó ta phải chuyển
sang dạng số. Đôi khi chúng ta muốn có cách thức nào đó tốt hơn để lƣu trực
tiếp giá trị vào trong tập tin và sau đó đọc trực tiếp giá trị ra từ tập tin. Ví dụ khi
viết một số lƣợng lớn các số integer vào trong tập tin nhƣ là những số nguyên,
thì khi đó ta có thể đọc các giá trị này ra nhƣ là số integer. Trƣờng hợp nếu
chúng đƣợc viết vào tập tin với dạng văn bản, thì khi đọc ra ta phải đọc ra văn
bản và phải chuyển mỗi giá trị từ một chuỗi đến các số integer. Tốt hơn việc
phải thực hiện thêm các bƣớc chuyển đổi, ta có thể gắn một kiểu luồng nhị phân
BinaryStream vào trong một tập tin, rồi sau đó đọc và ghi thông tin nhị phân từ
luồng này.
Tiếp theo ta sẽ xem một ví dụ minh họa việc đọc viết dữ liệu nhị phân vào một
tập tin. Mặc dù trong chƣơng trình này thực hiện việc viết 100 giá trị integer vào
trong một tập tin nhƣng có thể dễ dàng viết bất cứ kiểu dữ liệu nào khác.
Ghi chú: Thông tin nhị phân là thông tin đã đƣợc định dạng kiểu lƣu trữ dữ liệu.
Ví dụ 9.8: Viết vào một tập tin nhị phân.
//binarywriter.cs
using System;
using System.IO;
namespace Programming_CSharp
{
public class Tester {
public static void Main(string[] args)
{
if ( args.Length < 1)
{
Console.WriteLine("Phai nhap ten tap tin!");
}
else
{
FileStream myFile = new FileStream( args[0], FileMode.CreateNew);
BinaryWriter bwFile = new BinaryWriter(myFile); for (int i=0; i <
100; i++)
{
bwFile.Write(i);
}
bwFile.Close();
myFile.Close();
}
}
}
}
Cũng tƣơng tự nhƣ các ví dụ trên thì tên tập tin đƣợc đƣa vào tham số dòng
lệnh. Nếu chƣơng trình đƣợc nhập các tham số đầy đủ, chƣơng trình sẽ thực
hiện việc viết thông tin nhị phân vào trong tập tin, và không có output ra màn
hình console. Nếu chúng ta mở tập tin và xem thì chỉ thấy các ký tự mở rộng
đựơc thể hiện, sẽ không thấy những số đọc đƣợc.
Trong chƣơng trình này cũng chƣa thực hiện việc xử lý các ngoại lệ. Nếu thực
hiện việc viết thông tin vào một tập tin đã hiện hữu, thì một ngoại lệ sẽ đƣợc
phát sinh khi thực hiện lệnh:
FileStream myFile = new FileStream( args[0], FileMode.CreateNew);
Đọc thông tin nhị phân từ tập tin
Trong phần trƣớc chúng ta đã thực hiện việc viết thông tin nhị phân vào
trong tập tin, và bây giờ chúng ta mong muốn đƣợc đọc các thông tin đã ghi vào
trong tập tin. Việc đọc thông tin cũng khá đơn giản nhƣ là việc viết vào. Chƣơng
trình 12.10 sau minh họa cho công việc này.
Ví dụ 9.9: Đọc thông tin nhị phân.
// BinaryRead.cs: Doc thong tin tu file nhi phan namespace
Programming_CSharp
{
using System; using System.IO;
public class Tester
{
public static void Main( String[] args)
{
if ( args.Length < 1)
{
Console.WriteLine("Phai nhap ten tap tin");
}
else
{
FileStream myFile = new FileStream( args[0], FileMode.Open);
BinaryReader brFile = new BinaryReader(myFile);
// đọc dữ liệu
Console.WriteLine("Dang doc tap tin....");
while (brFile.PeekChar() != -1)
{
Console.Write("<{0}>", brFile.ReadInt32());
}
Console.WriteLine("....Doc xong"); brFile.Close();
myFile.Close();
}
}
}
}
Kết quả:
Dang doc tap tin....
<0> <1> <2> <3> <4> <5> <6> <7> <8> <9> <10> <11> <12> <13> <14>
<15> <16> <17>
<18> <19> <2 0> <21> <22> <23> <24> <25> <26> <27> <28> <29> <30>
<31> <32> <33>
<34> <35> <36> <37> <38> <39> <40> <41> <42> <43> <44> <45> <46>
<47> <48> <4 9>
<50> <51> <52> <53> <54> <55> <56> <57> <58> <59> <60> <61> <62>
<63> <64> <65>
<66> <67> <68> <69> <70> <71> <72> <73> <74> <75> <76> <77> <78>
<79> <80> <81>
<82> <83> <84> <85> <86> <87> <88> <89> <90> <91> <92> <93> <94>
<95> <96> <97>
<98> <99> ....Doc xong!
Với ứng dụng này, chúng ta có thể đọc dữ liệu mà chúng ta đã viết trong ví dụ
trƣớc. Trong ví dụ này chúng ta tạo ra luồng FileStream. Lúc này, mode thao tác
của tập tin đƣợc sử dụng là mode FileMode.Open. Sau đó chúng ta thực hiện
việc gắn luồng này với luồng BinaryReader trong dòng tiếp sau, luồng này sẽ
giúp cho chúng ta đọc thông tin nhị phân:
FileStream myFile = new FileStream( args[0], FileMode.Open);
BinaryReader brFile = new BinaryReader(myFile);
Sau khi tạo ra luồng giúp cho việc đọc thông tin nhị phân từ tập tin, chƣơng
trình bắt đầu đọc thông qua vòng lặp:
while (brFile.PeekChar() != -1)
{
Console.Write("<{0}>", brFile.ReadInt32());
}
Ở đây có một vài sự khác nhỏ, phƣơng thức PeekChar của lớp BinaryReader
đƣợc sử dụng. Phƣơng thức này sẽ lấy ký tự kế tiếp trong luồng. Nếu ký tự kế
tiếp là cuối tập tin thì giá trị -1 đƣợc trả về. Ngƣợc lại, thì ký tự kế tiếp đƣợc trả
về Khi ký tự kế tiếp không phải ký tự cuối tập tin thì lệnh bên trong vòng lặp sẽ
đọc một số integer từ đối tƣợng BinaryStream brFile.
Phƣơng thức đƣợc sử dụng để đọc một số nguyên là ReadInt32, chúng ta sử
dụng kiểu tên của Framework tốt hơn là kiểu do C# đƣa ra. Nên nhớ rằng, tất cả
những lớp từ Framework điều đƣợc gọi bởi ngôn ngữ C# và chúng không phải
là một bộ phận của ngôn ngữ C#. Những lớp này còn đƣợc sử dụng tốt bởi
những ngôn ngữ khác C#.
Ngoài ra lớp BinaryReader còn có những phƣơng thức khác để thực hiện việc
đọc các kiểu dữ liệu khác nhau. Những phƣơng thức đọc này đƣợc sử dụng cùng
với cách mà ReadInt32 đƣợc sử dụng trong chƣơng trình.
Bài tập
1. Viết một chƣơng trình minh họa việc truy xuất thông tin hệ thống của máy tính
đang sử dụng. Thông tin này bao gồm: tên máy tính, hệ điều hành, bộ nhớ, đĩa
cứng...
2. Viết chƣơng trình minh họa một máy tính cá nhân cho phép thực hiện các phép
toán cơ bản. Chƣơng trình hiện ra một menu các lệnh và mỗi lệnh đƣợc gán cho
một số: nhƣ công thì số 1, trừ số 2, nhân 3,... Cho phép ngƣời dùng chọn một lệnh
thông qua nháp vào số tƣơng ứng. Sau đó cho ngƣời dùng nháp vào từng toán hạng
rồi thực hiện phép toán và cuối cùng in kết quả ra màn hình.
3. Viết chƣơng trình cho phép xem thông tin về một táp tin. Chƣơng trình cho
ngƣời dùng nháp vào tên táp tin rồi sau đó lần lƣợt hiển thị các thông tin nhƣ:
thuộc tính táp tin, ngày giờ tạo láp, kích thƣớc táp tin...
4. Viết chƣơng trình xem táp tin văn bản giống nhƣ lệnh type của DOS. Chƣơng
trình cho phép ngƣời dùng nháp tên táp tin thông qua tham số dòng lệnh. Nếu
ngƣời dùng không nháp qua tham số dòng lệnh thì yêu cầu nháp vào.
5. Viết chƣơng trình cho phép ngƣời dùng nháp vào một mảng số nguyên. Sau đó
sắp xếp mảng này theo thứ tự tăng dần rồi lƣu mảng vào một táp tin trên đĩa với
dạng nhị phân.
BÀI TẬP MỞ RỘNG VÀ NÂNG CAO
Bài 1:
Yêu Cầu
Thiết kế form nhƣ hình bên dƣới

Viết lệnh để chƣơng trình hoạt động nhƣ sau:


+ Ngƣời sử dụng sẽ nhập Password vào textbox , sau đó nhấn nút “Hiễn Thị‟ thì
nội dung của password sẽ đƣợc hiễn thị ra trên label bên dƣới
Hƣớng dẫn:
Trong sự kiện Click của button “Hiễn Thị”, gõ vào câu lệnh sau
lblHienThi.Text = txtPassWord.Text;
+ Ngƣời sử dụng nhấn nút “Tiếp” để xoá nội dung textbox, label và đặt con trỏ vào
texttbox password để ngƣời sử dụng nhập nội dung mới thuận lợi.
Trong sự kiện Click của button “Tiếp”, gõ vào đoạn lệnh sau
lblHienThi.Text = "";
txtPassWord.Clear();
txtPassWord.Focus();
+ Nhấp nút “Đóng” để kết thúc chƣơng trình.
Trong sự kiện Click của button “Đóng”, gõ vào câu lệnh sau
this.Close();
Bài 2:
Thiết kế giao diện nhƣ hình sau:
btnHo

lblHoTen
btn_Ho
txtHo txtTen

Viết lệnh để chƣơng trình hoạt động nhƣ sau:


 Nhấn vào Button btnHo thì gán nội dung của txtHo cho lblHoTen
 Nhấn vào Button btnTen thì gán nội dung của txtTen cho lblHoTen
 Nhấn vào Button btnHoTen thì gán nội dung của txtHo cộng với txtTen cho
lblHoTen
 Nhấn Double click vào lblHoTen thì nội dung của lblHoTen bị xoá
 Nhấn vào btnKetThuc thi đóng chƣơng trình
Hƣớng dẫn:
lblHoTen.Text = txtHo.Text + “ “ + txtTen.Text
Bài 3:
Thiết kế form và thực hiện các chức năng sau:
Khi ngƣời sử dụng nhập một số vào textbox rồi Enter hoặc nhấp vào nút cập
nhật thì số này đƣợc thêm vào listbox, đồng thời nội dung trong textbox bị xóa và
focus đƣợc chuyển về textbox.
Ngƣời dùng nhấn vào nút nào thì thực hiện chức năng tƣơng ứng của nút đó.
Gợi ý:
Lƣu ý thuộc tính AcceptButton của form
Phần tử đầu tiên trong danh sách có Index = 0
Bài 4:
Thiết kế form và thực hiện các chức năng sau

- Quy định form hiễn thị giữa màn hình. Không cho ngƣời sử dụng thay đổi
kích thƣớc form.
- Quy định việc di chuyển Tab hợp lý.
- Các ListBox đƣợc phép chọn nhiều mục (kết hợp giữa phím Shift, Control
và chuột)
- Khi ngƣời sử dụng Click nút “Cập nhật” hoặc nhấn phím Enter thì mặt định
nhập tên sinh viên từ textbox vào danh sách lớp A (không chấp nhận dữ liệu
rỗng). Theo đó ngƣời sử dụng có thể chọn lớp để cập nhật từ combobox lớp.
- Các nút “>” và “<” khi đƣợc Click sẽ di chuyển tất cả các mục đang chọn
sang ListBox bên kia tƣơng ứng.
- Các nút “>>” và “<<” khi đƣợc Click sẽ di chuyển toàn các mục sang
Listbox bên kia tƣơng ứng.
- Nút lệnh “Xóa lớp A”, “Xóa lớp B” cho phép xóa các mục đang chọn trong
list hiện hành.
Bài 5:
Thiết kế giao diện nhƣ hình vẽ

Yêu cầu:
 Khi form thực thi
 Tất cả text boxes và labels đều rỗng.
 Đặt focus vào text box Tên Hàng và xác lập việc di chuyển Tab hợp lý
(menu View\Tab Order)
 Form hiễn thị giữa màn hình. (StartPosition =CenterScreen )
 Thiết lập nút Thanh Tóan nhận sự kiện phím Enter (AcceptButton), nút Tiếp
nhận sự kiện phím ESC (CancelButton)
 Thực hiện canh lề phải cho các đối tƣợng chứa dữ liệu là số (Text
Align=Right)
 Xử lý nút lệnh “Thanh Tóan”
Các text box Số lƣợng, Đơn Giá buộc phải có giá trị và chỉ cho phép nhập số.
Thêm vào đó:
 Số lƣợng >=0. (int)
 Đơn giá >=0 (decimal)
Cung cấp các Message Box khác nhau để thông báo rõ ràng cho việc dữ liệu bị
nhập sai.
Tính toán cho mặt hàng hiện tại xuất kết quả ra label Thành Tiền.
Thành tiền = số lƣợng * đơn giá;
 Nút Tiếp:
Xóa nội dung các textbox và label
Đặt focus vào textbox đầu tiên
 Nút Kết Thúc:
Phát sinh Messagebox hỏi lại việc đóng form có chính xác không, nếu ngƣời sử
dụng xác nhận bằng nút Yes thì cho thóat chƣơng trình
Bài 7:
Mục đích:
Sử dụng hằng số, biến toàn cục (module variable)
Yêu cầu
Thiết kế giao diện nhƣ hình bên dƣới.

Yêu cầu:
1) Sinh viên tự thiết kế các đối tƣợng trên form cho phù hợp
2) Thiết đặt TabIndex cho hợp lý.
3) Nhấn nút Tính hoặc Enter thì tính và hiện thông tin lên các ô tƣơng ứng.
4) Nhấn vào Exit hoặc Esc thì thoát chƣơng trình.
Ghi chú: sử dụng biến toàn cục và hằng số cho hợp lý
Bài 8:
Mục đích:
Sử dụng hằng số, biến toàn cục (module variable)
Yêu cầu
Thiết kế giao diện nhƣ hình bên dƣới.
1. Khi chƣơng trình vừa thực thi:
- Form đƣợc hiển thị giữa màn hình. Canh lề phải cho các control chứa dữ liệu
là số, thiết lập hệ thống Tab hợp lý.
- Thiết lập nút “Tính TT” nhận phím Enter thay cho sự kiện Click.
2. Yêu cầu cài đặt
Tên KHách hàng không đƣợc phép rỗng.
Số lƣợng sách là số nguyên dƣơng
Mỗi quyển sách đều đƣợc bán với giá là 20000.
 Nút “Tính TT”: tính thành tiền và xuất kết quả lên lable Thành tiền
Thành tiền = Số lƣợng * Đơn Giá ;
 Nút “Tiếp”:
 Xóa nội dung các control trong gourpbox “Hóa Đơn”
 Đặt focus cho Textbox “Tên Khách Hàng”
 Nút “Thống Kê”: Tính và hiển thị kết quả trên các lable trong groupbox
“Thống kê”
 Nút “Kết Thúc”: Phát sinh messageBox hỏi lại ngƣời dùng có thật sự muốn
đóng ứng dụng hay không?
Bài 9:
Mục đích:
Sử dụng cấu trúc điều khiển dạng lựa chọn
Yêu cầu
Thiết kế giao diện nhƣ hình bên dƣới.

 Nút “Thực hiện đổi”: sẽ đổi năm dƣơng lịch sang năm âm lịch tƣơng
ứng.
Bài 10:
Mục đích:
Sử dụng mảng một chiều
Yêu cầu:
Thiết kế Form nhƣ hình sau:

Trong đó 2 group box sẽ chứa 2 label, để hiễn thị mảng và kết quả xử lý trên
các button tƣơng ứng.
Hƣớng dẫn :
1. khai báo mảng tòan cục, với kích thƣớc mảng MAX_SIZE là 1 hằng số.

2. Khởi tạo mảng trong Form_Load


3. Xây dựng các phƣơng thức theo yêu cầu
4. Gọi sử dụng các phƣơng thức tại các button.
Bài 11:
Tạo ứng dụng theo mô hình hƣớng đối tƣợng:

Yêu cầu:
Thiế kế class Hóa Đơn đáp ứng yêu cầu xử lý của giao diện trên.

Hƣớng dẫn:
1. Tạo các field private:
Tên hàng, số lượng, đon giá.
2. Tạo các field static:
Số hóa đon, doanh thu.
3. Tạo các property thông thƣờng.
4. Tạo property ReadOnly đối với các Field static.
5. Tạo các Contructor
6. Tạo các phƣơng thức
Bài 12:
Yêu cầu:
1. Thiết kế Form nhƣ hình sau:

2. Thiết kế class Hóa Đơn đáp ứng yêu cầu xử lý của giao diện trên.

Hƣớng dẫn:
 Tạo các field private:
Họ tên, địa chỉ, điện thoại, giá tiền, màu xe.
 Tạo các field static:
Số khách hàng, số khách mua xe đen, doanh thu.
 Tạo các property thông thƣờng.
 Tạo property ReadOnly đối với các Field static.
 Tạo các Contructor
Ví dụ:

Xây dựng các phƣơng thức xử lý tính toán.


Ví dụ:
TÀI LIỆU THAM KHẢO
1) Phạm Hữu Khang, C# 2005 cơ bản, Nxb Lao Động Xã Hội, 2006.
2) Phạm Hữu Khang, C# 2005 Tập 2-Lập trình Windows Form, Nxb Lao Động Xã
Hội, 2006.
3) Dƣơng Quang Thiện, Lập trình Visual C# như thế nào? Tập 1,2,3, Nxb Tổng
hợp Tp HCM, 2O05.
4) Ths.Nguyễn Cẩn, Tự học ngôn ngữ lập trình C++,Nbx Đồng Nai,1996 .
5) Jesse Liberty, Programming C#, 2nd Edition, tr 1-320 ,Nxb OReilly.
6) Ben Albahari, CSharp Essentials, 2nd Edition, tr 1-88, Nxb OReilly.
7) VN-Guide, Lâp trình Java, Nxb Thống Kê, 2000.

You might also like