🔙 Quay lại trang tải sách pdf ebook Các Giải Pháp Lập Trình C#
Ebooks
Nhóm Zalo
1
2
tổng hợp và biên dịch
Nguyễn Ngọc Bình Phương - Thái Thanh Phong
cùng sự cộng tác của
Nguyễn Thanh Nhân - Trần Lê Vĩnh Phong Nguyễn Quang Nam - Đinh Phan Chí Tâm Bùi Minh Khoa - Lê Ngọc Sơn
Thái Kim Phụng - Lê Trần Nhật Quỳnh
Chịu trách nhiệm xuất bản: TS. Nguyễn Xuân Thủy Biên tập: Hồ Nguyễn Thị Thanh Thúy
Trình bày bìa: Nguyễn Thị Thanh Thủy
Chế bản & Sửa bản in: Nguyễn Ngọc Bình Phương
Nhà sách Đất Việt
Địa chỉ: 225 Nguyễn Tri Phương, Q.5, TP. Hồ Chí Minh Điện thoại: (08) 2 652 039
E-mail: [email protected]
3
Website: www.dvpub.com.vn
4
Nguyễn Ngọc Bình Phương - Thái Thanh Phong
tổng hợp & biên dịch
http://www.dvpub.com.vn/dv/details.aspx?itemid=243
5
NHÀ XUẤT BẢN GIAO THÔNG VẬN TẢI
6
7
LỜI NÓI ĐẦU
LỜI NÓI ĐẦU
C
ác giải pháp lập trình C# khảo sát chiều rộng của thư viện lớp .NET Framework và cung cấp giải pháp cụ thể cho các vấn đề thường gặp. Mỗi giải pháp được trình bày theo dạng “vấn đề/giải
p
háp” một cách ngắn gọn và kèm theo là các ví dụ mẫu.
Các giải pháp lập trình C# không nhằm mục đích hướng dẫn bạn cách lập trình C#. Tuy vậy, ngay cả khi mới làm quen với lập trình ứng dụng được xây dựng trên .NET Framework với C#, bạn cũng sẽ nhận thấy quyển sách này là một tài nguyên vô giá.
Ở mức lý tưởng, khi bạn đối mặt với một vấn đề, quyển sách này sẽ cung cấp một giải pháp—hay ít nhất nó sẽ gợi cho bạn hướng đi đúng. Ngay cả nếu bạn chỉ muốn mở rộng kiến thức của mình về thư viện lớp .NET, Các giải pháp lập trình C# cũng là một tài liệu rất hữu ích.
Bạn không thể trở nên thành thạo C# và các lớp trong thư viện lớp .NET nếu chỉ đơn thuần đọc về chúng, bạn phải sử dụng và thử nghiệm chúng bằng cách viết thật nhiều chương trình. Cấu trúc và nội dung của quyển sách này cũng như tính khả thi trong thế giới thực của các giải pháp được đưa ra sẽ cung cấp điểm khởi đầu hoàn hảo, để từ đó làm bàn đạp cho việc thử nghiệm của chính bạn.
Phần mã lệnh trong quyển sách này đã được viết và chạy thử nghiệm trên phiên bản 1.1 của .NET Framework. Trong nhiều trường hợp, bạn sẽ nhận thấy ví dụ mẫu này cũng sẽ chạy trên phiên bản 1.0 hay 2.0 của .NET Framework, tuy nhiên điều này chưa được thử nghiệm.
Chúng tôi xin chân thành cảm ơn các bạn Nguyễn Thanh Nhân, Trần Lê Vĩnh Phong, Nguyễn Quang Nam, Đinh Phan Chí Tâm, Bùi Minh Khoa, Lê Ngọc Sơn, Thái Kim Phụng, và Lê Trần Nhật Quỳnh đã có những đóng góp
8
quý báu cho quyển sách; cảm ơn Nhà xuất bản Giao thông Vận tải và Nhà sách Đất Việt đã tạo điều kiện cho quyển sách này đến với bạn đọc.
Do lần đầu tiên xuất bản nên quyển sách này khó tránh khỏi những thiếu sót. Rất mong nhận được ý kiến đóng góp và nhận xét của các bạn để lần tái bản sau được hoàn thiện hơn.
Trân trọng cảm ơn
9
10
CẤU TRÚC CỦA SÁCH
CẤU TRÚC CỦA SÁCH
Quyển sách này được chia thành 17 chương, mỗi chương tập trung vào một chủ đề cụ thể trong quá trình tạo các giải pháp C#.
Chương 1: PHÁT TRIỂN ỨNG DỤNG
Chương 2: THAO TÁC DỮ LIỆU
Chương 3: MIỀN ỨNG DỤNG, CƠ CHẾ PHẢN CHIẾU,
VÀ SIÊU DỮ LIỆU
Chương 4: TIỂU TRÌNH, TIẾN TRÌNH, VÀ SỰ ĐỒNG BỘ
Chương 5: XML
Chương 6: WINDOWS FORM
Chương 7: ASP.NET VÀ WEB FORM
Chương 8: ĐỒ HỌA, ĐA PHƯƠNG TIỆN, VÀ IN ẤN
Chương 9: FILE, THƯ MỤC, VÀ I/O
Chương 10: CƠ SỞ DỮ LIỆU
Chương 11: LẬP TRÌNH MẠNG
Chương 12: DỊCH VỤ WEB XML VÀ REMOTING
Chương 13: BẢO MẬT
Chương 14: MẬT MÃ
Chương 15: KHẢ NĂNG LIÊN TÁC MÃ LỆNH
KHÔNG-ĐƯỢC-QUẢN-LÝ
Chương 16: CÁC GIAO DIỆN VÀ MẪU THÔNG DỤNG
Chương 17: SỰ HÒA HỢP VỚI MÔI TRƯỜNG WINDOWS
11
12
QUY ƯỚC
QUY ƯỚC
Quyển sách này sử dụng các quy ước như sau:
Về font chữ
■ Chữ in nghiêng—Dùng cho tên riêng, tên file và thư
mục, và đôi khi để nhấn mạnh.
■ Chữ với bề rộng cố định (font Courie New)—Dùng cho
các đoạn chương trình, và cho các phần tử mã lệnh như
câu lệnh, tùy chọn, biến, đặc tính, khóa, hàm, kiểu, lớp,
không gian tên, phương thức, module, thuộc tính, thông
số, giá trị, đối tượng, sự kiện, phương thức thụ lý sự kiện,
thẻ XML, thẻ HTML, nội dung file, và kết xuất từ các câu
lệnh.
■ Chữ in đậm với bề rộng cố định—Dùng trong các đoạn
chương trình để nêu bật một phần quan trọng của mã lệnh
hoặc dùng cho các dòng lệnh, câu lệnh SQL.
Về ký hiệu
Vấn đề Thủ thuật
13
Giải pháp Ghi chú
14
15
YÊU CẦU VỀ HỆ THỐNG
YÊU CẦU VỀ HỆ THỐNG
Để chạy được những ví dụ mẫu đi kèm quyển sách này, bạn sẽ cần
những phần mềm sau đây:
■ Microsoft .NET Framework SDK version 1.1
■ Microsoft Visual Studio .NET 2003
■ Microsoft Windows 2000, Windows XP,
hoặc Microsoft Windows Server 2003
■ Microsoft SQL Server 2000 hoặc MSDE
đối với các mục trong chương 10
■ Microsoft Internet Information Services (IIS)
đối với một số mục trong chương 7 và chương 12
Yêu cầu tối thiểu về phần cứng là bộ vi xử lý Pentium II 450 MHz,
với dung lượng RAM tối thiểu là 128 MB nếu bạn đang sử dụng
Microsoft Windows 2000, và là 256 MB nếu bạn đang sử dụng
Windows XP, Windows 2000 Server, hay Windows Server 2003. Bạn
cần khoảng 5 GB dung lượng đĩa cứng còn trống để cài đặt Visual
Studio .NET 2003. Những giá trị này là mức tối thiểu, quá trình phát
triển sẽ dễ dàng hơn trên một hệ thống với dung lượng RAM lớn và
đĩa cứng còn trống nhiều.
Mặc dù bản hiện thực .NET Framework cho Windows của Microsoft
là tiêu điểm của quyển sách này, một mục tiêu quan trọng là cấp một
16
tài nguyên hữu ích cho những lập trình viên C# không quan tâm đến
nền mà họ đang làm việc hoặc công cụ mà họ truy xuất. Ngoài
những chủ đề đặc biệt không được hỗ trợ trên tất cả nền .NET (như
Windows Form, ADO.NET, và ASP.NET), nhiều ví dụ mẫu trong
quyển sách này đều hợp lệ trên tất cả bản hiện thực .NET.
17
18
CÁCH SỬ DỤNG ĐĨA CD
CÁCH SỬ DỤNG ĐĨA CD
M
ã lệnh được cấp ở dạng tập các giải pháp và dự án Visual Studio .NET 2003, được tổ chức theo chương và số đề mục. Mỗi chương là một giải pháp độc lập, và
mỗi đề mục là một dự án độc lập bên trong giải pháp của chương. Một vài đề mục trong chương 11 và chương 12 trình bày về lập trình mạng gồm những dự án độc lập có chứa các phần client và server trong giải pháp của đề mục.
Mặc dù tất cả những ví dụ mẫu được cấp ở dạng dự án Visual Studio .NET, nhưng hầu hết đều bao gồm một file nguồn đơn mà bạn có thể biên dịch và chạy độc lập với Visual Studio .NET. Nếu không sử dụng Visual Studio .NET 2003, bạn có thể định vị mã nguồn cho một đề mục cụ thể bằng cách duyệt cấu trúc thư mục của ví dụ mẫu. Ví dụ, để tìm mã nguồn cho mục 4.3, bạn sẽ tìm nó trong thư mục Chuong04\04-03. Nếu sử dụng trình biên dịch dòng lệnh thì phải bảo đảm rằng bạn đã thêm tham chiếu đến tất cả các assembly cần thiết.
Một số ứng dụng mẫu yêu cầu các đối số dòng lệnh (sẽ được mô tả trong phần văn bản của đề mục). Nếu sử dụng Visual Studio .NET, bạn có thể nhập các đối số này trong Project Properties (mục Debugging của phần Configuration Properties). Nhớ rằng, nếu cần nhập tên thư mục hay file có chứa khoảng trắng thì bạn cần đặt tên đầy đủ trong dấu nháy kép.
Tất cả ví dụ truy xuất dữ liệu ADO.NET được tạo với SQL Server 2000. Chúng cũng có thể được sử dụng với SQL Server 7 và MSDE.
19
Visual Studio .NET có chứa các kịch bản SQL để cài đặt các cơ sở dữ liệu mẫu Northwind và Pubs nếu chúng chưa hiện diện (các file instnwnd.sql và instpubs.sql trong thư mục C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\ v1.1\Samples\Setup). Bạn có thể chạy các kịch bản này bằng Query Analyzer (với SQL Server) hay OSQL.exe (với MSDE).
Để sử dụng các đề mục trong chương 7 và chương 12, bạn cần chép chúng vào thư mục I:\CSharp\ (đường dẫn này là mã cứng trong các file dự án Visual Studio .NET). Bạn cũng sẽ cần tạo một thư mục ảo có tên là CSharp ánh xạ đến I:\CSharp. Bạn có thể cài đặt phép ánh xạ này bằng IIS Manager. Thực hiện theo các bước dưới đây:
1. Khởi chạy IIS Manager (chọn Start | Control Panel | Administrative Tools | Internet Information Services). 2. Khởi chạy Virtual Directory Wizard trong IIS Manager bằng cách nhắp phải vào Default Web Site và chọn New | Virtual Directory từ menu ngữ cảnh.
3. Nhắp Next để bắt đầu. Mẩu thông tin đầu tiên là bí danh CSharp. Nhắp Next để tiếp tục.
4. Mẩu thông tin thứ hai là thư mục vật lý I:\CSharp. Nhắp Next để tiếp tục.
5. Cửa sổ thuật sĩ cuối cùng cho phép bạn điều chỉnh quyền cho thư mục ảo. Bạn nên sử dụng các thiết lập mặc định. Nhắp Next.
6. Nhắp Finish để kết thúc trình thuật sĩ. Bạn sẽ thấy thư mục ảo này trong phần cây của IIS Manager.
7. Khai triển thư mục ảo CSharp trong IIS thành thư mục nằm trong CSharp\Chuong07\07-01.
8. Nhắp phải vào thư mục này, chọn Properties, rồi nhắp vào nút Create trong thẻ Directory để chuyển thư mục này thành thư mục ứng dụng Web.
9. Lặp lại bước 8 cho mỗi mục trong chương 7.
10. Theo trình tự đã được trình bày trong các bước 7-9, tạo thư mục ứng dụng Web cho các đề mục 12.2, 12.3, 12.4, và 12.6 trong chương 12.
20
MỤC LỤC
MỤC LỤC
L ỜI NÓI ĐẦU ................................................................................................................................7 C ẤU TRÚC CỦA SÁCH ..............................................................................................................1 0 Q UY ƯỚC ...................................................................................................................................1 2 Y ÊU CẦU VỀ HỆ THỐNG .......................................................................................................... 1 5 C ÁCH SỬ DỤNG ĐĨA CD ...........................................................................................................1 8 M ỤC LỤC ....................................................................................................................................2 0
C hương 1: PHÁT TRIỂN ỨNG DỤNG 2 9 1 . Tạo ứng dụng Console ........................................................................................... 3 1 2 . Tạo ứng dụng dựa-trên-Windows ...........................................................................3 3 3 . Tạo và sử dụng module .......................................................................................... 3 7 4 . Tạo và sử dụng thư viện .........................................................................................3 9 5 . Truy xuất các đối số dòng lệnh ...............................................................................4 0 6 . Chọn biên dịch một khối mã vào file thực thi ..........................................................4 2 7. Truy xuất một phần tử chương trình có tên trùng với một từ khóa ........................................................................................4 5
21
8 . Tạo và quản lý cặp khóa tên mạnh .........................................................................4 5 9 . Tạo tên mạnh cho assembly ...................................................................................4 7 1 0. Xác minh một assembly tên mạnh không bị sửa đổi ............................................ 4 9 1 1. Hoãn việc ký assembly ......................................................................................... 5 0 1 2. Ký assembly với chữ ký số Authenticode .............................................................5 2 1 3. Tạo và thiết lập tin tưởng một SPC thử nghiệm ................................................... 5 4 1 4. Quản lý Global Assembly Cache .......................................................................... 5 6 1 5. Ngăn người khác dịch ngược mã nguồn của bạn ................................................ 5 6
C hương 2: THAO TÁC DỮ LIỆU 5 9 1 . Thao tác chuỗi một cách hiệu quả ..........................................................................6 1 2 . Mã hóa chuỗi bằng các kiểu mã hóa ký tự ............................................................. 6 2 3 . Chuyển các kiểu giá trị cơ bản thành mảng kiểu byte ............................................6 5 4 . Mã hóa dữ liệu nhị phân thành văn bản ................................................................. 6 7 5 . Sử dụng biểu thức chính quy để kiểm tra dữ liệu nhập ..........................................7 0 6 . Sử dụng biểu thức chính quy đã được biên dịch ....................................................7 2 7 . Tạo ngày và giờ từ chuỗi ........................................................................................ 7 5 8 . Cộng, trừ, so sánh ngày giờ ................................................................................... 7 6 9 . Sắp xếp một mảng hoặc một ArrayList ...................................................................7 8 1 0. Chép một tập hợp vào một mảng ......................................................................... 7 9 1 1. Tạo một tập hợp kiểu mạnh ..................................................................................8 0 1 2. Lưu một đối tượng khả-tuần-tự-hóa vào file .........................................................8 1
C hương 3: MIỀN ỨNG DỤNG, CƠ CHẾ PHẢN CHIẾU, VÀ SIÊU DỮ LIỆU 8 6 1 . Tạo miền ứng dụng .................................................................................................8 8 2 . Chuyển các đối tượng qua lại các miền ứng dụng .................................................9 0 3 . Tránh nạp các assembly không cần thiết vào miền ứng dụng ............................... 9 1 4 . Tạo kiểu không thể vượt qua biên miền ứng dụng .................................................9 2 5 . Nạp assembly vào miền ứng dụng hiện hành ........................................................ 9 2 6 . Thực thi assembly ở miền ứng dụng khác ............................................................. 9 4 7 . Thể hiện hóa một kiểu trong miền ứng dụng khác ................................................. 9 5 8 . Truyền dữ liệu giữa các miền ứng dụng ...............................................................1 01 9 . Giải phóng assembly và miền ứng dụng .............................................................. 1 03 1 0. Truy xuất thông tin Type ..................................................................................... 1 04 1 1. Kiểm tra kiểu của một đối tượng .........................................................................1 06 1 2. Tạo một đối tượng bằng cơ chế phản chiếu .......................................................1 07 1 3. Tạo một đặc tính tùy biến ....................................................................................1 10 14. Sử dụng cơ chế phản chiếu để kiểm tra cá c đặc tính của một phần tử chương trình ...............................................................1 13
C hương 4: TIỂU TRÌNH, TIẾN TRÌNH, VÀ SỰ ĐỒNG BỘ 1 15 1 . Thực thi phương thức với thread-pool ..................................................................1 17 2 . Thực thi phương thức một cách bất đồng bộ ....................................................... 1 21 3 . Thực thi phương thức bằng Timer ........................................................................1 29 4 . Thực thi phương thức bằng cách ra hiệu đối tượng WaitHandle .........................1 32 5 . Thực thi phương thức bằng tiểu trình mới ............................................................1 35 6 . Điều khiển quá trình thực thi của một tiểu trình ....................................................1 37
22
7 . Nhận biết khi nào một tiểu trình kết thúc .............................................................. 1 42 8 . Đồng bộ hóa quá trình thực thi của nhiều tiểu trình ..............................................1 43 9 . Tạo một đối tượng tập hợp có tính chất an-toàn-về-tiểu-trình ..............................1 48 1 0. Khởi chạy một tiến trình mới ...............................................................................1 49 1 1. Kết thúc một tiến trình .........................................................................................1 52 12. Bảo đảm chỉ có thể chạy m ột thể hiện của ứng dụng tại một thời điểm ............................................................ 1 54
C hương 5: XML 1 57 1 . Hiển thị cấu trúc của một tài liệu XML trong TreeView .........................................1 59 2 . Chèn thêm nút vào tài liệu XML ............................................................................1 64 3 . Chèn thêm nút vào tài liệu XML một cách nhanh chóng ...................................... 1 66 4 . Tìm một nút khi biết tên của nó .............................................................................1 69 5 . Thu lấy các nút XML trong một không gian tên XML cụ thể ................................. 1 70 6 . Tìm các phần tử với biểu thức XPath ...................................................................1 72 7 . Đọc và ghi XML mà không phải nạp toàn bộ tài liệu vào bộ nhớ ......................... 1 75 8 . Xác nhận tính hợp lệ của một tài liệu XML dựa trên một Schema ....................... 1 78 9 . Sử dụng XML Serialization với các đối tượng tùy biến ........................................ 1 84 1 0. Tạo XML Schema cho một lớp .NET ..................................................................1 88 1 1. Tạo lớp từ một XML Schema ..............................................................................1 88 1 2. Thực hiện phép biến đổi XSL ..............................................................................1 89
C hương 6: WINDOWS FORM 1 93 1 . Thêm điều kiểm vào form lúc thực thi ...................................................................1 95 2 . Liên kết dữ liệu vào điều kiểm .............................................................................. 1 97 3 . Xử lý tất cả các điều kiểm trên form ..................................................................... 1 99 4 . Theo vết các form khả kiến trong một ứng dụng .................................................. 2 00 5 . Tìm tất cả các form trong ứng dụng MDI ..............................................................2 01 6 . Lưu trữ kích thước và vị trí của form .................................................................... 2 03 7 . Buộc ListBox cuộn xuống ......................................................................................2 05 8 . Chỉ cho phép nhập số vào TextBox ......................................................................2 06 9 . Sử dụng ComboBox có tính năng auto-complete .................................................2 07 1 0. Sắp xếp ListView theo cột bất kỳ ........................................................................2 11 1 1. Liên kết menu ngữ cảnh vào điều kiểm ..............................................................2 13 1 2. Sử dụng một phần menu chính cho menu ngữ cảnh ......................................... 2 14 1 3. Tạo form đa ngôn ngữ ........................................................................................ 2 17 1 4. Tạo form không thể di chuyển được ...................................................................2 19 1 5. Làm cho form không đường viền có thể di chuyển được ...................................2 20 1 6. Tạo một icon động trong khay hệ thống ............................................................. 2 22 1 7. Xác nhận tính hợp lệ của đầu vào cho một điều kiểm ........................................2 23 1 8. Thực hiện thao tác kéo-và-thả ............................................................................2 26 1 9. Sử dụng trợ giúp cảm-ngữ-cảnh .........................................................................2 28 2 0. Áp dụng phong cách Windows XP ......................................................................2 29 2 1. Thay đổi độ đục của form ....................................................................................2 31
C hương 7: ASP.NET VÀ WEB FORM 2 34
23
1 . Chuyển hướng người dùng sang trang khác ........................................................2 36 2 . Duy trì trạng thái giữa các yêu cầu của trang .......................................................2 37 3 . Tạo các biến thành viên có trạng thái cho trang ...................................................2 43 4 . Đáp ứng các sự kiện phía client với JavaScript ................................................... 2 44 5 . Hiển thị cửa sổ pop-up với JavaScript ..................................................................2 47 6 . Thiết lập focus cho điều kiểm ................................................................................2 49 7 . Cho phép người dùng upload file ..........................................................................2 50 8 . Sử dụng IIS authentication ....................................................................................2 53 9 . Sử dụng Forms authentication ..............................................................................2 57 1 0. Thực hiện xác nhận tính hợp lệ có-chọn-lựa ......................................................2 60 1 1. Thêm động điều kiểm vào Web Form .................................................................2 63 1 2. Trả về động một bức hình ...................................................................................2 66 1 3. Nạp điều kiểm người dùng bằng mã lệnh ...........................................................2 70 1 4. Sử dụng page-caching và fragment-caching ......................................................2 75 1 5. Dùng lại dữ liệu với ASP.NET Cache .................................................................2 76 1 6. Kích hoạt việc gỡ rối ứng dụng Web .................................................................. 2 80 1 7. Thay đổi quyền đã cấp cho mã ASP.NET .......................................................... 2 84
C hương 8: ĐỒ HỌA, ĐA PHƯƠNG TIỆN, VÀ IN ẤN 2 87 1 . Tìm tất cả các font đã được cài đặt ...................................................................... 2 89 2 . Thực hiện “hit testing” với shape .......................................................................... 2 91 3 . Tạo form có hình dạng tùy biến ............................................................................ 2 95 4 . Tạo điều kiểm có hình dạng tùy biến ....................................................................2 97 5 . Thêm tính năng cuộn cho một bức hình ...............................................................3 01 6 . Thực hiện chụp màn hình Desktop .......................................................................3 03 7 . Sử dụng “double buffering” để tăng tốc độ vẽ lại ..................................................3 05 8 . Hiển thị hình ở dạng thumbnail .............................................................................3 08 9 . Phát tiếng “beep” của hệ thống .............................................................................3 10 1 0. Chơi file audio .....................................................................................................3 11 1 1. Chơi file video ..................................................................................................... 3 13 1 2. Lấy thông tin về các máy in đã được cài đặt ...................................................... 3 17 1 3. In văn bản đơn giản ............................................................................................ 3 21 1 4. In văn bản có nhiều trang ....................................................................................3 24 1 5. In text dạng wrapping ..........................................................................................3 28 1 6. Hiển thị print-preview .......................................................................................... 3 30 1 7. Quản lý tác vụ in ................................................................................................. 3 33 1 8. Sử dụng Microsoft Agent .................................................................................... 3 38
C hương 9: FILE, THƯ MỤC, VÀ I/O 3 46 1 . Truy xuất các thông tin về file hay thư mục .......................................................... 3 48 2 . Thiết lập các thuộc tính của file và thư mục ......................................................... 3 53 3 . Chép, chuyển, xóa file hay thư mục ..................................................................... 3 54 4 . Tính kích thước của thư mục ................................................................................3 57 5 . Truy xuất thông tin phiên bản của file ...................................................................3 59 6 . Sử dụng TreeView để hiển thị cây thư mục just-in-time ......................................3 60 7 . Đọc và ghi file văn bản ..........................................................................................3 63 8 . Đọc và ghi file nhị phân .........................................................................................3 65
24
9 . Đọc file một cách bất đồng bộ ...............................................................................3 67 1 0. Tìm file phù hợp một biểu thức wildcard .............................................................3 70 1 1. Kiểm tra hai file có trùng nhau hay không ...........................................................3 71 1 2. Thao tác trên đường dẫn file ...............................................................................3 73 1 3. Xác định đường dẫn tương ứng với một file hay thư mục ................................. 3 74 1 4. Làm việc với đường dẫn tương đối .................................................................... 3 75 1 5. Tạo file tạm ......................................................................................................... 3 76 1 6. Lấy dung lượng đĩa còn trống .............................................................................3 77 1 7. Hiển thị các hộp thoại file ....................................................................................3 79 1 8. Sử dụng không gian lưu trữ riêng .......................................................................3 82 1 9. Theo dõi hệ thống file để phát hiện thay đổi .......................................................3 84 2 0. Truy xuất cổng COM ...........................................................................................3 86
C hương 10: CƠ SỞ DỮ LIỆU 3 89 1 . Kết nối cơ sở dữ liệu .............................................................................................3 92 2 . Sử dụng connection-pooling .................................................................................3 94 3 . Thực thi câu lệnh SQL hoặc thủ tục tồn trữ ..........................................................3 97 4 . Sử dụng thông số trong câu lệnh SQL hoặc thủ tục tồn trữ ................................. 4 00 5 . Xử lý kết quả của truy vấn SQL bằng data-reader ............................................... 4 03 6 . Thu lấy tài liệu XML từ truy vấn SQL Server ........................................................ 4 07 7 . Nhận biết tất cả các thể hiện SQL Server 2000 trên mạng .................................. 4 11 8 . Đọc file Excel với ADO.NET ................................................................................. 4 13 9 . Sử dụng Data Form Wizard ..................................................................................4 15 1 0. Sử dụng Crystal Report Wizard ..........................................................................4 24
C hương 11: LẬP TRÌNH MẠNG 4 35 1 . Download file thông qua HTTP .............................................................................4 37 2 . Download và xử lý file bằng stream ......................................................................4 38 3 . Lấy trang HTML từ một website có yêu cầu xác thực .......................................... 4 40 4 . Hiển thị trang web trong ứng dụng dựa-trên-Windows .........................................4 42 5 . Lấy địa chỉ IP của máy tính hiện hành ..................................................................4 46 6 . Phân giải tên miền thành địa chỉ IP ...................................................................... 4 47 7 . “Ping” một địa chỉ IP ..............................................................................................4 48 8 . Giao tiếp bằng TCP ...............................................................................................4 52 9 . Lấy địa chỉ IP của client từ kết nối socket .............................................................4 57 1 0. Thiết lập các tùy chọn socket ..............................................................................4 59 1 1. Tạo một TCP-server hỗ-trợ-đa-tiểu-trình ............................................................4 60 1 2. Sử dụng TCP một cách bất đồng bộ .................................................................. 4 63 1 3. Giao tiếp bằng UDP ............................................................................................ 4 67 1 4. Gửi e-mail thông qua SMTP ............................................................................... 4 70 1 5. Gửi và nhận e-mail với MAPI .............................................................................4 71
C hương 12: DỊCH VỤ WEB XML VÀ REMOTING 4 74 1 . Tránh viết mã cứng cho địa chỉ URL của dịch vụ Web XML ................................ 4 77 2 . Sử dụng kỹ thuật response-caching trong dịch vụ Web XML ...............................4 78 3 . Sử dụng kỹ thuật data-caching trong dịch vụ Web XML .......................................4 79
25
4 . Tạo phương thức web hỗ trợ giao dịch ............................................................... 4 82 5 . Thiết lập thông tin xác thực cho dịch vụ Web XML ...............................................4 85 6 . Gọi bất đồng bộ một phương thức web ................................................................4 86 7 . Tạo lớp khả-truy-xuất-từ-xa .................................................................................. 4 88 8 . Đăng ký tất cả các lớp khả-truy-xuất-từ-xa trong một assembly .......................... 4 94 9 . Quản lý các đối tượng ở xa trong IIS ....................................................................4 96 1 0. Phát sinh sự kiện trên kênh truy xuất từ xa ....................................................... 4 97 1 1. Kiểm soát thời gian sống của một đối tượng ở xa .............................................5 02 1 2. Kiểm soát phiên bản của các đối tượng ở xa .....................................................5 04 13. Tạo phương thức một chiều
vớ i dịch vụ Web XML hay Remoting ..........................................................................5 06
C hương 13: BẢO MẬT 5 09
1. Cho phép mã lệnh có-độ-tin-cậy-một-phần sử dụng assembly tên mạnh của bạn ........................................................................5 12 2 . Vô hiệu bảo mật truy xuất mã lệnh ....................................................................... 5 14 3 . Vô hiệu việc kiểm tra quyền thực thi .....................................................................5 16 4 . Bảo đảm bộ thực thi cấp cho assembly một số quyền nào đó ............................. 5 17 5 . Giới hạn các quyền được cấp cho assembly ....................................................... 5 19 6 . Xem các yêu cầu quyền được tạo bởi một assembly ...........................................5 20 7 . Xác định mã lệnh có quyền nào đó lúc thực thi hay không .................................. 5 22 8. Hạn chế ai đó thừa kế các lớp của bạn
và chép đè các thành viên lớp ................................................................................... 5 23 9 . Kiểm tra chứng cứ của một assembly .................................................................. 5 25 1 0. Xử lý chứng cứ khi nạp một assembly .............................................................. 5 27 1 1. Xử lý bảo mật bộ thực thi bằng chứng cứ của miền ứng dụng .........................5 29 12. Xử lý bảo mật bộ thực thi b ằng chính sách bảo mật của miền ứng dụng ...........................................................5 31 13. Xác định người dùng hiện hành có là thành viên củ a một nhóm Windows nào đó hay không ...............................................................5 35 1 4. Hạn chế những người dùng nào đó thực thi mã lệnh của bạn ...........................5 38 1 5. Giả nhận người dùng Windows .......................................................................... 5 43
C hương 14: MẬT MÃ 5 48 1 . Tạo số ngẫu nhiên ................................................................................................ 5 50 2 . Tính mã băm của password ..................................................................................5 52 3 . Tính mã băm của file .............................................................................................5 54 4 . Kiểm tra mã băm ...................................................................................................5 55 5 . Bảo đảm tính toàn vẹn dữ liệu bằng mã băm có khóa ......................................... 5 58 6 . Bảo vệ file bằng phép mật hóa đối xứng .............................................................. 5 60 7 . Truy lại khóa đối xứng từ password .....................................................................5 66 8 . Gửi một bí mật bằng phép mật hóa bất đối xứng .................................................5 68 9 . Lưu trữ khóa bất đối xứng một cách an toàn .......................................................5 74 1 0. Trao đổi khóa phiên đối xứng một cách an toàn .................................................5 77
C hương 15: KHẢ NĂNG LIÊN TÁC MÃ LỆNH KHÔNG-ĐƯỢC-QUẢN-LÝ 5 84 1 . Gọi một hàm trong một DLL không-được-quản-lý ................................................5 86 2 . Lấy handle của một điều kiểm, cửa sổ, hoặc file ..................................................5 90
26
3 . Gọi một hàm không-được-quản-lý có sử dụng cấu trúc .......................................5 91 4 . Gọi một hàm không-được-quản-lý có sử dụng callback .......................................5 94 5 . Lấy thông tin lỗi không-được-quản-lý ................................................................... 5 95 6 . Sử dụng thành phần COM trong .NET-client ........................................................5 97 7 . Giải phóng nhanh thành phần COM ..................................................................... 6 00 8 . Sử dụng thông số tùy chọn ...................................................................................6 00 9 . Sử dụng điều kiểm ActiveX trong .NET-client .......................................................6 02 1 0. Tạo thành phần .NET dùng cho COM-client .......................................................6 03
C hương 16: CÁC GIAO DIỆN VÀ MẪU THÔNG DỤNG 6 05 1 . Hiện thực kiểu khả-tuần-tự-hóa (serializable type) ...............................................6 07 2 . Hiện thực kiểu khả-sao-chép (cloneable type) ..................................................... 6 14 3 . Hiện thực kiểu khả-so-sánh (comparable type) ....................................................6 17 4 . Hiện thực kiểu khả-liệt-kê (enumerable type) .......................................................6 22 5 . Hiện thực lớp khả-hủy (disposable class) .............................................................6 29 6 . Hiện thực kiểu khả-định-dạng (formattable type) ..................................................6 33 7 . Hiện thực lớp ngoại lệ tùy biến ............................................................................. 6 36 8 . Hiện thực đối số sự kiện tùy biến ......................................................................... 6 40 9 . Hiện thực mẫu Singleton .......................................................................................6 42 1 0. Hiện thực mẫu Observer .....................................................................................6 43
C hương 17: SỰ HÒA HỢP VỚI MÔI TRƯỜNG WINDOWS 6 51 1 . Truy xuất thông tin môi trường ..............................................................................6 53 2 . Lấy giá trị của một biến môi trường ......................................................................6 57 3 . Ghi một sự kiện vào nhật ký sự kiện Windows .....................................................6 58 4 . Truy xuất Windows Registry ................................................................................. 6 59 5 . Tạo một dịch vụ Windows .....................................................................................6 63 6 . Tạo một bộ cài đặt dịch vụ Windows .................................................................... 6 68 7 . Tạo shortcut trên Desktop hay trong Start menu ..................................................6 71
P HỤ LỤC A: GIỚI THIỆU MỘT SỐ CÔNG CỤ .NET ............................................................. 6 76 A .1 Biên dịch các đoạn mã ngắn với Snippet Compiler ............................................6 76 A .2 Xây dựng biểu thức chính quy với Regulator .....................................................6 78 A .3 Sinh mã với CodeSmith .......................................................................................6 79 A .4 Viết kiểm thử đơn vị với NUnit ............................................................................6 81 A .5 Kiểm soát mã lệnh với FxCop ............................................................................ 6 83 A .6 Khảo sát assembly với .NET Reflector ...............................................................6 84 A .7 Lập tài liệu mã lệnh với NDoc ..............................................................................6 86 A .8 Tạo dựng giải pháp với NAnt ...............................................................................6 89 A .9 Chuyển đổi phiên bản ASP.NET với ASP.NET Version Switcher .......................6 91 A .10 Chuyển đổi phiên bản dự án với Visual Studio .NET Project Converter ...........6 92 A .11 Chuyển mã nguồn VB.NET sang C# với VB.NET to C# Converter .................. 6 93 A .12 Chuyển mã nguồn C# sang VB.NET với Convert C# to VB.NET ..................... 6 93 A .13 Xây dựng website quản trị cơ sở dữ liệu với ASP.NET Maker 1.1 ...................6 94 P HỤ LỤC B: THUẬT NGỮ ANH - VIỆT ...................................................................................6 97
27
T ÀI LIỆU THAM KHẢO ............................................................................................................ 7 05
28
29
Chương 1:PHÁT TRIỂN ỨNG DỤNG
1
30
31
Chương 1: Phát triển ứng dụng
hương này trình bày một số kiến thức nền tảng, cần thiết trong quá trình phát triển một Cứng dụng C#. Các mục trong chương sẽ trình bày chi tiết các vấn đề sau đây: ■ Xây dựng các ứng dụng Console và Windows Form (mục 1.1 và 1.2). ■ Tạo và sử dụng đơn thể mã lệnh và thư viện mã lệnh (mục 1.3 và 1.4). ■ Truy xuất đối số dòng lệnh từ bên trong ứng dụng (mục 1.5).
■ Sử dụng các chỉ thị biên dịch để tùy biến việc biên dịch mã nguồn (mục 1.6). ■ Truy xuất các phần tử chương trình (được xây dựng trong ngôn ngữ khác) có tên xung đột với các từ khóa C# (mục 1.7).
■ Tạo và xác minh tên mạnh cho assembly (mục 1.8, 1.9, 1.10, và 1.11). ■ Ký một assembly bằng chữ ký số Microsoft Authenticode (mục 1.12 và 1.13). ■ Quản lý những assembly chia sẻ được lưu trữ trong Global Assembly Cache (mục 1.14).
■ Ngăn người dùng dịch ngược assembly của bạn (mục 1.15).
🖎 Tất cả các công cụ được thảo luận trong chương này đều có trong Microsoft .NET Framework hoặc .NET Framework SDK.
Các công cụ thuộc Framework nằm trong thư mục chính của phiên bản Framework mà bạn đang sử dụng (mặc định là \WINDOWS\Microsoft.NET\ Framework\v1.1.4322 nếu bạn sử dụng .NET Framework version 1.1). Quá trình cài đặt .NET sẽ tự động thêm thư mục này vào đường dẫn môi trường của hệ thống.
Các công cụ được cung cấp cùng với SDK nằm trong thư mục Bin của thư mục cài đặt SDK (mặc định là \Program Files\Microsoft Visual Studio .NET 2003\ SDK\v1.1\Bin). Thư mục này không được thêm vào đường dẫn một cách tự động, vì vậy bạn phải tự thêm nó vào để dễ dàng truy xuất các công cụ này.
Hầu hết các công cụ trên đều hỗ trợ hai dạng đối số dòng lệnh: ngắn và dài. Chương này luôn trình bày dạng dài vì dễ hiểu hơn (nhưng bù lại bạn phải gõ nhiều hơn). Đối với dạng ngắn, bạn hãy tham khảo tài liệu tương ứng trong .NET Framework SDK.
1. Tạo ứng dụng Console
Bạn muốn xây dựng một ứng dụng không cần giao diện người dùng đồ họa (GUI), thay vào đó hiển thị kết quả và đọc dữ liệu nhập từ dòng lệnh. Hiện thực một phương thức tĩnh có tên là Main dưới các dạng sau trong ít nhất một file mã nguồn:
• public static void Main();
• public static void Main(string[] args);
• public static int Main();
32
Chương 1: Phát triển ứng dụng
• public static int Main(string[] args);
Sử dụng đối số /target:exe khi biên dịch assembly của bạn bằng trình biên dịch C# (csc.exe).
Mặc định trình biên dịch C# sẽ xây dựng một ứng dụng Console trừ khi bạn chỉ định loại khác. Vì lý do này, không cần chỉ định /target.exe, nhưng thêm nó vào sẽ rõ ràng hơn, hữu ích khi tạo các kịch bản biên dịch sẽ được sử dụng bởi các ứng dụng khác hoặc sẽ được sử dụng lặp đi lặp lại trong một thời gian. Ví dụ sau minh họa một lớp có tên là ConsoleUtils (được định nghĩa trong file ConsoleUtils.cs):
using System;
public class ConsoleUtils {
// Phương thức hiển thị lời nhắc và đọc đáp ứng từ console. public static string ReadString(string msg) {
Console.Write(msg);
return System.Console.ReadLine();
}
// Phương thức hiển thị thông điệp.
public static void WriteString(string msg) {
System.Console.WriteLine(msg);
}
// Phương thức Main dùng để thử nghiệm lớp ConsoleUtils.
public static void Main() {
// Yêu cầu người dùng nhập tên.
string name = ReadString("Please enter your name : ");
// Hiển thị thông điệp chào mừng.
WriteString("Welcome to Microsoft .NET Framework, " + name); }
}
33
Chương 1: Phát triển ứng dụng
Để xây dựng lớp ConsoleUtils thành một ứng dụng Console có tên là ConsoleUtils.exe, sử dụng lệnh:
csc /target:exe ConsoleUtils.cs
Bạn có thể chạy file thực thi trực tiếp từ dòng lệnh. Khi chạy, phương thức Main của ứng dụng ConsoleUtils.exe yêu cầu bạn nhập tên và sau đó hiển thị thông điệp chào mừng như sau:
Please enter your name : Binh Phuong
Welcome to Microsoft .NET Framework, Binh Phuong
Thực tế, ứng dụng hiếm khi chỉ gồm một file mã nguồn. Ví dụ, lớp HelloWorld dưới đây sử dụng lớp ConsoleUtils để hiển thị thông điệp “Hello, world” lên màn hình (HelloWorld nằm trong file HelloWorld.cs).
public class HelloWorld {
public static void Main() {
ConsoleUtils.WriteString("Hello, world");
}
}
Để xây dựng một ứng dụng Console gồm nhiều file mã nguồn, bạn phải chỉ định tất cả các file mã nguồn này trong đối số dòng lệnh. Ví dụ, lệnh sau đây xây dựng ứng dụng MyFirstApp.exe từ các file mã nguồn HelloWorld.cs và ConsoleUtils.cs:
csc /target:exe /main:HelloWorld /out:MyFirstApp.exe
HelloWorld.cs ConsoleUtils.cs
Đối số /out chỉ định tên của file thực thi sẽ được tạo ra. Nếu không được chỉ định, tên của file thực thi sẽ là tên của file mã nguồn đầu tiên—trong ví dụ trên là HelloWorld.cs. Vì cả hai lớp HelloWorld và ConsoleUtils đều có phương thức Main, trình biên dịch không thể tự động quyết định đâu là điểm nhập cho file thực thi. Bạn phải sử dụng đối số /main để chỉ định tên của lớp chứa điểm nhập cho ứng dụng của bạn.
2. Tạo ứng dụng dựa-trên-Windows
Bạn cần xây dựng một ứng dụng cung cấp giao diện người dùng đồ họa (GUI) dựa-trên-Windows Form.
Hiện thực một phương thức tĩnh Main trong ít nhất một file mã nguồn. Trong Main, tạo một thể hiện của một lớp thừa kế từ lớp System.Windows.Forms.Form (đây là form chính của ứng dụng). Truyền đối tượng này cho phương thức tĩnh Run của lớp System.Windows.Forms.Application. Sử dụng đối số /target:winexe khi biên dịch assembly của bạn bằng trình biên dịch C# (csc.exe).
Việc xây dựng một ứng dụng có giao diện người dùng đồ họa Windows đơn giản hoàn toàn khác xa việc phát triển một ứng dụng dựa-trên-Windows hoàn chỉnh. Tuy nhiên, bất kể viết
34
Chương 1: Phát triển ứng dụng
một ứng dụng đơn giản như Hello World hay viết phiên bản kế tiếp cho Microsoft Word, bạn cũng phải thực hiện những việc sau:
• Tạo một lớp thừa kế từ lớp System.Windows.Forms.Form cho mỗi form cần cho ứng dụng.
• Trong mỗi lớp form, khai báo các thành viên mô tả các điều kiểm trên form, ví dụ Button, Label, ListBox, TextBox. Các thành viên này nên được khai báo là private hoặc ít nhất cũng là protected để các phần tử khác của chương trình không truy xuất trực tiếp chúng được. Nếu muốn cho phép truy xuất các điều kiểm này, hiện thực các thành viên cần thiết trong lớp form để cung cấp việc truy xuất gián tiếp (kiểm soát được) đến các điều kiểm nằm trong.
• Trong lớp form, khai báo các phương thức thụ lý các sự kiện do các điều kiểm trên form sinh ra, chẳng hạn việc nhắp vào Button, việc nhấn phím khi một TextBox đang tích cực. Các phương thức này nên được khai báo là private hoặc protected và tuân theo mẫu sự kiện .NET chuẩn (sẽ được mô tả trong mục 16.10). Trong các phương thức này (hoặc trong các phương thức được gọi bởi các các phương thức này), bạn sẽ định nghĩa các chức năng của ứng dụng.
• Khai báo một phương thức khởi dựng cho lớp form để tạo các điều kiểm trên form và cấu hình trạng thái ban đầu của chúng (kích thước, màu, nội dung…). Phương thức khởi dựng này cũng nên liên kết các phương thức thụ lý sự kiện của lớp với các sự kiện tương ứng của mỗi điều kiểm.
• Khai báo phương thức tĩnh Main—thường là một phương thức của lớp tương ứng với form chính của ứng dụng. Phương thức này là điểm bắt đầu của ứng dụng và có các dạng như đã được đề cập ở mục 1.1. Trong phương thức Main, tạo một thể hiện của form chính và truyền nó cho phương thức tĩnh Application.Run. Phương thức Run hiển thị form chính và khởi chạy một vòng lặp thông điệp chuẩn trong tiểu trình hiện hành, chuyển các tác động từ người dùng (nhấn phím, nhắp chuột…) thành các sự kiện gửi đến ứng dụng.
Lớp WelcomeForm trong ví dụ dưới đây minh họa các kỹ thuật trên. Khi chạy, nó yêu cầu người dùng nhập vào tên rồi hiển thị một MessageBox chào mừng.
using System.Windows.Forms;
public class WelcomeForm : Form {
// Các thành viên private giữ tham chiếu đến các điều kiểm. private Label label1;
private TextBox textBox1;
private Button button1;
// Phương thức khởi dựng (tạo một thể hiện form
35
Chương 1: Phát triển ứng dụng
// và cấu hình các điều kiểm trên form).
public WelcomeForm() {
// Tạo các điều kiểm trên form.
this.label1 = new Label();
this.textBox1 = new TextBox();
this.button1 = new Button();
// Tạm hoãn layout logic của form trong khi
// chúng ta cấu hình và bố trí các điều kiểm.
this.SuspendLayout();
// Cấu hình các Label (hiển thị yêu cầu).
this.label1.Location = new System.Drawing.Point(16, 36); this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(128, 16);
this.label1.TabIndex = 0;
this.label1.Text = "Please enter your name:";
// Cấu hình TextBox (nhận thông tin từ người dùng).
this.textBox1.Location = new System.Drawing.Point(152, 32); this.textBox1.Name = "textBox1";
this.textBox1.TabIndex = 1;
this.textBox1.Text = "";
// Cấu hình Buton (người dùng nhấn vào sau khi nhập tên). this.button1.Location = new System.Drawing.Point(109, 80); this.button1.Name = "button1";
this.button1.TabIndex = 2;
this.button1.Text = "Enter";
this.button1.Click += new System.EventHandler(this.button1_Click);
// Cấu hình WelcomeForm và thêm các điều kiểm.
this.ClientSize = new System.Drawing.Size(292, 126);
this.Controls.Add(this.button1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label1);
this.Name = "form1";
36
Chương 1: Phát triển ứng dụng
this.Text = "Microsoft .NET Framework";
// Phục hồi layout logic của form ngay khi
// tất cả các điều kiểm đã được cấu hình.
this.ResumeLayout(false);
}
// Điểm nhập của ứng dụng (tạo một thể hiện form, chạy vòng lặp // thông điệp chuẩn trong tiểu trình hiện hành - vòng lặp chuyển // các tác động từ người dùng thành các sự kiện đến ứng dụng). public static void Main() {
Application.Run(new WelcomeForm());
}
// Phương thức thụ lý sự kiện
// (được gọi khi người dùng nhắp vào nút Enter). private void button1_Click(object sender, System.EventArgs e) {
// Ghi ra Console.
System.Console.WriteLine("User entered: " + textBox1.Text);
// Hiển thị lời chào trong MessageBox.
MessageBox.Show("Welcome to Microsoft .NET Framework, " + textBox1.Text, "Microsoft .NET Framework"); }
}
37
Chương 1: Phát triển ứng dụng
Hình 1.1 Một ứng dụng Windows Form đơn giản
Để xây dựng lớp WelcomeForm (trong file WelcomeForm.cs) thành một ứng dụng, sử dụng lệnh:
csc /target:winexe WelcomeForm.cs
Đối số /target:winexe báo cho trình biên dịch biết đây là ứng dụng dựa-trên-Windows. Do đó, trình biên dịch sẽ xây dựng file thực thi sao cho không có cửa sổ Console nào được tạo ra khi bạn chạy ứng dụng. Nếu bạn sử dụng /target:exe khi xây dựng một ứng dụng Windows Form thay cho /target:winexe thì ứng dụng vẫn làm việc tốt, nhưng sẽ tạo ra một cửa sổ Console khi chạy. Mặc dù điều này không được ưa chuộng trong một ứng dụng hoàn chỉnh, cửa sổ Console vẫn hữu ích nếu bạn cần ghi ra các thông tin gỡ rối hoặc đăng nhập khi đang phát triển và thử nghiệm một ứng dụng Windows Form. Bạn có thể ghi ra Console bằng phương thức Write và WriteLine của lớp System.Console.
Ứng dụng WelcomeForm.exe trong hình 1.1 hiển thị lời chào người dùng có tên là Binh Phuong. Phiên bản này của ứng dụng được xây dựng bằng đối số /target:exe, nên có cửa sổ Console để hiển thị kết quả của dòng lệnh Console.WriteLine trong phương thức thụ lý sự kiện button1_Click .
🖎 Việc xây dựng một ứng dụng GUI đồ sộ thường tốn nhiều thời gian do phải tạo đối tượng, cấu hình và liên kết nhiều form và điều kiểm. Nhưng may mắn là Microsoft Visual Studio .NET tự động hóa hầu hết các hoạt động này. Nếu không có công cụ như Microsoft Visual Studio .NET thì việc xây dựng một ứng dụng đồ họa đồ sộ sẽ rất lâu, nhàm chán và dễ sinh ra lỗi.
3. Tạo và sử dụng module
Bạn cần thực hiện các công việc sau:
• Tăng hiệu quả thực thi và sử dụng bộ nhớ của ứng dụng bằng cách bảo đảm rằng bộ thực thi nạp các kiểu ít được sử dụng chỉ khi nào cần thiết.
38
Chương 1: Phát triển ứng dụng
• Biên dịch các kiểu được viết trong C# thành một dạng có thể sử dụng lại được trong các ngôn ngữ .NET khác.
• Sử dụng các kiểu được phát triển bằng một ngôn ngữ khác bên trong ứng dụng C# của bạn.
Sử dụng đối số /target:module (của trình biên dịch C#) để xây dựng mã nguồn C# của bạn thành một module. Sử dụng đối số /addmodule để kết hợp các module hiện có vào assembly của bạn.
Module là các khối cơ bản tạo dựng nên các assembly .NET. Module bao gồm một file đơn chứa:
• Mã ngôn ngữ trung gian (Microsoft Intermediate Language—MSIL): Được tạo từ mã nguồn C# trong quá trình biên dịch.
• Siêu dữ liệu (metadata): Mô tả các kiểu nằm trong module.
• Các tài nguyên (resource): Chẳng hạn icon và string table, được sử dụng bởi các kiểu trong module.
Assembly gồm một hay nhiều module và một manifest. Khi chỉ có một module, module và manifest thường được xây dựng thành một file cho thuận tiện. Khi có nhiều module, assembly là một nhóm luận lý của nhiều file được triển khai như một thể thống nhất. Trong trường hợp này, manifest có thể nằm trong một file riêng hay chung với một trong các module.
Việc xây dựng một assembly từ nhiều module gây khó khăn cho việc quản lý và triển khai assembly; nhưng trong một số trường hợp, cách này có nhiều lợi ích, bao gồm:
• Bộ thực thi sẽ chỉ nạp một module khi các kiểu định nghĩa trong module này được yêu cầu. Do đó, khi có một tập các kiểu mà ứng dụng ít khi dùng, bạn có thể đặt chúng trong một module riêng mà bộ thực thi chỉ nạp khi cần. Việc này có các lợi ích sau: ▪ Tăng hiệu quả thực thi, đặc biệt khi ứng dụng được nạp qua mạng.
▪ Giảm thiểu nhu cầu sử dụng bộ nhớ.
• Khả năng sử dụng nhiều ngôn ngữ khác nhau để viết các ứng dụng chạy trên bộ thực thi ngôn ngữ chung (Common Language Runtime—CLR) là một thế mạnh của .NET Framework. Tuy nhiên, trình biên dịch C# không thể biên dịch mã nguồn được viết bằng Microsoft Visual Basic .NET hay COBOL .NET trong assembly của bạn. Bạn phải sử dụng trình biên dịch của ngôn ngữ đó biên dịch mã nguồn thành MSIL theo một cấu trúc mà trình biên dịch C# có thể hiểu được—đó là module. Tương tự, nếu muốn lập trình viên của các ngôn ngữ khác sử dụng các kiểu được phát triển bằng C#, bạn phải xây dựng chúng thành một module.
Để biên dịch file nguồn ConsoleUtils.cs thành một module, sử dụng lệnh: csc /target:module ConsoleUtils.cs
Lệnh này sẽ cho kết quả là một file có tên là ConsoleUtils.netmodule. Phần mở rộng netmodule là phần mở rộng mặc định cho module, và tên file trùng với tên file nguồn C#.
39
Chương 1: Phát triển ứng dụng
Bạn cũng có thể xây dựng một module từ nhiều file nguồn, cho kết quả là một file (module) chứa MSIL và siêu dữ liệu cho các kiểu chứa trong tất cả file nguồn. Ví dụ, lệnh:
csc /target:module ConsoleUtils.cs WindowsUtils.cs
biên dịch hai file nguồn ConsoleUtils.cs và WindowsUtils.cs thành một module có tên là ConsoleUtils.netmodule.
Tên của module được đặt theo tên file nguồn đầu tiên trừ khi bạn chỉ định cụ thể bằng đối số /out. Ví dụ, lệnh:
csc /target:module /out:Utilities.netmodule
ConsoleUtils.cs WindowsUtils.cs
sẽ cho kết quả là file Utilities.netmodule.
Để xây dựng một assembly gồm nhiều module, sử dụng đối số /addmodule. Ví dụ, để xây dựng file thực thi MyFirstApp.exe từ hai module: WindowsUtils.netmodule và ConsoleUtils.netmodule và hai file nguồn: SourceOne.cs và SourceTwo.cs, sử dụng lệnh:
csc /out:MyFirstApp.exe /target:exe
/addmodule:WindowsUtils.netmodule,ConsoleUtils.netmodule
SourceOne.cs SourceTwo.cs
Lệnh này sẽ cho kết quả là một assembly gồm các file sau:
• MyFirstApp.exe: Chứa manifest cũng như MSIL cho các kiểu được khai báo trong hai file nguồn SourceOne.cs và SourceTwo.cs.
• ConsoleUtils.netmodule và WindowsUtils.netmodule: Giờ đây là một phần của assembly nhưng không thay đổi sau khi biên dịch. (Nếu bạn chạy MyFirstApp.exe mà không có các file netmodule, ngoại lệ System.IO.FileNotFoundException sẽ bị ném).
4. Tạo và sử dụng thư viện
Bạn cần xây dựng một tập các chức năng thành một thư viện để nó có thể được tham chiếu và tái sử dụng bởi nhiều ứng dụng.
Để tạo thư viện, sử dụng đối số /target:library khi biên dịch assembly của bạn bằng trình biên dịch C# (csc.exe). Để tham chiếu thư viện, sử dụng đối số /reference và chỉ định tên của thư viện khi biên dịch ứng dụng.
Mục 1.1 minh họa cách xây dựng ứng dụng MyFirstApp.exe từ hai file mã nguồn ConsoleUtils.cs và HelloWorld.cs. File ConsoleUtils.cs chứa lớp ConsoleUtils, cung cấp các phương thức đơn giản hóa sự tương tác với Console. Các chức năng này của lớp ConsoleUtils cũng có thể hữu ích cho các ứng dụng khác. Để sử dụng lại lớp này, thay vì gộp cả mã nguồn của nó vào mỗi ứng dụng, bạn có thể xây dựng nó thành một thư viện, khiến các chức năng này có thể truy xuất được bởi nhiều ứng dụng.
Để xây dựng file ConsoleUtils.cs thành một thư viện, sử dụng lệnh:
csc /target:library ConsoleUtils.cs
Lệnh này sinh ra một file thư viện có tên là ConsoleUtils.dll.
40
Chương 1: Phát triển ứng dụng
Để tạo một thư viện từ nhiều file mã nguồn, liệt kê tên các file này ở cuối dòng lệnh. Bạn có thể sử dụng đối số /out để chỉ định tên thư viện, nếu không, tên thư viện được đặt theo tên của file mã nguồn đầu tiên. Ví dụ, để tạo thư viện MyFirstLibrary.dll từ hai file mã nguồn ConsoleUtils.cs và WindowsUtils.cs, sử dụng lệnh:
csc /out:MyFirstLibrary.dll /target:library
ConsoleUtils.cs WindowsUtils.cs
Trước khi phân phối thư viện cho người khác sử dụng, bạn nên tạo tên mạnh (strong-name) để không ai có thể chỉnh sửa assembly của bạn. Việc đặt tên mạnh cho thư viện còn cho phép người khác cài đặt nó vào Global Assembly Cache, giúp việc tái sử dụng dễ dàng hơn (xem mục 1.9 về cách đặt tên mạnh cho thư viện của bạn và mục 1.14 về cách cài đặt một thư viện có tên mạnh vào Global Assembly Cache). Ngoài ra, bạn có thể đánh dấu thư viện của bạn với chữ ký Authenticode để người dùng biết bạn là tác giả của thư viện (xem mục 1.12 về cách đánh dấu thư viện với Authenticode).
Để biên dịch một assembly có sử dụng các kiểu được khai báo trong các thư viện khác, bạn phải báo cho trình biên dịch biết cần tham chiếu đến thư viện nào bằng đối số /reference. Ví dụ, để biên dịch file HelloWorld.cs (trong mục 1.1) trong trường hợp lớp ConsoleUtils nằm trong thư viện ConsoleUtils.dll, sử dụng lệnh:
csc /reference:ConsoleUtils.dll HelloWorld.cs
Bạn cần chú ý ba điểm sau:
• Nếu tham chiếu nhiều hơn một thư viện, bạn cần phân cách tên các thư viện bằng dấu phẩy hoặc chấm phẩy, nhưng không sử dụng khoảng trắng. Ví dụ:
/reference:ConsoleUtils.dll,WindowsUtils.dll
• Nếu thư viện không nằm cùng thư mục với file mã nguồn, bạn cần sử dụng đối số /lib để chỉ định thư mục chứa thư viện. Ví dụ:
/lib:c:\CommonLibraries,c:\Dev\ThirdPartyLibs
• Nếu thư viện cần tham chiếu là một assembly gồm nhiều file, bạn cần tham chiếu file có chứa manifest (xem thông tin về assembly gồm nhiều file trong mục 1.3).
5. Truy xuất các đối số dòng lệnh
Bạn cần truy xuất các đối số được chỉ định trên dòng lệnh khi thực thi ứng dụng. Sử dụng một dạng của phương thức Main, trong đó nhận đối số dòng lệnh dưới dạng một mảng chuỗi. Ngoài ra, có thể truy xuất đối số dòng lệnh từ bất cứ đâu trong mã nguồn của bạn bằng các thành viên tĩnh của lớp System.Environment.
Khai báo phương thức Main thuộc một trong các dạng sau để truy xuất đối số dòng lệnh dưới dạng một mảng chuỗi:
• public static void Main(string[] args) {}
41
Chương 1: Phát triển ứng dụng
• public static int Main(string[] args) {}
Khi chạy, đối số args sẽ chứa một chuỗi cho mỗi giá trị được nhập trên dòng lệnh và nằm sau tên ứng dụng. Phương thức Main trong ví dụ dưới đây sẽ duyệt qua mỗi đối số dòng lệnh được truyền cho nó và hiển thị chúng ra cửa sổ Console:
public class CmdLineArgExample {
public static void Main(string[] args) {
// Duyệt qua các đối số dòng lệnh.
foreach (string s in args) {
System.Console.WriteLine(s);
}
}
}
Khi thực thi CmdLineArgExample với lệnh:
CmdLineArgExample "one \"two\" three" four 'five six'
ứng dụng sẽ tạo ra kết xuất như sau:
one "two" three
four
'five
six'
Chú ý rằng, khác với C và C++, tên của ứng dụng không nằm trong mảng chứa các đối số. Tất cả ký tự nằm trong dấu nháy kép (“) được xem như một đối số, nhưng dấu nháy đơn (') chỉ được xem như ký tự bình thường. Nếu muốn sử dụng dấu nháy kép trong đối số, đặt ký tự vạch ngược (\) trước nó. Tất cả các khoảng trắng đều bị bỏ qua trừ khi chúng nằm trong dấu nháy kép.
Nếu muốn truy xuất đối số dòng lệnh ở nơi khác (không phải trong phương thức Main), bạn cần xử lý các đối số dòng lệnh trong phương thức Main và lưu trữ chúng để sử dụng sau này. Ngoài ra, bạn có thể sử dụng lớp System.Environment, lớp này cung cấp hai thành viên tĩnh trả về thông tin dòng lệnh: CommandLine và GetCommandLineArgs.
• Thuộc tính CommandLine trả về một chuỗi chứa toàn bộ dòng lệnh. Tùy thuộc vào hệ điều hành ứng dụng đang chạy mà thông tin đường dẫn có đứng trước tên ứng dụng hay không. Các hệ điều hành Windows NT 4.0, Windows 2000, và Windows XP không chứa thông tin đường dẫn, trong khi Windows 98 và Windows ME thì lại chứa.
• Phương thức GetCommandLineArgs trả về một mảng chuỗi chứa các đối số dòng lệnh. Mảng này có thể được xử lý giống như mảng được truyền cho phương thức Main, tuy nhiên phần tử đầu tiên của mảng này là tên ứng dụng.
42
Chương 1: Phát triển ứng dụng
6. Chọn biên dịch một khối mã vào file thực thi
Bạn cần chọn một số phần mã nguồn sẽ được biên dịch trong file thực thi. Sử dụng các chỉ thị tiền xử lý #if, #elif, #else, và #endif để chỉ định khối mã nào sẽ được biên dịch trong file thực thi. Sử dụng đặc tính System.Diagnostics. ConditionalAttribute để chỉ định các phương thức mà sẽ chỉ được gọi tùy theo điều kiện. Điều khiển việc chọn các khối mã bằng các chỉ thị #define và #undef trong mã nguồn, hoặc sử dụng đối số /define khi chạy trình biên dịch C#.
Nếu muốn ứng dụng của bạn hoạt động khác nhau tùy vào các yếu tố như nền hoặc môi trường mà ứng dụng chạy, bạn có thể kiểm tra điều kiện khi chạy bên trong mã nguồn và kích hoạt các hoạt động cần thiết. Tuy nhiên, cách này làm mã nguồn lớn lên và ảnh hưởng đến hiệu năng. Một cách tiếp cận khác là xây dựng nhiều phiên bản của ứng dụng để hỗ trợ các nền và môi trường khác nhau. Mặc dù cách này khắc phục được các vấn đề về độ lớn của mã nguồn và việc giảm hiệu năng, nhưng nó không phải là giải pháp tốt khi phải giữ mã nguồn khác nhau cho mỗi phiên bản. Vì vậy, C# cung cấp các tính năng cho phép bạn xây dựng các phiên bản tùy biến của ứng dụng chỉ từ một mã nguồn.
Các chỉ thị tiền xử lý cho phép bạn chỉ định các khối mã sẽ được biên dịch vào file thực thi chỉ nếu các ký hiệu cụ thể được định nghĩa lúc biên dịch. Các ký hiệu hoạt động như các “công tắc” on/off, chúng không có giá trị mà chỉ là “đã được định nghĩa” hay “chưa được định nghĩa”. Để định nghĩa một ký hiệu, bạn có thể sử dụng chỉ thị #define trong mã nguồn hoặc sử dụng đối số trình biên dịch /define. Ký hiệu được định nghĩa bằng #define có tác dụng đến cuối file định nghĩa nó. Ký hiệu được định nghĩa bằng /define có tác dụng trong tất cả các file đang được biên dịch. Để bỏ một ký hiệu đã định nghĩa bằng /define, C# cung cấp chỉ thị #undef, hữu ích khi bạn muốn bảo đảm một ký hiệu không được định nghĩa trong các file nguồn cụ thể. Các chỉ thị #define và #undef phải nằm ngay đầu file mã nguồn, trên cả các chỉ thị using. Các ký hiệu có phân biệt chữ hoa-thường.
Trong ví dụ sau, biến platformName được gán giá trị tùy vào các ký hiệu winXP, win2000, winNT, hoặc win98 có được định nghĩa hay không. Phần đầu của mã nguồn định nghĩa các ký hiệu win2000 và released (không được sử dụng trong ví dụ này), và bỏ ký hiệu win98 trong trường hợp nó được định nghĩa trên dòng lệnh trình biên dịch.
#define win2000
#define release
#undef win98
using System;
public class ConditionalExample {
public static void Main() {
43
Chương 1: Phát triển ứng dụng
// Khai báo chuỗi chứa tên của nền.
string platformName;
#if winXP // Biên dịch cho Windows XP
platformName = "Microsoft Windows XP";
#elif win2000 // Biên dịch cho Windows 2000
platformName = "Microsoft Windows 2000";
#elif winNT // Biên dịch cho Windows NT
platformName = "Microsoft Windows NT";
#elif win98 // Biên dịch cho Windows 98
platformName = "Microsoft Windows 98";
#else // Nền không được nhận biết
platformName = "Unknown";
#endif
Console.WriteLine(platformName);
}
}
Để xây dựng lớp ConditionalExample (chứa trong file ConditionalExample.cs) và định nghĩa các ký hiệu winXP và DEBUG (không được sử dụng trong ví dụ này), sử dụng lệnh:
csc /define:winXP;DEBUG ConditionalExample.cs
Cấu trúc #if .. #endif đánh giá các mệnh đề #if và #elif chỉ đến khi tìm thấy một mệnh đề đúng, nghĩa là nếu có nhiều ký hiệu được định nghĩa (chẳng hạn, winXP và win2000), thứ tự các mệnh đề là quan trọng. Trình biên dịch chỉ biên dịch đoạn mã nằm trong mệnh đề đúng. Nếu không có mệnh đề nào đúng, trình biên dịch sẽ biên dịch đoạn mã nằm trong mệnh đề #else.
Bạn cũng có thể sử dụng các toán tử luận lý để biên dịch có điều kiện dựa trên nhiều ký hiệu. Bảng 1.1 tóm tắt các toán tử được hỗ trợ.
Bảng 1.1 Các toán tử luận lý được hỗ trợ bởi chỉ thị #if .. #endif
Toán tử Ví dụ Mô tả
== #if winXP == true Bằng. Đúng nếu winXP được định nghĩa. Tương đương với #if winXP.
!= #if winXP != true Không bằng. Đúng nếu winXP không được định nghĩa. Tương đương với #if !winXP.
&& #if winXP && release Phép AND luận lý. Đúng nếu winXP và release được định nghĩa.
44
Chương 1: Phát triển ứng dụng
|| #if winXP || release Phép OR luận lý. Đúng nếu winXP hoặc release được định nghĩa.
() #if (winXP || win2000) && release
Dấu ngoặc đơn cho phép nhóm các biểu thức. Đúng nếu winXP hoặc win2000 được định nghĩa, đồng thời release cũng được định nghĩa.
🖎 Bạn không nên lạm dụng các chỉ thị biên dịch có điều kiện và không nên viết các biểu thức điều kiện quá phức tạp; nếu không, mã nguồn của bạn sẽ trở nên dễ nhầm lẫn và khó quản lý—đặc biệt khi dự án của bạn càng lớn.
Một cách khác không linh hoạt nhưng hay hơn chỉ thị tiền xử lý #if là sử dụng đặc tính System.Diagnostics.ConditionalAttribute. Nếu bạn áp dụng ConditionalAttribute cho một phương thức, trình biên dịch sẽ bỏ qua mọi lời gọi phương thức đó nếu ký hiệu do ConditionalAttribute chỉ định không được định nghĩa tại điểm gọi. Trong đoạn mã sau, ConditionalAttribute xác định rằng phương thức DumpState chỉ được biên dịch vào file thực thi nếu ký hiệu DEBUG được định nghĩa khi biên dịch.
[System.Diagnostics.Conditional("DEBUG")]
public static void DumpState() {//...}
Việc sử dụng ConditionalAttribute giúp đặt các điều kiện gọi một phương thức tại nơi khai báo nó mà không cần các chỉ thị #if. Tuy nhiên, bởi vì trình biên dịch thật sự bỏ qua các lời gọi phương thức, nên mã của bạn không thể phụ thuộc vào các giá trị trả về từ phương thức. Điều này có nghĩa là bạn có thể áp dụng ConditionalAttribute chỉ với các phương thức trả về void.
Bạn có thể áp dụng nhiều thể hiện ConditionalAttribute cho một phương thức, tương đương với phép OR luận lý. Các lời gọi phương thức DumpState dưới đây chỉ được biên dịch nếu DEBUG hoặc TEST được định nghĩa.
[System.Diagnostics.Conditional("DEBUG")]
[System.Diagnostics.Conditional("TEST")]
public static void DumpState() {//...}
Việc thực hiện phép AND luận lý cần sử dụng phương thức điều kiện trung gian, khiến cho mã trở nên quá phức tạp, khó hiểu và khó bảo trì. Ví dụ dưới đây cần phương thức trung gian DumpState2 để định nghĩa cả hai ký hiệu DEBUG và TEST.
[System.Diagnostics.Conditional("DEBUG")]
public static void DumpState() {
DumpState2();
}
[System.Diagnostics.Conditional("TEST")]
public static void DumpState2() {//...}
45
Chương 1: Phát triển ứng dụng
🖎 Các lớp Debug và Trace thuộc không gian tên System.Diagnostics sử dụng đặc tính ConditionalAttribute trong nhiều phương thức của chúng. Các phương thức của lớp Debug tùy thuộc vào việc định nghĩa ký hiệu DEBUG, còn các phương thức của lớp Trace tùy thuộc vào việc định nghĩa ký hiệu TRACE.
7. Truy xuất một phần tử chương trình có tên trùng với một từ khóa
Bạn cần truy xuất một thành viên của một kiểu, nhưng tên kiểu hoặc tên thành viên này trùng với một từ khóa của C#.
Đặt ký hiệu @ vào trước các tên trùng với từ khóa.
.NET Framework cho phép bạn sử dụng các thành phần phần mềm (software component) được phát triển bằng các ngôn ngữ .NET khác bên trong ứng dụng C# của bạn. Mỗi ngôn ngữ đều có một tập từ khóa (hoặc từ dành riêng) cho nó và có các hạn chế khác nhau đối với các tên mà lập trình viên có thể gán cho các phần tử chương trình như kiểu, thành viên, và biến. Do đó, có khả năng một thành phần được phát triển trong một ngôn ngữ khác tình cờ sử dụng một từ khóa của C# để đặt tên cho một phần tử nào đó. Ký hiệu @ cho phép bạn sử dụng một từ khóa của C# làm định danh và khắc phục việc đụng độ tên. Đoạn mã sau tạo một đối tượng kiểu operator và thiết lập thuộc tính volatile của nó là true (cả operator và volatile đều là từ khóa của C#):
// Tạo đối tượng operator.
@operator Operator1 = new @operator();
// Thiết lập thuộc tính volatile của operator.
Operator1.@volatile = true;
8. Tạo và quản lý cặp khóa tên mạnh
Bạn cần tạo một cặp khóa công khai và khóa riêng (public key và private key) để gán tên mạnh cho assembly.
Sử dụng công cụ Strong Name (sn.exe) để tạo cặp khóa và lưu trữ chúng trong một file hoặc trong một kho chứa khóa Cryptographic Service Provider.
🖎 Cryptographic Service Provider (CSP) là một phần tử của Win32 CryptoAPI, cung cấp các dịch vụ như mật hóa, giải mật hóa và tạo chữ ký số. CSP còn cung cấp các tiện ích cho kho chứa khóa (key container) như sử dụng giải thuật mật hóa mạnh và các biện pháp bảo mật của hệ điều hành để bảo vệ nội dung của kho chứa khóa. CSP và CryptoAPI không được đề cập đầy đủ trong quyển sách này, bạn hãy tham khảo thêm trong tài liệu SDK.
Để tạo một cặp khóa mới và lưu trữ chúng trong file có tên là MyKey.snk, thực thi lệnh sn –k MyKey.snk (phần mở rộng .snk thường được sử dụng cho các file chứa khóa tên mạnh). File
46
Chương 1: Phát triển ứng dụng
được tạo ra chứa cả khóa công khai và khóa riêng. Bạn có thể sử dụng lệnh sn –tp MyKey.snk để xem khóa công khai, lệnh này cho kết xuất như sau:
Microsoft (R) .NET Framework Strong Name Utility Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Public key is
07020000002400005253413200040000010001008bb302ef9180bf717ace00d570dd649821f24ed578 fdccf1bc4017308659c126570204bc4010fdd1907577df1c2292349d9c2de33e49bd991a0a5bc9b69e 5fd95bafad658a57b8236c5bd9a43be022a20a52c2bd8145448332d5f85e9ca641c26a4036165f2f35 3942b643b10db46c82d6d77bbc210d5a7c5aca84d7acb52cc1654759c62aa34988...
Public key token is f7241505b81b5ddc
Token của khóa công khai là 8 byte cuối của mã băm được tính ra từ khóa công khai. Vì khóa công khai quá dài nên .NET sử dụng token cho mục đích hiển thị, và là một cơ chế ngắn gọn cho các assembly khác tham chiếu khóa công khai (chương 14 sẽ thảo luận tổng quát về mã băm).
Như tên gọi của nó, khóa công khai (hoặc token của khóa công khai) không cần được giữ bí mật. Khi bạn tạo tên mạnh cho assembly (được thảo luận trong mục 1.9), trình biên dịch sẽ sử dụng khóa riêng để tạo một chữ ký số (một mã băm đã-được-mật-hóa) của assembly manifest. Trình biên dịch nhúng chữ ký số và khóa công khai vào assembly để người dùng có thể kiểm tra chữ ký số.
Việc giữ bí mật khóa riêng là cần thiết vì người truy xuất vào khóa riêng của bạn có thể thay đổi assembly và tạo một tên mạnh mới—khiến cho khách hàng của bạn không biết mã nguồn đã bị sửa đổi. Không có cơ chế nào để loại bỏ các khóa tên mạnh đã bị tổn hại. Nếu khóa riêng bị tổn hại, bạn phải tạo khóa mới và phân phối phiên bản mới của assembly (được đặt tên mạnh bằng các khóa mới). Bạn cũng cần thông báo cho khách hàng biết là khóa đã bị tổn hại và họ nên sử dụng phiên bản nào—trong trường hợp này, bạn bị mất cả tiền bạc và uy tín. Có nhiều cách để bảo vệ khóa riêng của bạn; sử dụng cách nào là tùy vào các yếu tố như:
• Cấu trúc và tầm cỡ của tổ chức.
• Quá trình phát triển và phân phối ứng dụng.
• Phần mềm và phần cứng hiện có.
• Yêu cầu của khách hàng.
Thông thường, một nhóm nhỏ các cá nhân đáng tin cậy (được gọi là signing authority) sẽ có trách nhiệm đảm bảo an toàn cho các khóa tên mạnh của công ty và ký mọi assembly trước khi chúng được phân phối. Khả năng trì hoãn ký assembly (sẽ được thảo luận ở mục 1.11) tạo điều kiện thuận lợi cho việc ứng dụng mô hình này và tránh được việc bạn phải phân phối khóa riêng cho mọi thành viên của nhóm phát triển.
47
Chương 1: Phát triển ứng dụng
Công cụ Strong Name còn cung cấp tính năng sử dụng kho chứa khóa CSP để đơn giản hóa việc bảo mật các khóa tên mạnh. Một khi đã tạo một cặp khóa trong một file, bạn có thể cài đặt các khóa này vào kho chứa khóa CSP và xóa file đi. Ví dụ, để lưu trữ cặp khóa nằm trong file MyKey.snk vào một kho chứa khóa CSP có tên là StrongNameKeys, sử dụng lệnh sn -i MyKeys.snk StrongNameKeys (mục 1.9 sẽ giải thích cách sử dụng các khóa tên mạnh được lưu trữ trong một kho chứa khóa CSP).
Một khía cạnh quan trọng của kho chứa khóa CSP là có các kho chứa khóa dựa-theo người dùng và có các kho chứa khóa dựa-theo-máy. Cơ chế bảo mật của Windows bảo đảm người dùng chỉ truy xuất được kho chứa khóa dựa-theo-người-dùng của chính họ. Tuy nhiên, bất kỳ người dùng nào của máy đều có thể truy xuất kho chứa khóa dựa-theo-máy.
Theo mặc định, công cụ Strong Name sử dụng kho chứa khóa dựa-theo-máy, nghĩa là mọi người đăng nhập vào máy và biết tên của kho chứa khóa đều có thể ký một assembly bằng các khóa tên mạnh của bạn. Để công cụ Strong Name sử dụng kho chứa khóa dựa-theo-người dùng, sử dụng lệnh sn –m n; khi muốn trở lại kho chứa khóa dựa-theo-máy, sử dụng lệnh sn – m y. Lệnh sn –m sẽ cho biết công cụ Strong Name hiện được cấu hình là sử dụng kho chứa khóa dựa-theo-người-dùng hay dựa-theo-máy.
Để xóa các khóa tên mạnh từ kho StrongNameKeys (cũng như xóa cả kho này), sử dụng lệnh sn –d StrongNameKeys.
9. Tạo tên mạnh cho assembly
Bạn cần tạo tên mạnh cho một assembly để nó:
• Có một định danh duy nhất, cho phép gán các quyền cụ thể vào assembly khi cấu hình Code Access Security Policy (chính sách bảo mật cho việc truy xuất mã lệnh).
• Không thể bị sửa đổi và sau đó mạo nhận là nguyên bản.
• Hỗ trợ việc đánh số phiên bản và các chính sách về phiên bản (version policy).
• Có thể được chia sẻ trong nhiều ứng dụng, và được cài đặt trong Global Assembly Cache (GAC).
Sử dụng các đặc tính (attribute) mức-assembly để chỉ định nơi chứa cặp khóa tên mạnh, và có thể chỉ định thêm số phiên bản và thông tin bản địa cho assembly. Trình biên dịch sẽ tạo tên mạnh cho assembly trong quá trình xây dựng.
Để tạo tên mạnh cho một assembly bằng trình biên dịch C#, bạn cần các yếu tố sau:
• Một cặp khóa tên mạnh nằm trong một file hoặc một kho chứa khóa CSP (xem mục 1.8 về cách tạo cặp khóa tên mạnh).
• Sử dụng các đặc tính mức-assembly để chỉ định nơi trình biên dịch có thể tìm thấy cặp khóa tên mạnh đó.
▪ Nếu cặp khóa nằm trong một file, áp dụng đặc tính System.Reflection. AssemblyKeyFileAttribute cho assembly và chỉ định tên file chứa các khóa.
48
Chương 1: Phát triển ứng dụng
▪ Nếu cặp khóa nằm trong một kho chứa khóa CSP, áp dụng đặc tính System.Reflection.AssemblyKeyNameAttribute cho assembly và chỉ định tên của kho chứa khóa.
Ngoài ra, bạn có thể tùy chọn:
• Áp dụng đặc tính System.Reflection.AssemblyCultureAttribute cho assembly để chỉ định thông tin bản địa mà assembly hỗ trợ (Bạn không thể chỉ định bản địa cho các assembly thực thi vì assembly thực thi chỉ hỗ trợ bản địa trung lập).
• Áp dụng đặc tính System.Reflection.AssemblyVersionAttribute cho assembly để chỉ định phiên bản của assembly.
Đoạn mã dưới đây (trong file HelloWorld.cs) minh họa cách sử dụng các đặc tính (phần in đậm) để chỉ định khóa, bản địa, và phiên bản cho assembly:
using System;
using System.Reflection;
[assembly:AssemblyKeyName("MyKeys")]
[assembly:AssemblyCulture("")]
[assembly:AssemblyVersion("1.0.0.0")]
public class HelloWorld {
public static void Main() {
Console.WriteLine("Hello, world");
}
}
Để tạo một assembly tên mạnh từ đoạn mã trên, tạo các khóa tên mạnh và lưu trữ chúng trong file MyKeyFile bằng lệnh sn -k MyKeyFile.snk. Sau đó, sử dụng lệnh sn -i MyKeyFile.snk MyKeys để cài đặt các khóa vào một kho chứa khóa CSP có tên là MyKeys. Cuối cùng, sử dụng lệnh csc HelloWorld.cs để biên dịch file HelloWorld.cs thành một assembly tên mạnh.
🖎 Bạn cũng có thể sử dụng công cụ Assembly Linker (al.exe) để tạo assembly tên mạnh, cách này cho phép chỉ định các thông tin tên mạnh trên dòng lệnh thay vì sử dụng các đặc tính trong mã nguồn. Cách này hữu ích khi bạn không muốn nhúng các đặc tính tên mạnh vào file nguồn và khi bạn sử dụng kịch bản để xây dựng những cây mã nguồn đồ sộ. Xem thêm thông tin về Assembly Linker trong tài liệu .NET Framework SDK.
49
Chương 1: Phát triển ứng dụng
10. Xác minh một assembly tên mạnh không bị sửa đổi
Bạn cần xác minh rằng một assembly tên mạnh chưa hề bị sửa đổi sau khi nó được biên dịch.
Sử dụng công cụ Strong Name (sn.exe) để xác minh tên mạnh của assembly.
Mỗi khi nạp một assembly tên mạnh, bộ thực thi .NET lấy mã băm đã-được-mật-hóa (được nhúng trong assembly) và giải mật hóa với khóa công khai (cũng được nhúng trong assembly). Sau đó, bộ thực thi tính mã băm của assembly manifest và so sánh nó với mã băm vừa-được-giải-mật-hóa. Quá trình xác minh này sẽ nhận biết assembly có bị thay đổi sau khi biên dịch hay không.
Nếu một quá trình xác minh tên mạnh thất bại với một assembly thực thi, bộ thực thi sẽ hiển thị hộp thoại như hình 1.2. Nếu cố nạp một assembly đã thất bại trong quá trình xác minh, bộ thực thi sẽ ném ngoại lệ System.IO.FileLoadException với thông điệp “Strong name validation failed”.
Hình 1.2 Lỗi khi cố thực thi một assembly tên mạnh đã bị sửa đổi
Ngoài việc tạo và quản lý các khóa tên mạnh (đã được thảo luận trong mục 1.8), công cụ Strong Name còn cho phép xác minh các assembly tên mạnh. Để xác minh assembly tên mạnh HelloWorld.exe không bị sửa đổi, sử dụng lệnh sn -vf HelloWorld.exe. Đối số -v yêu cầu công cụ Strong Name xác minh tên mạnh của một assembly xác định, đối số -f buộc thực hiện việc xác minh tên mạnh ngay cả nó đã bị vô hiệu trước đó cho một assembly nào đó. (Bạn có thể sử dụng đối số -Vr để vô hiệu việc xác minh tên mạnh đối với một assembly, ví dụ sn -Vr HelloWorld.exe; mục 1.11 sẽ trình bày lý do tại sao cần vô hiệu việc xác minh tên mạnh).
Nếu assembly này được xác minh là không đổi, bạn sẽ thấy kết xuất như sau: Microsoft (R) .NET Framework Strong Name Utility Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Assembly 'HelloWorld.exe' is valid
Tuy nhiên, nếu assembly này đã bị sửa đổi, bạn sẽ thấy kết xuất như sau: Microsoft (R) .NET Framework Strong Name Utility Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Failed to verify assembly -- Unable to format error message 8013141A
50
Chương 1: Phát triển ứng dụng
11. Hoãn việc ký assembly
Bạn cần tạo một assembly tên mạnh, nhưng không muốn mọi thành viên trong nhóm phát triển truy xuất khóa riêng của cặp khóa tên mạnh.
Trích xuất và phân phối khóa công khai của cặp khóa tên mạnh. Làm theo hướng dẫn trong mục 1.9 để tạo tên mạnh cho assembly. Áp dụng đặc tính System.Reflection.AssemblyDelaySignAttribute cho assembly để chỉ định nó là assembly sẽ được ký sau. Sử dụng đối số -Vr của công cụ Strong Name (sn.exe) để vô hiệu việc xác minh tên mạnh cho assembly này.
Các assembly tham chiếu đến assembly tên mạnh sẽ chứa token của assembly được tham chiếu, nghĩa là assembly được tham chiếu phải được tạo tên mạnh trước khi được tham chiếu. Trong một môi trường phát triển mà assembly thường xuyên được xây dựng lại, mỗi người phát triển và kiểm thử đều cần có quyền truy xuất cặp khóa tên mạnh của bạn—đây là một nguy cơ bảo mật chủ yếu.
Thay vì phân phối khóa riêng cho mọi thành viên của nhóm phát triển, .NET Framework cung cấp cơ chế hoãn việc ký một assembly (được gọi là delay signing), theo đó bạn có thể tạo tên mạnh không hoàn chỉnh cho assembly (tạm gọi là tên mạnh bán phần). Tên mạnh bán phần này chỉ chứa khóa công khai và token của khóa công khai (cần thiết để tham chiếu assembly), nhưng chừa chỗ cho chữ ký sẽ được tạo ra từ khóa riêng sau này.
Khi quá trình phát triển hoàn tất, signing authority (người chịu trách nhiệm về việc bảo mật và việc sử dụng cặp khóa tên mạnh) sẽ ký lại assembly đã bị hoãn trước đó để hoàn thành tên mạnh cho nó. Chữ ký được tính toán dựa trên khóa riêng và được nhúng vào assembly, và giờ đây bạn đã có thể phân phối assembly.
Khi hoãn việc ký một assembly, bạn chỉ cần truy xuất khóa công khai của cặp khóa tên mạnh. Không có nguy cơ bảo mật nào từ việc phân phối khóa công khai, và signing authority phải phân phối khóa công khai đến mọi thành viên của nhóm phát triển. Để trích xuất khóa công khai từ file MyKeys.snk và ghi nó vào file MyPublicKey.snk, sử dụng lệnh sn -p MyKeys.snk MyPublicKey.snk. Nếu bạn lưu trữ cặp khóa tên mạnh trong một kho chứa khóa CSP có tên là MyKeys, sử dụng lệnh sn -pc MyKeys MyPublicKey.snk để trích xuất khóa công khai ra rồi lưu trữ nó vào file MyPublicKey.snk.
Ví dụ dưới đây áp dụng các đặc tính đã được thảo luận trong mục 1.9 để khai báo phiên bản, bản địa, và nơi chứa khóa công khai. Đồng thời áp dụng đặc tính AssemblyDelaySign(true) cho assembly để báo cho trình biên dịch biết bạn muốn trì hoãn việc ký assembly.
using System;
using System.Reflection;
[assembly:AssemblyKeyFile("MyPublicKey.snk")]
[assembly:AssemblyCulture("")]
[assembly:AssemblyVersion("1.0.0.0")]
51
Chương 1: Phát triển ứng dụng
[assembly:AssemblyDelaySign(true)]
public class HelloWorld {
public static void Main() {
Console.WriteLine("Hello, world");
}
}
Khi cố nạp một assembly bị hoãn ký, bộ thực thi sẽ nhận ra assembly này có tên mạnh và cố xác minh assembly (như được thảo luận trong mục 1.10). Nhưng vì không có chữ ký số nên bạn phải vô hiệu chức năng xác minh này bằng lệnh sn -Vr HelloWorld.exe. Khi quá trình phát triển hoàn tất, bạn cần ký lại assembly để hoàn thành tên mạnh cho assembly. Công cụ Strong Name cho phép thực hiện điều này mà không cần thay đổi mã nguồn hoặc biên dịch lại assembly, tuy nhiên, bạn phải có quyền truy xuất khóa riêng của cặp khóa tên mạnh. Để ký lại assembly có tên là HelloWorld.exe với cặp khóa nằm trong file MyKeys.snk, sử dụng lệnh sn -R HelloWorld.exe MyKeys.snk. Nếu cặp khóa được lưu trữ trong một kho chứa khóa CSP có tên là MyKeys, sử dụng lệnh sn -Rc HelloWorld.exe MyKeys.
Sau khi đã ký lại assembly, bạn phải mở chức năng xác minh tên mạnh cho assembly bằng đối số -Vu của công cụ Strong Name, ví dụ sn -Vu HelloWorld.exe. Để kích hoạt lại việc xác minh tên mạnh cho tất cả các assembly đã bị bạn vô hiệu trước đó, sử dụng lệnh sn –Vx. Sử dụng lệnh sn -Vl để xem danh sách các assembly đã bị vô hiệu chức năng này.
🖎 Khi sử dụng assembly ký sau, bạn nên so sánh các lần xây dựng khác nhau của assembly để bảo đảm chúng chỉ khác nhau ở chữ ký. Điều này chỉ có thể thực hiện được nếu assembly đã được ký lại bằng đối số -R của công cụ Strong Name. Sử dụng lệnh sn -D assembly1 assembly2 để so sánh hai assembly.
Hình 1.3 Tạm hoãn việc ký assembly
52
Chương 1: Phát triển ứng dụng
Hình 1.4 Ký lại assembly
12. Ký assembly với chữ ký số Authenticode
Bạn cần ký một assembly bằng Authenticode để người dùng biết bạn chính là người phát hành (publisher) và assembly không bị sửa đổi sau khi ký. Sử dụng công cụ File Signing (signcode.exe) để ký assembly với Software Publisher Certificate (SPC) của bạn.
Tên mạnh cung cấp một định danh duy nhất cũng như chứng minh tính toàn vẹn của một assembly, nhưng nó không xác minh ai là người phát hành assembly này. Do đó, .NET Framework cung cấp kỹ thuật Authenticode để ký assembly. Điều này cho phép người dùng biết bạn là người phát hành và xác nhận tính toàn vẹn của assembly. Chữ ký Authenticode còn được sử dụng làm chứng cứ (evidence) cho assembly khi cấu hình chính sách bảo mật truy xuất mã lệnh (Code Access Security Policy—xem mục 13.9 và 13.10).
Để ký một assembly với chữ ký Authenticode, bạn cần một SPC do một Certificate Authority ( CA) cấp. CA được trao quyền để cấp SPC (cùng với nhiều kiểu chứng chỉ khác) cho các cá nhân hoặc công ty sử dụng. Trước khi cấp một chứng chỉ, CA có trách nhiệm xác nhận những người yêu cầu và bảo đảm họ ký kết không sử dụng sai các chứng chỉ do CA cấp.
Để có được một SPC, bạn nên xem Microsoft Root Certificate Program Members tại [http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/rootcertprog.asp]. Ở đây, bạn có thể tìm thấy danh sách các CA, nhiều CA trong số đó có thể cấp cho bạn một SPC. Với mục đích thử nghiệm, bạn có thể tạo một SPC thử nghiệm theo quá trình sẽ được mô tả trong mục 1.13. Tuy nhiên, bạn không thể phân phối phần mềm được ký với chứng chỉ thử nghiệm này. Vì một SPC thử nghiệm không do một CA đáng tin cậy cấp, nên hầu hết
người dùng sẽ không tin tưởng assembly được ký bằng SPC thử nghiệm này. Khi đã có một SPC, sử dụng công cụ File Signing để ký assembly của bạn. Công cụ File Signing sử dụng khóa riêng của SPC để tạo một chữ ký số và nhúng chữ ký này cùng phần
53
Chương 1: Phát triển ứng dụng
công khai của SPC vào assembly (bao gồm khóa công khai). Khi xác minh một assembly, người dùng sử dụng khóa công khai để giải mật hóa mã băm đã-được-mật-hóa, tính toán lại mã băm của assembly, và so sánh hai mã băm này để bảo đảm chúng là như nhau. Khi hai mã băm này trùng nhau, người dùng có thể chắc chắn rằng bạn đã ký assembly, và nó không bị thay đổi từ khi bạn ký.
Ví dụ, để ký một assembly có tên là MyAssembly.exe với một SPC nằm trong file MyCert.spc và khóa riêng nằm trong file MyPrivateKey.pvk, sử dụng lệnh:
signcode -spc MyCert.spc -v MyPrivateKey.pvk MyAssembly.exe
Trong ví dụ này, công cụ File Signing sẽ hiển thị một hộp thoại như hình 1.5, yêu cầu bạn nhập mật khẩu (được sử dụng để bảo vệ khóa riêng trong file MyPrivateKey.pvk).
Hình 1.5 Công cụ File Signing yêu cầu nhập mật khầu khi truy xuất file chứa khóa riêng
Bạn cũng có thể truy xuất khóa và chứng chỉ trong các kho chứa. Bảng 1.2 liệt kê các đối số thường dùng nhất của công cụ File Signing. Bạn hãy tham khảo tài liệu .NET Framework SDK để xem tất cả các đối số.
Bảng 1.2 Các đối số thường dùng của công cụ File Signing
Đối số Mô tả
-k Chỉ định tên của kho chứa khóa riêng SPC
-s Chỉ định tên của kho chứa SPC
-spc Chỉ định tên file chứa SPC
-v Chỉ định tên file chứa khóa riêng SPC
Để ký một assembly gồm nhiều file, bạn cần chỉ định tên file chứa assembly manifest. Nếu muốn sử dụng cả tên mạnh và Authenticode cho assembly, bạn phải tạo tên mạnh cho assembly trước (xem cách tạo tên mạnh cho assembly trong mục 1.9).
Để kiểm tra tính hợp lệ của một file được ký với chữ ký Authenticode, sử dụng công cụ Certificate Verification (chktrust.exe). Ví dụ, sử dụng lệnh chktrust MyAssembly.exe để kiểm tra file MyAssembly.exe. Nếu chưa cấu hình cho hệ thống để nó tin tưởng SPC dùng để ký assembly, bạn sẽ thấy hộp thoại tương tự như hình 1.6, hiển thị thông tin về người phát hành và cho bạn chọn là có tin tưởng người phát hành đó hay không (chứng chỉ trong hình 1.6 là một chứng chỉ thử nghiệm được tạo theo quá trình được mô tả trong mục 1.13).
54
Chương 1: Phát triển ứng dụng
Nếu bạn nhắp Yes, hoặc trước đó đã chọn là luôn tin tưởng SPC, công cụ Certificate Verification xác nhận tính hợp lệ của chữ ký và assembly.
Hình 1.6 Công cụ Certificate Verification
13. Tạo và thiết lập tin tưởng một SPC thử nghiệm
Bạn cần tạo một SPC để thử nghiệm.
Sử dụng công cụ Certificate Creation (makecert.exe) để tạo một chứng chỉ X.509 và sử dụng công cụ Software Publisher Certificate (cert2spc.exe) để tạo một SPC từ chứng chỉ X.509 này. Thiết lập tin tưởng chứng chỉ thử nghiệm bằng công cụ Set Registry (setreg.exe).
Để tạo một SPC thử nghiệm cho một nhà phát hành phần mềm có tên là Square Nguyen, trước hết sử dụng công cụ Certificate Creation để tạo một chứng chỉ X.509. Lệnh:
makecert -n "CN=Square Nguyen" -sk MyKeys TestCertificate.cer
sẽ tạo một file có tên là TestCertificate.cer chứa một chứng chỉ X.509, và lưu trữ khóa riêng tương ứng trong một kho chứa khóa CSP có tên là MyKeys (được tạo tự động nếu chưa tồn tại). Bạn cũng có thể ghi khóa riêng vào file bằng cách thay -sk bằng -sv. Ví dụ, để ghi khóa riêng vào một file có tên là PrivateKeys.pvk, sử dụng lệnh:
makecert -n "CN=Square Nguyen" -sv PrivateKey.pvk TestCertificate.cer
55
Chương 1: Phát triển ứng dụng
Hình 1.7 Công cụ Certificate Creation nhắc nhập mật khẩu để bảo vệ file chứa khóa riêng
Nếu bạn ghi khóa riêng vào file, công cụ Certificate Creation sẽ nhắc bạn nhập mật khẩu để bảo vệ file này (xem hình 1.7).
Công cụ Certificate Creation hỗ trợ nhiều đối số, bảng 1.3 liệt kê một vài đối số thường dùng. Xem thêm tài liệu .NET Framework SDK về công cụ Certificate Creation.
Bảng 1.3 Các đối số thường dùng của công cụ Certificate Creation
Đối số Mô tả
-e Chỉ định ngày chứng chỉ không còn hiệu lực.
-m Chỉ định khoảng thời gian (tính bằng tháng) mà chứng chỉ còn hiệu lực. Chỉ định một tên X.500 tương ứng với chứng chỉ. Đây là tên của người
-n
phát hành phần mềm mà người dùng thấy khi họ xem chi tiết của SPC tạo ra.
-sk Chỉ định tên CSP giữ khóa riêng.
-ssChỉ định tên kho chứng chỉ (công cụ Certificate Creation sẽ lưu chứng chỉ X.509 trong đó).
-sv Chỉ định tên file giữ khóa riêng.
Khi đã tạo một chứng chỉ X.509 bằng công cụ Certificate Creation, cần chuyển chứng chỉ này thành một SPC bằng công cụ Software Publisher Certificate Test (cert2spc.exe). Để chuyển TestCertificate.cer thành một SPC, sử dụng lệnh:
cert2spc TestCertificate.cer TestCertificate.spc
Công cụ Software Publisher Certificate Test không có đối số tùy chọn nào. Bước cuối cùng để sử dụng SPC thử nghiệm là thiết lập tin tưởng CA thử nghiệm gốc (root test CA); đây là người phát hành mặc định các chứng chỉ thử nghiệm. Bước này chỉ cần lệnh setreg 1 true của công cụ Set Registry (setreg.exe). Khi kết thúc thử nghiệm SPC, bỏ thiết lập tin tưởng đối với CA thử nghiệm bằng lệnh setreg 1 false. Bây giờ, bạn có thể sử dụng SPC thử nghiệm để ký assembly với Authenticode như quá trình mô tả ở mục 1.12.
56
Chương 1: Phát triển ứng dụng
14. Quản lý Global Assembly Cache
Bạn cần thêm hoặc loại bỏ assembly từ Global Assembly Cache (GAC). Sử dụng công cụ Global Assembly Cache (gacutil.exe) từ dòng lệnh để xem nội dung của GAC, cũng như thêm hoặc loại bỏ assembly.
Trước khi được cài đặt vào GAC, assembly phải có tên mạnh (xem mục 1.9 về cách tạo tên mạnh cho assembly). Để cài đặt assembly có tên là SomeAssembly.dll vào GAC, sử dụng lệnh gacutil /i SomeAssembly.dll.
Để loại bỏ SomeAssembly.dll ra khỏi GAC, sử dụng lệnh gacutil /u SomeAssembly. Chú ý không sử dụng phần mở rộng .dll để nói đến assembly một khi nó đã được cài đặt vào GAC. Để xem các assembly đã được cài đặt vào GAC, sử dụng lệnh gacutil /l. Lệnh này sẽ liệt kê tất cả các assembly đã được cài đặt trong GAC, cũng như danh sách các assembly đã được biên dịch trước sang dạng nhị phân và cài đặt trong NGEN cache. Sử dụng lệnh gacutil /l SomeAssembly để tránh phải tìm hết danh sách xem một assembly đã được cài đặt chưa. 🖎 .NET Framework sử dụng GAC chỉ khi thực thi, trình biên dịch C# sẽ không tìm
trong GAC bất kỳ tham chiếu ngoại nào mà assembly của bạn tham chiếu đến. Trong quá trình phát triển, trình biên dịch C# phải truy xuất được một bản sao cục bộ của bất kỳ assembly chia sẻ nào được tham chiếu đến. Bạn có thể chép assembly chia sẻ vào thư mục mã nguồn của bạn, hoặc sử dụng đối số /lib của trình biên dịch C# để chỉ định thư mục mà trình biên dịch có thể tìm thấy các assembly cần thiết trong đó.
15. Ngăn người khác dịch ngược mã nguồn của bạn
Bạn muốn bảo đảm assembly .NET của bạn không bị dịch ngược. Xây dựng các giải pháp dựa-trên-server nếu có thể để người dùng không truy xuất assembly được. Nếu bạn phải phân phối assembly thì không có cách nào để ngăn người dùng dịch ngược chúng. Cách tốt nhất có thể làm là sử dụng kỹ thuật obfuscation và các thành phần đã được biên dịch thành mã lệnh nguyên sinh (native code) để assembly khó bị dịch ngược hơn.
Vì assembly .NET bao gồm một tập các mã lệnh và siêu dữ liệu được chuẩn hóa, độc lập nền tảng mô tả các kiểu nằm trong assembly, nên chúng tương đối dễ bị dịch ngược. Điều này cho phép các trình dịch ngược dễ dàng tạo được mã nguồn rất giống với mã gốc, đây sẽ là vấn đề khó giải quyết nếu mã của bạn có chứa các thông tin hoặc thuật toán cần giữ bí mật.
Cách duy nhất để đảm bảo người dùng không thể dịch ngược assembly là không cho họ lấy được assembly. Nếu có thể, hiện thực các giải pháp dựa-trên-server như các ứng dụng Microsoft ASP.NET và dịch vụ Web XML. Với một chính sách bảo mật tốt ở server, không ai có thể truy xuất assembly, do đó không thể dịch ngược chúng.
57
Chương 1: Phát triển ứng dụng
Nếu việc xây dựng các giải pháp dựa-trên-server là không phù hợp, bạn có hai tùy chọn sau đây:
• Sử dụng một obfuscator để khiến cho assembly của bạn khó bị dịch ngược (Visual Studio .NET 2003 có chứa phiên bản Community của một obfuscator, có tên là Dotfuscator). Obfuscator sử dụng nhiều kỹ thuật khác nhau khiến cho assembly khó bị dịch ngược; nguyên lý của các kỹ thuật này là:
▪ Đổi tên các trường và các phương thức private nhằm gây khó khăn cho việc đọc và hiểu mục đích của mã lệnh.
▪ Chèn các lệnh dòng điều khiển khiến cho người khác khó có thể lần theo logic của ứng dụng.
• Chuyển những phần của ứng dụng mà bạn muốn giữ bí mật thành các đối tượng COM hay các DLL nguyên sinh, sau đó sử dụng P/Invoke hoặc COM Interop để gọi chúng từ ứng dụng được-quản-lý của bạn (xem chương 15 về cách gọi mã lệnh không-được quản-lý).
Không có cách tiếp cận nào ngăn được những người có kỹ năng và quyết tâm dịch ngược mã nguồn của bạn, nhưng chúng sẽ làm cho công việc này trở nên khó khăn đáng kể và ngăn được hầu hết nhưng kẻ tò mò thông thường.
Nguy cơ một ứng dụng bị dịch ngược không chỉ riêng cho C# hay .NET. Một người quyết tâm có thể dịch ngược bất kỳ phần mềm nào nếu anh ta có kỹ năng và thời gian.
58
Chương1: Pháttriểnứngdụng
59
Chương 2:THAO TÁC DỮ LIỆU
2
60
61
Chương 2: Thao tác dữ liệu
Framework cung cấp nhiều kỹ thuật để đơn giản hóa hay nâng cao hiệu quả các Hthao tác dữ liệu thông dụng. Chương này sẽ đề cập các kỹ thuật sau:
ầu hết các ứng dụng đều cần thao tác trên một loại dữ liệu nào đó. Microsoft .NET
■ Thao tác chuỗi một cách hiệu quả (mục 2.1).
■ Mô tả các kiểu dữ liệu cơ sở bằng các kiểu mã hóa khác nhau (mục 2.2, 2.3, và 2.4). ■ Sử dụng biểu thức chính quy để xác nhận tính hợp lệ và thao tác chuỗi (mục 2.5 và 2.6).
■ Làm việc với ngày và giờ (mục 2.7 và 2.8).
■ Làm việc với mảng và tập hợp (mục 2.9, 2.10, và 2.11).
■ Tuần tự hóa trạng thái đối tượng và lưu nó vào file (mục 2.12).
1. Thao tác chuỗi một cách hiệu quả
Bạn cần thao tác trên nội dung của một đối tượng String và tránh chi phí của việc tự động tạo các đối tượng String mới do tính không đổi của đối tượng String.
Sử dụng lớp System.Text.StringBuilder để thực hiện các thao tác, sau đó chuyển kết quả thành String bằng phương thức StringBuilder.ToString.
Các đối tượng String trong .NET là không đổi, nghĩa là một khi đã được tạo thì chúng không thể bị thay đổi. Ví dụ, nếu bạn tạo một String bằng cách nối một số ký tự hoặc chuỗi, thì khi thêm một phần tử mới vào cuối String hiện có, bộ thực thi sẽ tạo ra một String mới chứa kết quả (chứ không phải String cũ bị thay đổi). Do đó sẽ nảy sinh chi phí đáng kể nếu ứng dụng của bạn thường xuyên thao tác trên String.
Lớp StringBuilder khắc phục vấn đề này bằng cách cung cấp một bộ đệm ký tự, và cho phép thao tác trên nội dung của nó mà bộ thực thi không phải tạo đối tượng mới để chứa kết quả sau mỗi lần thay đổi. Bạn có thể tạo một đối tượng StringBuilder rỗng hoặc được khởi tạo là nội dung của một String hiện có. Sau đó, thao tác trên nội dung của StringBuilder này bằng các phương thức nạp chồng (cho phép bạn chèn, thêm dạng chuỗi của các kiểu dữ liệu khác nhau). Cuối cùng, gọi StringBuilder.ToString để chuyển nội dung hiện tại của StringBuilder thành một String.
Khi bạn thêm dữ liệu mới vào chuỗi, có hai thuộc tính quan trọng ảnh hưởng đến hoạt động của StringBuilder là Capacity và Length. Capacity mô tả kích thước của bộ đệm StringBuilder, còn Length mô tả kích thước của chuỗi ký tự trong bộ đệm. Nếu việc thêm dữ liệu mới vào StringBuilder làm kích thước chuỗi (Length) vượt quá kích thước bộ đệm (Capacity) thì StringBuilder sẽ cấp phát bộ đệm mới để chứa chuỗi. Nếu thiếu cẩn thận, việc cấp phát bộ đệm này có thể phủ định lợi ích của việc sử dụng StringBuilder. Do đó, nếu biết chính xác kích thước của chuỗi, hoặc biết kích thước tối đa của chuỗi, bạn có thể tránh việc cấp phát bộ đệm quá mức cần thiết bằng cách thiết lập thuộc tính Capacity hoặc chỉ định kích thước bộ đệm lúc tạo StringBuilder. Khi thiết lập các thuộc tính Capacity và Length, cần chú ý các điểm sau:
62
Chương 2: Thao tác dữ liệu
• Nếu bạn thiết lập giá trị Capacity nhỏ hơn giá trị Length, thuộc tính Capacity sẽ ném ngoại lệ System.ArgumentOutOfRangeException.
• Nếu bạn thiết lập giá trị Length nhỏ hơn kích thước của chuỗi hiện có trong bộ đệm, chuỗi sẽ bị cắt bớt phần lớn hơn.
• Nếu bạn thiết lập giá trị Length lớn hơn kích thước của chuỗi, bộ đệm sẽ được "lấp" thêm các khoảng trắng cho bằng với Length. Việc thiết lập giá trị Length lớn hơn giá trị Capacity sẽ tự động điều chỉnh Capacity cho bằng với Length.
Phương thức ReverseString dưới đây minh họa cách sử dụng lớp StringBuilder để đảo một chuỗi. Nếu không sử dụng lớp StringBuilder để thực hiện thao tác này thì sẽ tốn chi phí đáng kể, đặc biệt khi chuỗi nguồn dài. Việc khởi tạo StringBuilder với kích thước bằng chuỗi nguồn bảo đảm không cần phải cấp phát lại bộ đệm trong quá trình đảo chuỗi.
public static string ReverseString(string str) {
// Kiểm tra các trường hợp không cần đảo chuỗi.
if (str == null || str.Length == 1) {
return str;
}
// Tạo một StringBuilder với sức chứa cần thiết.
System.Text.StringBuilder revStr =
new System.Text.StringBuilder(str.Length);
// Duyệt ngược chuỗi nguồn từng ký tự một
// và thêm từng ký tự đọc được vào StringBuilder.
for (int count = str.Length-1; count > -1; count--) {
revStr.Append(str[count]);
}
// Trả về chuỗi đã được đảo.
return revStr.ToString();
}
2. Mã hóa chuỗi bằng các kiểu mã hóa ký tự
Bạn cần trao đổi dữ liệu dạng ký tự với các hệ thống sử dụng kiểu mã hóa khác với UTF-16 (kiểu mã hóa này được sử dụng bởi CRL).
63
Chương 2: Thao tác dữ liệu
Sử dụng lớp System.Text.Encoding và các lớp con của nó để chuyển đổi ký tự giữa các kiểu mã hóa khác nhau.
Unicode không phải là kiểu mã hóa duy nhất, cũng như UTF-16 không phải cách duy nhất biểu diễn ký tự Unicode. Khi ứng dụng cần trao đổi dữ liệu ký tự với các hệ thống bên ngoài (đặc biệt là các hệ thống cũ), dữ liệu cần phải được chuyển đổi giữa UTF-16 và kiểu mã hóa mà hệ thống đó hỗ trợ.
Lớp trừu tượng Encoding, và các lớp con của nó cung cấp các chức năng để chuyển ký tự qua lại giữa nhiều kiểu mã hóa khác nhau. Mỗi thể hiện của lớp con hỗ trợ việc chuyển đổi giữa UTF-16 và một kiểu mã hóa khác. Phương thức tĩnh Encoding.GetEncoding nhận vào tên hoặc số hiệu trang mã (code page number) của một kiểu mã hóa và trả về thể hiện của lớp mã hóa tương ứng.
Bảng 2.1 liệt kê một vài kiểu mã ký tự và số hiệu trang mã mà bạn phải truyền cho phương thức GetEncoding để tạo ra thể hiện của lớp mã hóa tương ứng. Bảng này cũng cung cấp các thuộc tính tĩnh của lớp Encoding đại diện cho phương thức GetEncoding tương ứng.
Bảng 2.1 Các lớp mã hóa ký tự
Kiểu mã hóa Lớp Sử dụng
ASCII ASCIIEncodingGetEncoding(20127) hay thuộc tính ASCII
Mặc định (kiểu mã hóa
hiện hành trên hệ thống)EncodingGetEncoding(0)
hay thuộc tính Default
UTF-7 UTF7EncodingGetEncoding(65000) hay thuộc tính UTF7
UTF-8 UTF8EncodingGetEncoding(65001) hay thuộc tính UTF8
UTF-16 (Big Endian) UnicodeEncodingGetEncoding(1201) hay thuộc tính BigEndianUnicode
UTF-16 (Little Endian) UnicodeEncodingGetEncoding(1200) hay thuộc tính Unicode
Windows OS Encoding GetEncoding(1252)
Sau khi đã lấy được đối tượng lớp Encoding hỗ trợ kiểu mã hóa thích hợp, sử dụng phương thức GetBytes để chuyển chuỗi nguồn (được mã hóa theo UTF-16) thành mảng kiểu byte chứa các ký tự được mã hóa theo kiểu cần chuyển, và sử dụng GetString để chuyển mảng byte thành chuỗi đích. Ví dụ dưới đây trình bày cách sử dụng một vài lớp mã hóa:
using System;
using System.IO;
using System.Text;
public class CharacterEncodingExample {
64
Chương 2: Thao tác dữ liệu
public static void Main() {
// Tạo file giữ các kết quả.
using (StreamWriter output = new StreamWriter("output.txt")) {
// Tạo và ghi ra file một chuỗi chứa ký hiệu của số PI. string srcString = "Area = \u03A0r^2";
output.WriteLine("Source Text : " + srcString);
// Ghi các byte được mã hóa theo UTF-16
// của chuỗi nguồn ra file.
byte[] utf16String = Encoding.Unicode.GetBytes(srcString); output.WriteLine("UTF-16 Bytes: {0}",
BitConverter.ToString(utf16String));
// Chuyển chuỗi nguồn được mã hóa theo UTF-16 // thành UTF-8 và ASCII
byte[] utf8String = Encoding.UTF8.GetBytes(srcString); byte[] asciiString = Encoding.ASCII.GetBytes(srcString);
// Ghi mảng các byte được mã hóa theo UTF-8 và ASCII ra file. output.WriteLine("UTF-8 Bytes: {0}",
BitConverter.ToString(utf8String));
output.WriteLine("ASCII Bytes: {0}",
BitConverter.ToString(asciiString));
// Chuyển các byte được mã hóa theo UTF-8 và ASCII // thành chuỗi được mã hóa theo UTF-16 và ghi ra file. output.WriteLine("UTF-8 Text : {0}",
Encoding.UTF8.GetString(utf8String));
output.WriteLine("ASCII Text : {0}",
Encoding.ASCII.GetString(asciiString));
// Ghi dữ liệu xuống file và đóng file.
output.Flush();
65
Chương 2: Thao tác dữ liệu
output.Close();
}
}
}
Chạy CharacterEncodingExample sẽ tạo ra file output.txt. Mở file này trong một trình soạn thảo có hỗ trợ Unicode, bạn sẽ thấy kết quả như sau:
Source Text : Area = Πr^2
UTF-16 Bytes: 41-00-72-00-65-00-61-00-20-00-3D-00-20-00-A0-03-72-00-5E-00-32-00 UTF-8 Bytes: 41-72-65-61-20-3D-20-CE-A0-72-5E-32
ASCII Bytes: 41-72-65-61-20-3D-20-3F-72-5E-32
UTF-8 Text : Area = Πr^2
ASCII Text : Area = ?r^2
Chú ý rằng, nếu sử dụng UTF-16 thì mỗi ký tự được mã hóa bởi 2 byte, nhưng vì hầu hết các ký tự đều là ký tự chuẩn nên byte cao là 0 (nếu sử dụng little-endian thì byte thấp viết trước). Do đó, hầu hết các ký tự đều được mã hóa bởi những số giống nhau trong ba kiểu mã hóa, ngoại trừ ký hiệu PI được mã hóa khác (được in đậm trong kết quả ở trên). Để mã hóa PI cần 2 byte, đòi hỏi này được UTF-8 hỗ trợ nên thể hiện được Π, trong khi đó ASCII chỉ sử dụng một byte nên thay PI bằng mã 3F, đây là mã của dấu hỏi (?).
🖎 Nếu chuyển các ký tự Unicode sang ASCII hoặc một kiểu mã hóa khác thì có thể mất dữ liệu. Bất kỳ ký tự Unicode nào có mã ký tự không biểu diễn được trong kiểu mã hóa đích sẽ bị bỏ qua khi chuyển đổi.
Lớp Encoding cũng cung cấp phương thức tĩnh Covert để đơn giản hóa việc chuyển một mảng byte từ kiểu mã hóa này sang kiểu mã hóa khác không phải qua trung gian UTF-16. Ví dụ, dòng mã sau chuyển trực tiếp các byte trong mảng asciiString từ ASCII sang UTF-8:
byte[] utf8String = Encoding.Convert(Encoding.ASCII, Encoding.UTF8, asciiString);
3. Chuyển các kiểu giá trị cơ bản thành mảng kiểu byte
Bạn cần chuyển các kiểu giá trị cơ bản thành mảng kiểu byte. Lớp System.BitConverter cung cấp các phương thức tĩnh rất tiện lợi cho việc chuyển đổi qua lại giữa các mảng kiểu byte và hầu hết các kiểu giá trị cơ bản— trừ kiểu decimal. Để chuyển một giá trị kiểu decimal sang mảng kiểu byte, bạn cần sử dụng đối tượng System.IO.BinaryWriter để ghi giá trị đó vào một thể hiện System.IO.MemoryStream, sau đó gọi phương thức Memorystream.ToArray. Để có một giá trị decimal từ một mảng kiểu byte, bạn cần tạo một đối tượng MemoryStream từ mảng kiểu byte, sau đó sử dụng thể hiện System.IO.BinaryReader để đọc giá trị này từ MemoryStream.
66
Chương 2: Thao tác dữ liệu
Phương thức tĩnh GetBytes của lớp BitConverter cung cấp nhiều phiên bản nạp chồng cho phép chuyển hầu hết các kiểu giá trị cơ bản sang mảng kiểu byte. Các kiểu được hỗ trợ là bool, char, double, short, int, long, float, ushort, uint, và ulong. Lớp BitConverter cũng cung cấp các phương thức tĩnh cho phép chuyển các mảng kiểu byte thành các kiểu giá trị chuẩn như ToBoolean, ToUInt32, ToDouble,... Ví dụ sau minh họa cách chuyển các giá trị bool và int thành mảng kiểu byte, và ngược lại. Đối số thứ hai trong ToBoolean và ToUInt32 cho biết vị trí (tính từ 0) trong mảng byte mà BitConverter sẽ lấy các byte kể từ đó để tạo giá trị dữ liệu.
byte[] b = null;
// Chuyển một giá trị bool thành mảng kiểu byte và hiển thị.
b = BitConverter.GetBytes(true);
Console.WriteLine(BitConverter.ToString(b));
// Chuyển một mảng kiểu byte thành giá trị bool và hiển thị.
Console.WriteLine(BitConverter.ToBoolean(b,0));
// Chuyển một giá trị int thành mảng kiểu byte và hiển thị.
b = BitConverter.GetBytes(3678);
Console.WriteLine(BitConverter.ToString(b));
// Chuyển một mảng kiểu byte thành giá trị int và hiển thị.
Console.WriteLine(BitConverter.ToInt32(b,0));
Đối với kiểu decimal, lớp BitConverter không hỗ trợ, nên bạn phải sử dụng thêm MemoryStream và BinaryWriter.
// Tạo mảng kiểu byte từ giá trị decimal.
public static byte[] DecimalToByteArray (decimal src) {
// Tạo một MemoryStream làm bộ đệm chứa dữ liệu nhị phân.
using (MemoryStream stream = new MemoryStream()) {
// Tạo một BinaryWriter để ghi dữ liệu nhị phân vào stream. using (BinaryWriter writer = new BinaryWriter(stream)) {
// Ghi giá trị decimal vào BinaryWriter/MemoryStream. writer.Write(src);
67
Chương 2: Thao tác dữ liệu
// Trả về mảng kiểu byte.
return stream.ToArray();
}
}
}
Để chuyển một mảng kiểu byte thành một giá trị decimal, sử dụng BinaryReader để đọc từ MemoryStream.
// Tạo giá trị decimal từ mảng kiểu byte.
public static decimal ByteArrayToDecimal (byte[] src) {
// Tạo một MemoryStream chứa mảng.
using (MemoryStream stream = new MemoryStream(src)) {
// Tạo một BinaryReader để đọc từ stream.
using (BinaryReader reader = new BinaryReader(stream)) {
// Đọc và trả về giá trị decimal từ
// BinaryReader/MemoryStream.
return reader.ReadDecimal();
}
}
}
🖎 Lớp BitConverter cũng cung cấp phương thức ToString để tạo một String chứa giá trị mảng. Gọi ToString và truyền đối số là một mảng byte sẽ trả về một String chứa giá trị thập lục phân của các byte trong mảng, các giá trị này cách nhau bởi dấu gạch nối, ví dụ “34-A7-2C”. Tuy nhiên, không có phương thức nào tạo một mảng kiểu byte từ một chuỗi theo định dạng này.
4. Mã hóa dữ liệu nhị phân thành văn bản
Bạn cần chuyển dữ liệu nhị phân sang một dạng sao cho có thể được lưu trữ trong một file văn bản ASCII (chẳng hạn file XML), hoặc được gởi đi trong e mail.
Sử dụng các phương thức tĩnh ToBase64String và FromBase64String của lớp System.Converter để chuyển đổi qua lại giữa dữ liệu nhị phân và chuỗi được mã hóa theo Base64.
68
Chương 2: Thao tác dữ liệu
Base64 là một kiểu mã hóa cho phép bạn mô tả dữ liệu nhị phân như một dãy các ký tự ASCII để nó có thể được chèn vào một file văn bản hoặc một e-mail, mà ở đó dữ liệu nhị phân không được cho phép. Base64 làm việc trên nguyên tắc sử dụng 4 byte để chứa 3 byte dữ liệu nguồn và đảm bảo mỗi byte chỉ sử dụng 7 bit thấp để chứa dữ liệu. Điều này có nghĩa là mỗi byte dữ liệu được mã hóa theo Base64 có dạng giống như một ký tự ASCII, nên có thể được lưu trữ hoặc truyền đi bất cứ nơi đâu cho phép ký tự ASCII.
Lớp Convert cung cấp hai phương thức ToBase64String và FromBase64String để mã hóa và giải mã Base64. Tuy nhiên, trước khi mã hóa Base64, bạn phải chuyển dữ liệu thành mảng kiểu byte; và sau khi giải mã, bạn phải chuyển mảng kiểu byte trở về kiểu dữ liệu thích hợp (xem lại mục 2.2 và 2.3).
Ví dụ sau minh họa cách sử dụng lớp Convert để mã hóa và giải mã Base64 với chuỗi Unicode, giá trị int, giá trị decimal. Đối với giá trị decimal, bạn phải sử dụng lại các phương thức ByteArrayToDecimal và DecimalToByteArray trong mục 2.3.
// Mã hóa Base64 với chuỗi Unicode.
public static string StringToBase64 (string src) {
// Chuyển chuỗi thành mảng kiểu byte.
byte[] b = Encoding.Unicode.GetBytes(src);
// Trả về chuỗi được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một chuỗi Unicode được mã hóa theo Base64.
public static string Base64ToString (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về chuỗi Unicode.
return Encoding.Unicode.GetString(b);
}
// Mã hóa Base64 với giá trị decimal.
public static string DecimalToBase64 (decimal src) {
// Chuyển giá trị decimal thành mảng kiểu byte.
69
Chương 2: Thao tác dữ liệu
byte[] b = DecimalToByteArray(src);
// Trả về giá trị decimal được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một giá trị decimal được mã hóa theo Base64.
public static decimal Base64ToDecimal (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về giá trị decimal.
return ByteArrayToDecimal(b);
}
// Mã hóa Base64 với giá trị int.
public static string IntToBase64 (int src) {
// Chuyển giá trị int thành mảng kiểu byte.
byte[] b = BitConverter.GetBytes(src);
// Trả về giá trị int được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một giá trị int được mã hóa theo Base64.
public static int Base64ToInt (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về giá trị int.
return BitConverter.ToInt32(b,0);
}
70
Chương 2: Thao tác dữ liệu
5. Sử dụng biểu thức chính quy để kiểm tra dữ liệu nhập
Bạn cần kiểm tra dữ liệu nhập vào có đúng với cấu trúc và nội dung được quy định trước hay không. Ví dụ, bạn muốn bảo đảm người dùng nhập địa chỉ IP, số điện thoại, hay địa chỉ e-mail hợp lệ.
Sử dụng biểu thức chính quy để bảo đảm dữ liệu nhập đúng cấu trúc và chỉ chứa các ký tự được quy định trước đối với từng dạng thông tin.
Khi ứng dụng nhận dữ liệu từ người dùng hoặc đọc dữ liệu từ file, bạn nên giả định dữ liệu này là chưa chính xác và cần được kiểm tra lại. Một nhu cầu kiểm tra khá phổ biến là xác định các số điện thoại, số thẻ tín dụng, địa chỉ e-mail có đúng dạng hay không. Việc kiểm tra cấu trúc và nội dung của dữ liệu không đảm bảo dữ liệu là chính xác nhưng giúp loại bỏ nhiều dữ liệu sai và đơn giản hóa việc kiểm tra sau này. Biểu thức chính quy (regular expression) cung cấp một cơ chế rất tốt để kiểm tra một chuỗi có đúng với cấu trúc quy định trước hay không, do đó bạn có thể lợi dụng tính năng này cho mục đích kiểm tra dữ liệu nhập.
Trước tiên, bạn phải xác định cú pháp của biểu thức chính quy cho phù hợp với cấu trúc và nội dung của dữ liệu cần kiểm tra, đây là phần khó nhất khi sử dụng biểu thức chính quy. Biểu thức chính quy được xây dựng trên hai yếu tố: trực kiện (literal) và siêu ký tự (metacharacter). Trực kiện mô tả các ký tự có thể xuất hiện trong mẫu mà bạn muốn so trùng; siêu ký tự hỗ trợ việc so trùng các ký tự đại diện (wildcard), tầm trị, nhóm, lặp, điều kiện, và các cơ chế điều khiển khác. Ở đây không thảo luận đầy đủ về cú pháp biểu thức chính quy (tham khảo tài liệu .NET SDK để hiểu thêm về biểu thức chính quy), nhưng bảng 2.2 sẽ mô tả các siêu ký tự thường dùng.
Bảng 2.2 Các siêu ký tự thường dùng
Siêu ký tự Mô tả
. Mọi ký tự trừ ký tự xuống dòng (\n).
\d Ký tự chữ số thập phân (digit).
\D Ký tự không phải chữ số (non-digit).
\s Ký tự whitespace (như khoảng trắng, tab...).
\S Ký tự non-whitespace.
\w Ký tự word (gồm mẫu tự, chữ số, và dấu gạch dưới).
\W Ký tự non-word.
^ Bắt đầu một chuỗi hoặc dòng.
\A Bắt đầu một chuỗi.
$ Kết thúc một chuỗi hoặc dòng.
\z Kết thúc một chuỗi.
71
Chương 2: Thao tác dữ liệu
|Ngăn cách các biểu thức có thể so trùng, ví dụ AAA|ABA|ABB sẽ so trùng với AAA, ABA, hoặc ABB (các biểu thức được so trùng từ trái sang).
[abc]So trùng với một trong các ký tự trong nhóm, ví dụ [AbC] sẽ so trùng với A, b, hoặc C.
[^abc] So trùng với bất cứ ký tự nào không thuộc các ký tự trong nhóm, ví dụ [^AbC] sẽ không so trùng với A, b, or C nhưng so trùng với B, F,…
[a-z]So trùng với bất kỳ ký tự nào thuộc khoảng này, ví dụ [A-C] sẽ so trùng với A, B, hoặc C.
( ) Xác định một biểu thức con sao cho nó được xem như một yếu tố đơn lẻ đối với các yếu tố được trình bày trong bảng này.
?Xác định có một hoặc không có ký tự hoặc biểu thức con đứng trước nó, ví dụ A?B so trùng với B, AB, nhưng không so trùng với AAB.
*Xác định không có hoặc có nhiều ký tự hoặc biểu thức con đứng trước nó, ví dụ A*B so trùng với B, AB, AAB, AAAB,…
+Xác định có một hoặc có nhiều ký tự hoặc biểu thức con đứng trước nó, ví dụ A+B so trùng với AB, AAB, AAAB,… nhưng không so trùng với B.
{n}Xác định có đúng n ký tự hoặc biểu thức con đứng trước nó, ví dụ A{2} chỉ so trùng với AA.
{n,}Xác định có ít nhất n ký tự hoặc biểu thức con đứng trước nó, ví dụ A{2,} so trùng với AA, AAA, AAAA,… nhưng không so trùng với A.
{n, m}Xác định có từ n đến m ký tự đứng trước nó, ví dụ A{2,4} so trùng với AA, AAA, và AAAA nhưng không so trùng với A hoặc AAAAA.
Khi dữ liệu cần kiểm tra càng phức tạp thì cú pháp của biểu thức chính quy cũng càng phức tạp. Ví dụ, dễ dàng kiểm tra dữ liệu nhập chỉ chứa số hay có chiều dài tối thiểu, nhưng kiểm tra một URL khá phức tạp. Bảng 2.3 liệt kê một số biểu thức chính quy dùng để kiểm tra các kiểu dữ liệu thông dụng.
Bảng 2.3 Một số biểu thức chính quy thông dụng
Kiểu dữ liệu nhập Mô tả Biểu thức chính quy SốChỉ chứa các chữ số thập phân; ví dụ
5, hoặc 5683874674.^\d+$
PINChứa 4 chữ số thập phân,
ví dụ 1234.^\d{4}$
Mật khẩu đơn giản Chứa từ 6 đến 8 ký tự; ví dụ ghtd6f
hoặc b8c7hogh.^\w{6,8}$
Chứa dữ liệu phù hợp với cấu trúc
Số thẻ tín dụng
của hầu hết các loại số thẻ tín dụng, ví dụ 4921835221552042 hoặc 4921- 8352-2155-2042.
^\d{4}-?\d{4}-?\d{4}- ? \d{4}$
72
Chương 2: Thao tác dữ liệu
Địa chỉ e-mail
HTTP hoặc HTTPS URL
[\w-]+ nghĩa là chứa một
hoặc nhiều ký tự word
hoặc dấu gạch ngang, ví dụ [email protected]
Dữ liệu là một URL dựa-trên-HTTP hay dựa-trên-HTTPS, ví dụ http://www.microsoft.com
^[\w-]+@([\w- ]
+\.)+[\w-]+$
^https?://([\w- ] +\.)+[\w-]+(/[\w- ./ ? %=]*)?$
Một khi đã biết cú pháp của biểu thức chính quy, bạn tạo một đối tượng System.Text.RegularExpression.Regex bằng cách truyền cho phương thức khởi dựng của nó chuỗi chứa biểu thức chính quy. Sau đó, gọi phương thức IsMatch của đối tượng Regex và truyền chuỗi cần kiểm tra, phương thức này trả về một giá trị luận lý cho biết chuỗi có hợp lệ không. Cú pháp của biểu thức chính quy sẽ chỉ định Regex so trùng toàn bộ chuỗi hay chỉ so trùng một phần của chuỗi (xem ^, \A, $, và \z trong bảng 2.2)
Phương thức ValidateInput dưới đây minh họa cách kiểm tra chuỗi nhập bằng biểu thức chính quy:
public static bool ValidateInput(string regex, string input) {
// Tạo đối tượng Regex dựa trên biểu thức chính quy.
Regex r = new Regex(regex);
// Kiểm tra dữ liệu nhập có trùng với biểu thức chính quy hay không. return r.IsMatch(input);
}
Bạn có thể sử dụng đối tượng Regex để kiểm tra nhiều chuỗi, nhưng không thể thay đổi biểu thức chính quy được gắn cho nó; bạn phải tạo một đối tượng Regex mới tương ứng với một cấu trúc mới. Phương thức ValidateInput ở trên tạo ra một đối tượng Regex mới mỗi lần được gọi, thay vào đó bạn có thể sử dụng phương thức tĩnh nạp chồng IsMatch.
public static bool ValidateInput(string regex, string input) {
// Kiểm tra dữ liệu nhập có trùng với biểu thức chính quy hay không. return Regex.IsMatch(input, regex);
}
6. Sử dụng biểu thức chính quy đã được biên dịch
Bạn cần giảm thiểu các tác động lên hiệu năng của ứng dụng khi các biểu thức chính quy phức tạp được sử dụng thường xuyên.
73
Chương 2: Thao tác dữ liệu
Khi khởi tạo đối tượng System.Text.RegularExpressions.Regex, hãy truyền thêm tùy chọn Compiled thuộc kiểu liệt kê System.Text.RegularExpressions. RegexOptions để biên dịch biểu thức chính quy thành Microsoft Intermediate Language (MSIL).
Theo mặc định, khi bạn tạo đối tượng Regex, mẫu biểu thức chính quy do bạn xác định trong phương thức khởi dựng được biên dịch thành một dạng trung gian (không phải MSIL). Mỗi lần bạn sử dụng đối tượng Regex, bộ thực thi phiên dịch dạng trung gian này và áp dụng nó để kiểm tra chuỗi. Với các biểu thức chính quy phức tạp được sử dụng thường xuyên, việc phiên dịch lặp lặp đi lại có thể gây tác động xấu lên hiệu năng của ứng dụng.
Khi tùy chọn RegexOptions.Compiled được chỉ định, bộ thực thi sẽ biên dịch biểu thức chính quy thành MSIL. MSIL này được gọi là mã just-in-time (JIT), được biên dịch thành mã máy nguyên sinh trong lần thực thi đầu tiên, giống như mã assembly thông thường. Biểu thức chính quy được biên dịch cũng được sử dụng giống như đối tượng Regex, việc biên dịch chỉ giúp thực thi nhanh hơn.
Tuy nhiên, việc biên dịch biểu thức chính quy cũng có vài nhược điểm. Trước tiên, trình biên dịch JIT phải làm việc nhiều hơn, dẫn đến chậm quá trình biên dịch, đặc biệt khi tạo biểu thức chính quy được biên dịch khi ứng dụng khởi động. Thứ hai, biểu thức chính quy được biên dịch vẫn tồn tại trong bộ nhớ khi không còn được sử dụng nữa, nó không bị bộ thu gom rác (Garbage Collector) xóa đi như các biểu thức chính quy thông thường. Vùng nhớ bị chiếm chỉ được giải phóng khi chương trình kết thúc, hoặc khi bạn giải phóng miền ứng dụng.
Dòng mã sau minh họa cách tạo một đối tượng Regex được biên dịch thành MSIL: Regex reg = new Regex(@"[\w-]+@([\w-]+\.)+[\w-]+",
RegexOptions.Compiled);
Ngoài ra, phương thức tĩnh Regex.CompileToAssembly cho phép bạn tạo một biểu thức chính quy được biên dịch và ghi nó vào một assembly khác. Nghĩa là bạn có thể tạo một assembly chứa các biểu thức chính quy để sử dụng cho nhiều ứng dụng sau này. Để biên dịch một biểu thức chính quy và lưu nó vào một assembly, thực hiện các bước sau:
1. Tạo một mảng System.Text.RegularExpressions.RegexCompilationInfo đủ lớn để chứa các đối tượng RegexCompilationInfo, mỗi đối tượng ứng với một biểu thức chính quy cần được biên dịch.
2. Tạo một đối tượng RegexCompilationInfo cho mỗi biểu thức chính quy và truyền đối số cho phương thức khởi dựng để xác định các thuộc tính của biểu thức chính quy này. Các thuộc tính thông dụng là:
• IsPublic giá trị bool xác định lớp biểu thức chính quy được tạo ra có tầm vực là công khai hay không.
• Name một String xác định tên của lớp.
• Namespace một String xác định không gian tên của lớp.
• Pattern một String xác định mẫu mà biểu thức chính quy sẽ so trùng (xem chi tiết ở mục 2.5).
• Options một giá trị thuộc kiểu liệt kê System.Text.RegularExpressions. RegexOptions xác định các tùy chọn cho biểu thức chính quy.
74
Chương 2: Thao tác dữ liệu
3. Tạo một đối tượng System.Reflection.AssemblyName để xác định tên của assembly mà phương thức Regex.CompileToAssembly sẽ tạo ra.
4. Gọi phương thức Regex.CompileToAssembly, truyền các đối số là mảng RegexCompilationInfo và đối tượng AssemblyName.
Quá trình trên tạo ra một assembly chứa các khai báo lớp cho từng biểu thức chính quy được biên dịch, mỗi lớp dẫn xuất từ Regex. Để sử dụng một biểu thức chính quy đã được biên dịch trong assembly, bạn cần tạo đối tượng biểu thức chính quy này và gọi các phương thức của nó giống như khi tạo nó với phương thức khởi dựng Regex bình thường. Bạn nhớ thêm tham chiếu tới assembly khi sử dụng các lớp biểu thức chính quy nằm trong nó.
Đoạn mã sau minh họa cách tạo một assembly có tên là MyRegex.dll, chứa hai biểu thức chính quy có tên là PinRegex và CreditCardRegex:
using System.Text.RegularExpressions;
using System.Reflection;
public class CompiledRegexExample {
public static void Main() {
// Tạo mảng chứa các đối tượng RegexCompilationInfo.
RegexCompilationInfo[] regexInfo = new RegexCompilationInfo[2];
// Tạo đối tượng RegexCompilationInfo cho PinRegex.
regexInfo[0] = new RegexCompilationInfo(@"^\d{4}$",
RegexOptions.Compiled, "PinRegex", "", true);
// Tạo đối tượng RegexCompilationInfo cho CreditCardRegex. regexInfo[1] = new RegexCompilationInfo(
@"^\d{4}-?\d{4}-?\d{4}-?\d{4}$",
RegexOptions.Compiled, "CreditCardRegex", "", true);
// Tạo đối tượng AssemblyName để định nghĩa assembly.
AssemblyName assembly = new AssemblyName();
assembly.Name = "MyRegEx";
// Tạo các biểu thức chính quy được biên dịch.
Regex.CompileToAssembly(regexInfo, assembly);
75
Chương 2: Thao tác dữ liệu
}
}
7. Tạo ngày và giờ từ chuỗi
Bạn cần tạo một thể hiện System.DateTime mô tả giờ, ngày được chỉ định trong một chuỗi.
Sử dụng phương thức Parse hoặc ParseExact của lớp DateTime.
Có nhiều cách mô tả ngày, giờ; ví dụ 1st June 2004, 1/6/2004, 6/1/2004, 1-Jun-2004 cùng chỉ một ngày; 16:43 và 4:43 PM cùng chỉ một giờ. Lớp DateTime cung cấp phương thức tĩnh Parse rất linh hoạt, cho phép tạo thể hiện DateTime từ nhiều cách mô tả khác nhau trong chuỗi.
Phương thức Parse rất mạnh trong việc tạo đối tượng DateTime từ một chuỗi cho trước. Nó có thể xử lý một chuỗi chỉ chứa một phần thông tin hay chứa thông tin sai, và thay thế các giá trị thiếu bằng các giá trị mặc định. Ngày mặc định là ngày hiện tại, giờ mặc định là 12:00:00 AM. Nếu sau mọi cố gắng, Parse không thể tạo đối tượng DateTime, nó sẽ ném ngoại lệ System.FormatException. Ví dụ sau minh họa tính linh hoạt của Parse:
// 01/09/2004 12:00:00 AM
DateTime dt1 = DateTime.Parse("Sep 2004");
// 05/09/2004 02:15:33 PM
DateTime dt2 = DateTime.Parse("Sun 5 September 2004 14:15:33");
// 05/09/2004 12:00:00 AM
DateTime dt3 = DateTime.Parse("5,9,04");
// 05/09/2004 02:15:33 PM
DateTime dt4 = DateTime.Parse("5/9/2004 14:15:33");
// 07/10/2004 02:15:00 PM (giả sử ngày hiện tại là 07/10/2004)
DateTime dt5 = DateTime.Parse("2:15 PM");
Phương thức Parse linh hoạt và có thể tự sửa lỗi. Tuy nhiên, mức độ linh hoạt này không cần thiết trong trường hợp bạn muốn bảo đảm các chuỗi phải theo một định dạng nhất định. Khi đó, sử dụng phương thức ParseExact thay cho Parse. Dạng đơn giản nhất của ParseExact nhận ba đối số: chuỗi chứa ngày giờ, chuỗi định dạng xác định cấu trúc mà chuỗi chứa ngày giờ phải tuân theo, và một tham chiếu IFormatProvider cung cấp thông tin đặc thù về bản địa. Nếu IFormatProvider là null, thông tin về bản địa của tiểu trình (thread) hiện hành sẽ được sử dụng.
Nếu ngày giờ trong chuỗi đang xét không đúng với định dạng quy định, ParseExact sẽ ném ngoại lệ System.FormatException. Chuỗi định dạng được sử dụng tương tự như khi bạn chỉ
76
Chương 2: Thao tác dữ liệu
định chuỗi đại diện cho một đối tượng DateTime. Điều này có nghĩa là bạn có thể sử dụng cả định dạng chuẩn lẫn định dạng tùy biến. Tham khảo phần tài liệu cho lớp System.Globalization.DateTimeFormatInfo trong tài liệu .NET Framework SDK để có thông tin đầy đủ về tất cả các kiểu định dạng.
// Chỉ phân tích các chuỗi chứa LongTimePattern.
DateTime dt6 = DateTime.ParseExact("2:13:30 PM",
"h:mm:ss tt", null);
// Chỉ phân tích các chuỗi chứa RFC1123Pattern.
DateTime dt7 = DateTime.ParseExact(
"Sun, 05 Sep 2004 14:13:30 GMT",
"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", null);
// Chỉ phân tích các chuỗi chứa MonthDayPattern.
DateTime dt8 = DateTime.ParseExact("September 03",
"MMMM dd", null);
8. Cộng, trừ, so sánh ngày giờ
Bạn cần thực hiện các phép tính số học cơ bản hay phép so sánh trên ngày, giờ. Sử dụng các cấu trúc DateTime và TimeSpan (hỗ trợ các toán tử số học và so sánh).
Một đối tượng DateTime mô tả một thời điểm xác định (chẳng hạn 4:15 AM, ngày 21 tháng 04 năm 1980), trong khi đối tượng TimeSpan mô tả một khoảng thời gian (chẳng hạn 2 giờ, 35 phút). Bạn có thể cộng, trừ, so sánh các đối tượng TimeSpan và DateTime. Thực chất, cả DateTime và TimeSpan đều sử dụng tick để mô tả thời gian—1 tick bằng 100 nano-giây (một nano-giây bằng một phần tỷ (10-9) giây). TimeSpan lưu khoảng thời gian của nó là số tick bằng khoảng thời gian đó, DateTime lưu số tick đã trôi qua kể từ 12:00:00 khuya ngày 1 tháng 1 năm 0001 sau công nguyên. Cách tiếp cận này và việc sử dụng toán tử nạp chồng giúp DateTime và TimeSpan dễ dàng hỗ trợ các phép tính số học và so sánh. Bảng 2.4 tóm tắt các toán tử mà hai cấu trúc này hỗ trợ.
Bảng 2.4 Các toán tử được cung cấp bởi DateTime và TimeSpan
Toán tử TimeSpan DateTime
Gán (=)
Vì TimeSpan là một cấu trúc nên phép gán trả về một bản sao, không phải một tham chiếu.
Vì DateTime là một cấu trúc nên phép gán trả về một bản sao, không phải một tham chiếu.
77
Chương 2: Thao tác dữ liệu
Cộng (+) Cộng hai đối tượng TimeSpan.Cộng một TimeSpan vào một DateTime.
Trừ (-) Trừ hai đối tượng TimeSpan.Trừ một DateTime cho một DateTime hoặc một TimeSpan.
Bằng (==)So sánh hai đối tượng TimeSpan và trả về true nếu bằng nhau.
So sánh hai đối tượng DateTime và trả về true nếu bằng nhau.
Không bằng (!=)
Lớn hơn (>)
Lớn hoặc bằng (>=)
Nhỏ hơn (<)
Nhỏ hoặc bằng (<=)
So sánh hai đối tượng TimeSpan và trả về true nếu không bằng nhau.
Xác định một đối tượng TimeSpan có lớn hơn một đối tượng TimeSpan khác hay không.
Xác định một đối tượng TimeSpan có lớn hơn hoặc bằng một đối tượng TimeSpan khác hay không.
Xác định một đối tượng TimeSpan có nhỏ hơn một đối tượng TimeSpan khác hay không.
Xác định một đối tượng TimeSpan có nhỏ hơn hoặc bằng một đối tượng TimeSpan khác hay không.
So sánh hai đối tượng DateTime và trả về true nếu không bằng nhau.
Xác định một đối tượng DateTime có lớn hơn một đối tượng DateTime khác hay không.
Xác định một đối tượng DateTime có lớn hơn hoặc bằng một đối tượng DateTime khác hay không.
Xác định một đối tượng DateTime có nhỏ hơn một đối tượng DateTime khác hay không.
Xác định một đối tượng DateTime có nhỏ hơn hoặc bằng một đối tượng DateTime khác hay không.
Âm (-)Trả về một giá trị đảo dấu của một
TimeSpan.Không hỗ trợ.
Dương (+) Trả về chính TimeSpan. Không hỗ trợ.
Cấu trúc DateTime cũng hiện thực các phương thức AddTicks, AddMilliseconds, AddSeconds, AddMinutes, AddHours, AddDays, AddMonths, và AddYears. Mỗi phương thức này cho phép bạn cộng (hoặc trừ bằng các giá trị âm) phần tử thời gian thích hợp với đối tượng DateTime. Các phương thức này và các toán tử được liệt kê trong bảng 2.4 không làm thay đổi DateTime gốc —thay vào đó chúng sẽ tạo một đối tượng mới với giá trị đã được thay đổi. Đoạn mã dưới đây trình bày cách sử dụng các toán tử để thao tác các cấu trúc DateTime và TimeSpan:
// Tạo một TimeSpan mô tả 2.5 ngày.
TimeSpan timespan1 = new TimeSpan(2,12,0,0);
// Tạo một TimeSpan mô tả 4.5 ngày.
TimeSpan timespan2 = new TimeSpan(4,12,0,0);
// Tạo một TimeSpan mô tả 1 tuần.
TimeSpan oneWeek = timespan1 + timespan2;
// Tạo một DateTime với ngày giờ hiện tại.
DateTime now = DateTime.Now;
78
Chương 2: Thao tác dữ liệu
// Tạo một DateTime mô tả 1 tuần trước đây.
DateTime past = now - oneWeek;
// Tạo một DateTime mô tả 1 tuần trong tương lai.
DateTime future = now + oneWeek;
9. Sắp xếp một mảng hoặc một ArrayList
Bạn cần sắp xếp các phần tử trong một mảng hoặc một ArrayList. Sử dụng phương thức ArrayList.Sort để sắp xếp ArrayList và phương thức tĩnh Array.Sort để sắp xếp mảng.
Dạng đơn giản nhất của Sort là sắp xếp các đối tượng nằm trong một mảng hoặc ArrayList khi các đối tượng này có hiện thực giao diện System.Icomparable và có kiểu giống nhau—tất cả các kiểu dữ liệu cơ bản đều hiện thực Icomparable. Đoạn mã dưới đây minh họa cách sử dụng phương thức Sort:
// Tạo một mảng mới và thêm phần tử vào.
int[] array = {4, 2, 9, 3};
// Sắp xếp mảng.
Array.Sort(array);
// Hiển thị nội dung của mảng đã được sắp xếp.
foreach (int i in array) { Console.WriteLine(i);}
// Tạo một ArrayList mới và thêm phần tử vào.
ArrayList list = new ArrayList(4);
list.Add("Phong");
list.Add("Phuong");
list.Add("Khoa");
list.Add("Tam");
// Sắp xếp ArrayList.
list.Sort();
// Hiển thị nội dung của ArrayList đã được sắp xếp.
foreach (string s in list) { Console.WriteLine(s);}
79
Chương 2: Thao tác dữ liệu
Để sắp xếp các đối tượng không hiện thực IComparable, bạn cần truyền cho phương thức Sort một đối tượng hiện thực giao diện System.Collections.IComparer. Hiện thực của IComparer phải có khả năng so sánh các đối tượng nằm trong mảng hoặc ArrayList (xem mục 16.3 để biết cách hiện thực IComparable và IComparer).
10. Chép một tập hợp vào một mảng
Bạn cần chép nội dung của một tập hợp vào một mảng.
Sử dụng phương thức ICollection.CopyTo (được hiện thực bởi tất cả các lớp tập hợp), hoặc sử dụng phương thức ToArray (được hiện thực bởi các tập hợp ArrayList, Stack, Queue).
Các phương thức ICollection.CopyTo và ToArray có cùng chức năng, chúng chép các phần tử trong một tập hợp vào một mảng. Sự khác biệt nằm ở chỗ CopyTo chép vào một mảng đã có, trong khi ToArray tạo ra một mảng mới rồi chép vào đó.
CopyTo nhận hai đối số: một mảng và một chỉ số. Mảng này là đích của quá trình sao chép và phải có kiểu tương thích với các phần tử của tập hợp. Nếu kiểu không tương thích hay không có sự chuyển đổi ngầm từ kiểu phần tử của tập hợp sang kiểu phần tử của mảng thì ngoại lệ System.InvalidCastException sẽ bị ném. Chỉ số là một vị trí trong mảng mà bắt đầu từ đó các phần tử của tập hợp sẽ được chép vào. Nếu chỉ số này lớn hơn hoặc bằng chiều dài của mảng, hoặc số phần tử của tập hợp vượt quá sức chứa của mảng thì ngoại lệ System.ArgumentException sẽ bị ném. Đoạn mã sau minh họa cách sử dụng CopyTo để chép nội dung của một ArrayList vào một mảng:
// Tạo một ArrayList mới và thêm phần tử vào.
ArrayList list = new ArrayList(5);
list.Add("Phuong");
list.Add("Phong");
list.Add("Nam");
list.Add("Tam");
list.Add("Nhan");
// Tạo một string[] và sử dụng ICollection.CopyTo
// để chép nội dung của ArrayList.
string[] array1 = new string[5];
list.CopyTo(array1,0);
Các lớp ArrayList, Stack, và Queue cũng hiện thực phương thức ToArray, phương thức này tự động tạo một mảng với kích thước đủ để chứa các phần tử của của tập hợp. Nếu bạn không truyền đối số cho ToArray, nó sẽ trả về một object[] bất chấp kiểu của các đối tượng trong tập hợp. Tuy nhiên, bạn có thể truyền một đối tượng System.Type để chỉ định kiểu của mảng (Bạn phải ép mảng kiểu mạnh về đúng kiểu). Ví dụ sau minh họa cách sử dụng ToArray cho ArrayList ở trên:
80
Chương 2: Thao tác dữ liệu
// Sử dụng ArrayList.ToArray để tạo một object[]
// từ nội dung của tập hợp.
object[] array2 = list.ToArray();
// Sử dụng ArrayList.ToArray để tạo một string[] kiểu mạnh
// từ nội dung của tập hợp.
string[] array3 =
(string[])list.ToArray(System.Type.GetType("System.String")); 11. Tạo một tập hợp kiểu mạnh
Bạn cần tạo một tập hợp chỉ chứa các phần tử thuộc một kiểu nhất định. Tạo một lớp dẫn xuất từ lớp System.Collections.CollectionBase hay System.Collections.DictionaryBase, và hiện thực các phương thức an-toàn-về kiểu-dữ-liệu (type-safe) để thao tác trên tập hợp.
Các lớp CollectionBase và DictionaryBase có thể đóng vai trò các lớp cơ sở để dẫn xuất ra các lớp tập hợp an-toàn-kiểu mà không phải hiện thực lại các giao diện chuẩn: IDictionary, IList, ICollection, và IEnumerable.
• CollectionBase dùng cho các tập hợp dựa-trên-Ilist (như ArrayList). Thực chất, CollectionBase duy trì tập hợp bằng một đối tượng ArrayList chuẩn, có thể được truy xuất thông qua thuộc tính bảo vệ List.
• DictionaryBase dùng cho các tập hợp dựa-trên-IDictionary (như Hashtable). Thực chất, DictionaryBase duy trì tập hợp bằng một đối tượng Hashtable chuẩn, có thể được truy xuất thông qua thuộc tính bảo vệ Dictionary.
Đoạn mã sau hiện thực một tập hợp tên mạnh (dựa trên lớp CollectionBase) để thể hiện một danh sách các đối tượng System.Reflection.AssemblyName.
using System.Reflection;
using System.Collections;
public class AssemblyNameList : CollectionBase {
public int Add(AssemblyName value) {
return this.List.Add(value);
}
public void Remove(AssemblyName value) {