Bài giảng Ngôn ngữ lập trình ứng dụng (Phần 2)

pdf 105 trang ngocly 1180
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Ngôn ngữ lập trình ứng dụng (Phần 2)", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbai_giang_ngon_ngu_lap_trinh_ung_dung_phan_2.pdf

Nội dung text: Bài giảng Ngôn ngữ lập trình ứng dụng (Phần 2)

  1. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Chương 3: Lập trình hướng đối tượng trong C# 3.1 Xây dựng lớp – Đối tượng 3.1.1 Định nghĩa lớp Trong thế giới thực, bạn thường thấy nhiều đối tượng riêng lẻ thuộc cùng một loại. Có thể có hàng nghìn chiếc xe đạp cùng tồn tại, tất cả chúng đều giống nhau về cách sản xuất và mẫu mã. Mỗi chiếc xe đạp đã được tạo ra từ một tập thiết kế chung, vì thế chúng giống nhau về thành phần cấu tạo. Trong thuật ngữ hướng đối tượng, chúng ta nói rằng chiếc xe đạp là một thể hiện của một lớp các đối tượng có tên gọi là xe đạp. Một lớp là một bản thiết kế mà từ đó các đối tượng cụ thể được tạo ra. Trong C#, mọi chuyện đều xảy ra trong một lớp. Như các ví dụ mà chúng ta đã tìm hiểu trước, các hàm đều được đưa vào trong một lớp, kể cả hàm đầu vào của chương trình (hàm Main()): public classTester { public static int Main() { // } } Để định nghĩa một kiểu dữ liệu mới hay một lớp đầu tiên phải khai báo rồi sau đó mới định nghĩa các thuộc tính và phương thức của kiểu dữ liệu đó. Khai báo một lớp bằng cách sử dụng từ khoá class. Cú pháp đầy đủ của khai báo một lớp như sau: [Mức độ truy cập] class [: Lớp cơ sở] { [Nội dung lớp có thể bao gồm một hoặc nhiều thuộc tính và phương thức] [Mức độ truy cập] ; [Mức độ truy cập] () { [Nội dung phương thức] } } Trong đó: 46 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  2. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Mức độ truy cập: Thông thường, mức độ truy cập (access-modifiers) của một lớp là public. Ngoài ra các thành phần của lớp cũng có mức độ truy cập riêng cho biết loại phương thức nào được phép truy cập đến nó, hay nói cách khác nó mô tả phạm vi mà thành phần đó được nhìn thấy. Thuộc tính Giới hạn truy cập public Không hạn chế. Những thành viên được đánh dấu public có thể được dùng bởi bất kì các phương thức của lớp bao gồm những lớp khác. private Thành viên trong một lớp A được đánh dấu là private thì chỉ được truy cập bởi các phương thức của lớp A. protected Thành viên trong lớp A được đánh dấu là protected thì chỉ được các phương thức bên trong lớp A và những phương thức dẫn xuất từ lớp A truy cập. internal Thành viên trong lớp A được đánh dấu là internal thì được truy cập bởi những phương thức của bất cứ lớp nào trong cùng khối hợp ngữ với A. protected internal Thành viên trong lớp A được đánh dấu là protected internal được truy cập bởi các phương thức của lớp A, các phương thức của lớp dẫn xuất của A, và bất cứ lớp nào trong cùng khối hợp ngữ của A. Định danh: Định danh lớp chính là tên của lớp do người xây dựng chương trình tạo ra. Các định danh cần phải được đặt tên theo đúng quy tắc (Chỉ có thể chứa kí tự số, chữ và “_”, phân biệt chữ hoa, chữ thường và không được bắt đầu bằng số). Lớp cơ sở: Lớp cơ sở là lớp mà đối tượng sẽ kế thừa để phát triển ta sẽ bàn sau. Thuộc tính (trường dữ liệu): là biến dữ liệu của lớp đối tượng. Ví dụ: public class nguoi { public int tuoi;//Khai báo thuộc tính tuổi cho lớp nguoi với kiểu int } Phương thức (Methods): là thành phần xử lý của lớp đối tượng. Khi khai báo và định nghĩa 1 phương thức cần xác định những thành phần sau đây: 47 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  3. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện - Kiểu trả về: một phương thức có thể trả về một kiểu dữ liệu có sẵn hoặc do người dùng định nghĩa hoặc không trả về gì cả (void). - Tên phương thức: một định danh tuân thủ nguyên tắc đặt tên. - Danh sách tham số: một phương thức có thể có tham số truyền vào hoặc không o Truyền tham số theo kiểu tham trị (mặc định): giá trị của các biến trước khi được truyền vào phương thức và sau khi ra khỏi phương thức không thay đổi. Ví dụ: //Khai báo phương thức int ttong (int a, int b) { a=a+5; b=b+1; return a+b; } //Gọi hàm truyền tham số int so1=5, so2=10, tong; tong=ttong(so1,so2); Quan sát ví dụ trên ta thấy ban đầu so1 có giá trị là 5, so2 có giá trị là 10 khi được truyền vào trong phương thức ta thấy so1 sẽ được cộng thêm 5 và so2 được cộng thêm 1. Nhưng sau khi gọi hàm xong giá trị của so1 vẫn là 5 và so2 vẫn là 10. o Truyền tham số theo kiểu tham chiếu với từ khóa ref và out : là kiểu truyền tham số vào phương thức mà trong phương thức có thay đổi giá trị của biến thì sau khi ra khỏi phương thức giá trị của biến sẽ thay đổi. Điều nay tỏ ra rất hữu ích khi ta muốn một phương thích trả về nhiều hơn một giá trị. Ví dụ: //Khai báo phương thức int ttong (ref int a, ref int b) { a=a+5; b=b+1; return a+b; } //Gọi hàm truyền tham số int so1=5, so2=10, tong; tong=ttong(so1,so2); 48 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  4. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Kết quả sau khi ra khỏi phương thức ttong so1 sẽ có giá trị bằng 10, và so2 có giá trị là 11. Từ khóa ref và out đều có thể được sử dụng trong trường hợp này, sự khác nhau cơ bản là nếu dùng out thì không cần khởi tạo các biến trước khi truyền vào phương thức. o Truyền tham số kiểu mảng với từ khóa params : điều này rất hữu ích khi ta muốn tạo ra một phương thức với số lượng tham số là một dãy số, với số lượng không có định, tùy thuộc vào lúc gọi phương thức. Ví dụ: //Khai báo phương thức public void DisplayVals( params int[] intVals) foreach (int i inintVals) { Console.WriteLine(“DisplayVals: {0}”, i); } //Gọi phương thức DisplayVals(1,2,6,5,4,7); DisplayVals(2,2,1,3,4,3,5,6,6,8,9); int[] mang = new int[5]{2,4,9,7,8}; DisplayVals(mang); - Phần thực thi của phương thức: nội dung phương thức tùy vào mục đích sử dụng của phương thức. Ví dụ. public class nguoi { public void hello()// Phương thức không có tham số, không trả về gì cả { Console.WriteLine(“Xin chao cac ban”); } public int tinhTuoi(int nam_sinh)// Phương thức có tham số và trả về kiểu int { int tuoi=2014-nam_sinh; return tuoi;//Với phương thức có giá trị trả về cần có câu lệnh return để xác định xem phương thức sẽ trả về cái gì. } } 3.1.2 Tạo đối tượng 49 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  5. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Các đối tượng là điểm cốt lõi để hiểu về công nghệ hướng đối tượng. Bây giờ hãy nhìn xung quanh và bạn sẽ thấy được rất nhiều ví dụ về đối tượng của thế giới thực: con chó, cái bàn, ti vi, xe đạp. Đối tượng trong thế giới thực có chung hai đặc điểm: Tất cả đều có trạng thái và hành vi. Chó có trạng thái (tên, màu sắc, loại, tình trạng đói hay no) và hành vi (sủa, tha đồ vật đến, vẫy đuôi). Xe đạp cũng có trạng thái (bánh răng, nhịp bàn đạp hiện tại, tốc độ hiện tại) và hành vi (thay đổi bánh răng, thay đổi nhịp bàn đạp, sử dụng phanh). Việc xác định trạng thái và hành vi của các đối tượng trong thế giới thực là một cách tuyệt vời để bắt đầu nghĩ đến các khái niệm của lập trình hướng đối tượng. Bây giờ ta hãy dành ra một vài phút để quan sát các đối tượng của thế giới thực xung quanh bạn. Đối với mỗi đối tượng mà bạn nhìn thấy, hãy tự đặt ra cho mình hai câu hỏi: “Đối tượng này có thể ở trong những trạng thái nào?” và “Đối tượng này có thể thưc hiện những hành vi nào?” Hãy chắc chắn rằng bạn đã ghi lại những quan sát của mình. Khi làm thế, bạn sẽ nhận thấy rằng các đối tượng trong thế giới thực có sự khác nhau về độ phức tạp; chiếc đèn bàn có thể chỉ có 2 trạng thái (đang bật và đang tắt) và hai hành vi (bật và tắt), nhưng chiếc radio có thể có thêm các trạng thái khác (đang bật, đang tắt, âm lượng hiện tại, kênh hiện tại) và hành vi (bật, tắt, tăng âm lượng, giảm âm lượng, tìm kiếm, dò kênh và điều chỉnh). Bạn cũng sẽ nhận thấy rằng một số đối tượng này có thể chứa các đối tượng khác. Những quan sát về thế giới thực này đều được chuyển vào trong thế giới của lập trình hướng đối tượng. Một đối tượng phần mềm. Về mặt khái niệm thì các đối tượng phần mềm cũng tương tự như các đối tượng trong thế giới thực: Nó cũng bao gồm các trạng thái và hành vi liên quan. Một đối tượng lưu trữ trạng thái của nó trong các trường (có thể được gọi là biến trong một số ngôn ngữ lập trình) và thể hiện các hành vi của mình ra bên ngoài thông qua các phương thức (có thể được gọi là hàm ở trong một số ngôn ngữ lập trình). Các phương thức thao tác trên các trạng thái bên trong của một đối tượng và được dùng như là cơ 50 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  6. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện chế chính cho sự giao tiếp giữa đối tượng với đối tượng. Việc ẩn đi trạng thái bên trong và bắt buộc tất các các tương tác đều phải được thực hiện thông qua các phương thức của một đối tượng được biết đến như là sự bao gói dữ liệu (data encapsulation) – một nguyên lý cơ bản của lập trình hướng đối tượng. Hãy lấy một chiếc xe đạp làm ví dụ: Chiếc xe đạp được mô hình hóa như là một đối tượng phần mềm. Bằng cách mô tả các trạng thái (tốc độ hiện tại, nhịp đạp hiện tại, bánh răng hiện tại) và cung cấp các phương thức để thay đổi các trạng thái đó, thì một đối tượng vẫn kiểm soát được cách thức mà thế giới bên ngoài được phép sử dụng nó. Ví dụ, nếu chiếc xe đạp chỉ có 6 bánh răng, một phương thức để thay đổi bánh răng có thể từ chối bất cứ giá trị nào nhỏ hơn 1 hoặc lớn hơn 6. Cú pháp tạo đối tượng = new (); Ví dụ: //Khai báo lớp human class human() { int namSinh; string ten; void chao() { Console.WriteLine(“Xin chao cac ban”); } } //Tạo một đối tượng thuộc lớp human human hm1=new human(); //Đối tượng hm1 sẽ có 2 thuộc tính là namSinh và ten cũng như phương Phương thức khởi tạo (Constructor) 51 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  7. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Constructor được định nghĩa trong quá trình khai báo class, nếu như không định nghĩa Constructor thì chương trình sẽ tự sinh Constructor mặc định không chứa đối số. Mục đích chính của Constructor là thiết lập các giá trị cho các thuộc tính sau khi khởi tạo các đối tượng. Constructor có thể có một, nhiều đối số hoặc không có đối số nào. Tên phương thức Constructor khi báo phải trùng (chính xác) tên với tên của class. Có thể tạo nhiều Constructor cho class nhưng các constructor phải khác nhau về đối số. Đây cũng là khái niệm overload trong lập trình hướng đối tượng, nghĩa là tạo nhiều hàm / phương thức cùng tên nhưng khác về đối số. Đối số khác nhau ở đây là số lượng đối số và thứ tự các đối số. Constructor không có đối số là constructor mặc định. Hiểu nôm na là phương thức khởi dựng sẽ là phương thức được thực hiện đầu tiên khi vừa tạo đối tượng. Ví dụ //Khai báo lớp class Library { private int ibooktypes; //Constructor public Library() // Hàm tạo không đối số. { ibooktypes = 7; } public Library(int value) // Hàm tạo có một đối số { ibooktypes = value; } } //Tạo đối tượng Library lb1 = new Library(); // Hàm tạo không đối số; Library lb2 = new Library(100); // Hàm tạo được truyền vào với giá trị là 100, và nó sẽ được gán vào ibooktypes = 100; Khởi tạo biến thành viên Các biến thành viên có thể được khởi tạo trực tiếp khi khai báo trong quá trình khởi tạo, thay vì phải thực hiện việc khởi tạo các biến trong bộ khởi dựng. Để thực hiện việc khởi tạo này rất đơn giản là việc sử dụng phép gán giá trị cho một biến: Ví dụ: 52 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  8. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện private int Giay = 30; // Khởitạo Việc khởi tạo biến thành viên sẽ rất có ý nghĩa, vì khi xác định giá trị khởi tạo như vậy thì biến sẽ không nhận giá trị mặc định mà trình biên dịch cung cấp. Khi đó nếu các biến này không được gán lại trong các phương thức khởi dựng thì nó sẽ có giá trị mà ta đã khởi tạo. Sử dụng từ khóa this Từ khóa this được dùng để tham chiếu đến thể hiện hiện hành của một đối tượng. Tham chiếu this này được xem là con trỏ ẩn đến tất các phương thức không có thuộc tính tĩnh trong một lớp. Mỗi phương thức có thể tham chiếu đến những phương thức khác và các biến thành viên thông qua tham chiếu this này. Ví dụ: //Khai báo lớp class Library { private int ibooktypes; //Constructor public Library() // Hàm tạo không đối số. { ibooktypes = 7; } public Library(int ibooktypes ) // Hàm tạo có một đối số { this.ibooktypes = ibooktypes; //Sử dụng từ khóa this để tham chiếu tới thành phần của lớp } } 3.1.3 Sử dụng các thành viên static Việc truy cập đến thành viên tĩnh phải thực hiện thông qua tên lớp (không được truy cập thành viên tĩnh thông qua đối tượng) theo cú pháp chung: TênLớp.TênThànhViênTĩnh - Sử dụng phương thức tĩnh Một phương thức static có phạm vi hoạt động giống như một phương thức toàn cục mà không cần tạo ra bất cứ một thể hiện nào của lớp cả. Toàn cục ở đây hiểu theo nghĩa là toàn cục trong lớp. Để truy cập tới các thành viên tĩnh ta có cú pháp như sau: Tenlop.tenhamtinh ([danh sach tham so neu co]: Ví dụ diem.hien() là lời gọi đến phương thúc tĩnh có tên là hien() của lớp diem. - Sử dụng các thuộc tính tĩnh: 53 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  9. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Trong C# không hề có một biến nào có phạm vi hoạt động toàn cục như trong một số ngôn ngữ lập trình khác ( pascal, C, C++, Visual Basic ) việc sử dụng một biến với mục đích “toàn cục” trở nên là một điều không thể. Biến toàn cục trong các ngôn ngữ khác được hiểu là toàn cục trong ứng dụng nhưng đối với C# thì toàn cục theo nghĩa hiểu của nó là toàn cục trong một lớp và không có khái niệm toàn cục trong toàn bộ chương trình. Nếu ta khai báo một biến thành viên tĩnh của lớp thì biến thành viên tĩnh này có tầm vực hoạt động theo ý nghĩa toàn cục đó. Các biến thành viên tĩnh có hoạt động tích cực trong vai trò này. Cú pháp sử dụng thuộc tính tĩnh: Tenlop.tenThuocTinh; 3.1.4 Nạp chồng Thông thường khi lập trình thì các phương thức, các biến sẽ không được có tên trùng nhau. Tuy nhiên trong C# lại có cơ chế cho phép chúng ta đặt tên các phương thức trùng nhau nhưng phải khác nhau về thành phần tham số, điều này goi là nạp chồng phương thức. Ví dụ: void myMethod( int p1 ); void myMethod( float p1 ); void myMethod( int p1, int p2 ); void myMethod( int p1, string p2 ); trong ví dụ trên ta thấy có 4 phương thức trùng tên nhau tuy nhiên lại khác nhau về thành phần tham số (số lượng tham số, kiểu dữ liệu của tham số ) 3.2 Kế thừa - Đa hình 3.2.1 Sự kế thừa Là ngôn ngữ lập trình hướng đối tượng, nên C# cũng có đặc điểm về thừa kế (inheritance). Thừa kế là một trong những tính chất của lập trình hướng đối tượng. Nhờ sử dụng khái niệm thừa kế mà ta có thể tạo ra các lớp mới từ lớp cơ sở đã có. Khi sử dụng thừa kế, ta có thể tái sử dụng mã chương trình. Một số khái niệm cơ bản Lớp thừa kế gọi là lớp dẫn xuất (derived class) hay lớp con. Lớp cơ sở (base class) còn gọi là lớp cha. Lớp dẫn xuất thừa kế các thuộc tính (dữ liệu), các phương thức (method) của lớp cơ sở (base class). Cú pháp khai báo kế thừa [mức độ truy cập] class : { } 54 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  10. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Khi khai báo như trên tức là lớp con kế thừa lớp cha, lớp con sẽ được kế thừa các thuộc tính và phương thức của lớp cha tùy thuộc vào thuộc mức độ truy cập của các thành phần đó. Ví dụ: public class lopcha { int a=10; public void hello() { Console.WriteLine("Day la phuong thuc cua lop cha"); } } public class lopcon:lopcha//lopcon được khai báo kế thừa lopcha { //lớp con sẽ được sử dụng các thành phần của lớp cha } static void Main(string[] args) { lopcon lc = new lopcon(); lc.hello();//Vì phương thức hello của lớp cha khai báo public nên lớp con được sử dụng //lc.a = 10;//Vì thuộc tính a không khai báo mức độ truy cập (mặc định sẽ là private) nên lớp con không thể sử dụng được. Console.ReadKey(); } Kết quả: Ta thấy lớp con không có phương thức nào nhưng do kế thừa lớp cha nên nó sử dụng phương thức hello của lớp cha. Gọi phương thức khởi dựng của lớp cơ sở Vì lớp dẫn xuất không thể kế thừa phương thức khởi dựng của lớp cơ sở nên một lớp dẫn xuất phải thực thi phương thức khởi dựng riêng của mình. Nếu lớp cơ sở có một phương thức khởi dựng mặc định (tức là không có phương thức khởi dựng hoặc phương thức khởi dựng không có tham số) thì phương thức khởi dựng của lớp dẫn xuất được định nghĩa như cách thông thường. Nếu lớp cơ sở có phương thức khởi dựng có tham số thì lớp dẫn xuất cũng phải định nghĩa phương thức khởi dựng có tham số theo cú pháp sau: TênLớpCon(ThamSốLớpCon): TênLớpCơSở(ThamSốLớpCha) 55 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  11. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện { // Khởi tạo giá trị cho các thành phần của lớp dẫn xuất } Ví dụ: //Lop co so class Point2D { public int x,y; //phuong thuc tao lap cua lop co so co tham so public Point2D(int a, int b) { x = a; y = b; } public void Xuat2D() { Console.Write("({0}, {1})", x, y); } } //Lop dan xuat class Point3D:Point2D { public int z; //Vi phuong thuc tao lap cua lop co so co tham so nen phuong thuc tao lap cua lop dan xuat cung phai co tham so public Point3D(int a, int b, int c):base (a,b) { z = c; } public void Xuat3D() { Console.Write("({0}, {1}, {2})", x, y, z); } 56 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  12. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện } class PointApp { public static void Main() { Point2D p2 = new Point2D(1, 2); Console.Write("Toa do cua diem 2 D :"); p2.Xuat2D(); Console.WriteLine(); Point3D p3 = new Point3D(4,5,6); Console.Write("Toa do cua diem 3 D :"); p3.Xuat3D(); Console.ReadLine(); } } Trong ví dụ trên, vì phương thức khởi dựng của lớp Point2D có tham số: public Point2D(int a, int b ) nên khi lớp Point3D dẫn xuất từ lớp Point2D, phương thức khởi dựng của nó cần có ba tham số. Hai tham số đầu tiên dùng để khởi gán cho các biến x, y kế thừa từ lớp Point2D, tham số còn lại dùng để khởi gán cho biến thành viên z của lớp Point3D. Phương thức khởi dựng có nguyên mẫu như sau: public Point3D(int a, int b, int c):base (a, b) Phương thức khởi dựng này gọi phương thức khởi dựng của lớp cơ sở Point2D bằng cách đặt dấu “:” sau danh sách tham số và từ khoá base với các đối số tương ứng với phương thức khởi dựng của lớp cơ sở. Truy xuất tới các thành phần lớp cơ sở Trong lớp dẫn xuất để truy xuất tới các thành phần thuộc lớp cơ sở ta sử dụng từ khóa base. base.tenThanhPhan; Trong ví dụ trên lớp Point3 kế thừa lớp Point2 vậy trong lớp Point3 ta có thể gọi tới phương thức Xuat2D() trong lớp Point2 bằng câu lệnh: base.Xuat2D(); nếu muốn gọi tới các thuộc tính của lớp cơ sở ta cũng làm tương tự. Định nghĩa phiên bản mới trong lớp dẫn xuất Lớp dẫn xuất sẽ kế thừa tất cả các thành viên của lớp cơ sở, bao gồm tất cả các phương thức và biến thành viên của lớp cơ sở. Lớp dẫn xuất được tự do thực thi các 57 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  13. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện phiên bản của một phương thức của lớp cơ sở. Tuy nhiên trong một số trường hợp lớp dẫn xuất muốn tạo ra các phiên bản mới cho một số phương thức của lớp cơ sở cho phù hợp với mình. Lớp dẫn xuất có thể làm việc đó thông qua từ khóa new. Ví dụ: class XeHoi { protected int TocDo; protected string BienSo; protected string HangSX; public XeHoi(int td, string BS, string HSX) { TocDo = td; BienSo = BS; HangSX = HSX; } public void Xuat() { Console.Write("Xe: {0}, Bien so: {1}, Toc do: {2} kmh",HangSX, BienSo, TocDo); } } class XeCar: XeHoi { int SoHanhKhach; public XeCar(int td, string BS, string HSX, int SHK): base(td, BS, HSX) { SoHanhKhach = SHK; } public new void Xuat()//Lớp XeCar kế thừa lớp XeHoi và tiến hành định nghĩa một phiên bản mới cho phương thức Xuat() với từ khóa new { // Goi phuong thuc xuat cua lop co so thong qua tu khoa base base.Xuat(); Console.WriteLine(", {0} cho ngoi", SoHanhKhach); } } 3.2.2 Đa hình 58 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  14. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Hai đặc điểm mạnh nhất của kế thừa đó là khả năng sử dụng lại mã chương trình và đa hình (polymorphism). Đa hình là ý tưởng “sử dụng một giao diện chung cho nhiều phương thức khác nhau”, dựa trên phương thức ảo (virtual method) và cơ chế liên kết muộn (late binding). Nói cách khác, đây là cơ chế cho phép gởi một loại thông điệp tới nhiều đối tượng khác nhau mà mỗi đối tượng lại có cách xử lý riêng theo ngữ cảnh tương ứng của chúng. Tính đa hình (polymorphism) được hiểu như là khả năng sử dụng nhiều hình thức của một kiểu mà không cần phải quan tâm đến từng chi tiết. Ví dụ khi một tổng đài điện thoại gửi cho máy điện thoại của chúng ta một tín hiệu có cuộc gọi. Tổng đài không quan tâm đến điện thoại của ta là loại nào. Có thể ta đang dùng một điện thoại cũ dùng motor để rung chuông, hay là một điện thoại điện tử phát ra tiếng nhạc số. Hoàn toàn các thông tin về điện thoại của ta không có ý nghĩa gì với tổng đài, tổng đài chỉ biết một kiểu cơ bản là điện thoại mà thôi và diện thoại này sẽ biết cách báo chuông. Còn việc báo chuông như thế nào thì tổng đài không quan tâm. Tóm lại, tổng đài chỉ cần bảo điện thoại hãy làm điều gì đó để reng. Còn phần còn lại tức là cách thức reng là tùy thuộc vào từng loại điện thoại. Đây chính là tính đa hình. Để thực hiện được đa hình ta phải thực hiện các bước sau: 1. Lớp cơ sở đánh dấu phương thức ảo bằng từ khóa virtual hoặc abstract. 2. Các lớp dẫn xuất định nghĩa lại phương thức ảo này (đánh dấu bằng từ khóa override). 3. Vì tham chiếu thuộc lớp cơ sở có thể trỏ đến một đối tượng thuộc lớp dẫn xuất và có thể truy cập hàm ảo đã định nghĩa lại trong lớp dẫn xuất nên khi thực thi chương trình, tùy đối tượng được tham chiếu này trỏ tới mà phương thức tương ứng được gọi thực hiện. Nếu tham chiếu này trỏ tới đối tượng thuộc lớp cơ sở thì phương thức ảo của lớp cơ sở được thực hiện. Nếu tham chiếu này trỏ tới đối tượng thuộc lớp dẫn xuất thì phương thức ảo đã được lớp dẫn xuất định nghĩa lại được thực hiện. Ví dụ: using System; public class MyWindow { protected int top, left; //Toa do goc tren ben trai public MyWindow(int t, int l) { top = t; left = l; } // Phuong thuc ao 59 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  15. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện public virtual void DrawWindow( ) { Console.WriteLine(" dang ve Window tai toa do {0}, {1}", top, left); } } public class MyListBox : MyWindow { string listBoxContents; public MyListBox(int top,int left,string contents):base(top, left) { listBoxContents = contents; } public override void DrawWindow( ) { Console.WriteLine(" dang ve listbox {0} tai toa do: {1},{2}", listBoxContents, top, left); } } public class MyButton : MyWindow { public MyButton(int top,int left):base(top, left) {} public override voidDrawWindow( ) { Console.WriteLine (" dang ve button tai toa do:{0},{1}", top, left); } } public class Tester { static void Main( ) { Random R = new Random(); int t; string s = ""; MyWindow[] winArray = new MyWindow[4]; for(int i = 0;i < 4; i++) { 60 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  16. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện t = R.Next() % 3; switch(t) { case 0: winArray[i] =new MyWindow( i * 2, i * 4); break; case 1: s = "thu " + (i+1).ToString(); winArray[i] = new MyListBox(i*3, i * 5, s); break; case 2: winArray[i] =new MyButton(i * 10, i * 20); break; } } for(int i = 0; i < 4; i++) { winArray[i].DrawWindow( ); } Console.ReadLine(); } } Trong ví dụ này ta xây dựng một lớp MyWindow có một phương thức ảo: public virtual void DrawWindow( ) Các lớp MyListBox, MyButton kế thừa từ lớp MyWindow và định nghĩa lại (override) phương thức DrawWindow() theo cú pháp: public override void DrawWindow( ) Sau đó trong hàm Main() ta khai báo và tạo một mảng các đối tượng MyWindow. Vì mỗi phần tử thuộc mảng này là một tham chiếu thuộc lớp MyWindow nên nó có thể trỏ tới bất kỳ một đối tượng nào thuộc các lớp kế thừa lớp MyWindow, chẳng hạn lớp MyListBox hay lớp MyButton. Vòng lặp for đầu tiên tạo ngẫu nhiên các đối tượng thuộc một trong các lớp MyWindow, MyListBox, MyButton, vì vậy, tại thời điểm biên dịch chương trình, trình biên dịch không biết đối tượng thứ i thuộc lớp nào và do đó chưa thể xác định được đoạn mã của phương thức DrawWindow() cần gọi. Tuy nhiên, tại thời điểm chạy chương trình, sau vòng lặp for đầu tiên, mỗi winArray[i] tham chiếu tới một loại đối tượng cụ thể nên trình thực thi sẽ tự động xác định được phương thức DrawWindow() cần gọi. (Như vậy ta đã sử dụng một giao diện chung là DrawWindow() cho nhiều phương thức khác nhau). 61 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  17. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Chú ý rằng nếu phương thức DrawWindow() trong các lớp MyListBox, MyButton, không có từ khóa override như cú pháp: public override void DrawWindow( ) thì trình biên dịch sẽ báo lỗi. 3.2.3 Lớp trừu tượng, lớp cô lập, giao diện a. Lớp trừu tượng Lớp trừu tượng (Abstract Class) là lớp dùng để định nghĩa những thuộc tính và hành vi chung của những lớp khác Hay nói cách khác lớp trừu tượng là lớp dùng để khai báo thuộc tính và phương thức cho các lớp khác sử dụng. Lớp trừu tượng được dùng như một lớp cha (base class) của các lớp có cùng bản chất. Bản chất ở đây được hiểu là kiểu, loại, nhiệm vụ của class. Mỗi lớp dẫn xuất (derived class - lớp con) có thể thừa kế từ một lớp trừu tượng. Từ khóa abstract được dùng để định nghĩa một lớp trừu tượng. Những lớp được định nghĩa bằng cách dùng từ khóa abstract thì không cho phép khởi tạo đối tượng (instance) của lớp ấy. Lớp trừu tượng (abstract class) có thể coi như một quy tắc chung cho các lớp con (sub class) kế thừa từ nó. Khi các lớp kế thừa từ nó thì bắt buộc phải định nghĩa lại (override) tất cả các phương thức đã được khai báo abstract trong lớp trừu tượng. Cú pháp khai báo phương thức trừu tượng: abstract public void TênPhươngThức( ); Phương thức trừu tượng phải được đặt trong lớp trừu tượng. Lớp trừu tượng có từ khóa abstract đứng trước. Cú pháp khai báo lớp trừu tượng: abstract [thuộc tính truy cập] class {tên lớp} Ví dụ:Xây dựng lớp HinhHocvới phương thức tính chu vi, diện tích là phương thức trừu tượng hoặc phương thức ảo. Sau đó định nghĩa các lớp HinhChuNhat (hình chữ nhật), HinhTron (hình tròn) kế thừa từ lớp HinhHoc với các thành phần dữ liệu và phương thức tính chu vi, diện tích cụ thể của từng loại đối tượng. // lop hinh hoc (truu tuong) abstract public class HinhHoc { abstract public doubleDienTich(); virtual public doubleChuVi() { return 0;} } // lop hinh tron ke thua tu lop hinh hoc public classHinhTron : HinhHoc { double _bankinh; 62 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  18. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện public double BanKinh { get{ return_bankinh;} set{ _bankinh = value;} } public override doubleDienTich() { return _bankinh*_bankinh*3.1416; } public override doubleChuVi() { return _bankinh*2*3.1416; } } // lop hinh chu nhat ke thua tu lop hinh hoc public classHinhChuNhat : HinhHoc { double_dai, _rong; public doubleChieuDai { get{ return_dai;} set{ _dai = value;} } public doubleChieuRong { get{ return_rong;} set{ _rong = value;} } public override doubleDienTich(){ return _dai*_rong;} public override doubleChuVi(){return( _dai+_rong )*2;} } classTester { static voidMain(string[] args) { HinhHoc h; 63 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  19. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện HinhTron t = newHinhTron(); t.BanKinh = 5; Console.WriteLine("Thong tin ve hinh tron"); h = t; Console.WriteLine("Chu vi hinh tron: {0} ", h.ChuVi()); Console.WriteLine("Dien tich hinh tron:{0} ", h.DienTich()); HinhChuNhat n = newHinhChuNhat(); n.ChieuDai = 4; n.ChieuRong = 3; h = n; Console.WriteLine("Thong tin ve hinh chu nhat "); Console.WriteLine("Chu vi hinh chu nhat:{0}", h.ChuVi()); Console.WriteLine("Dien tich hinh chu nhat:{0}", h.DienTich()); Console.ReadLine(); } } Trong lớp HinhHoc ở ví dụ trên, ta khai báo phương thức tính diện tích là một phương thức trừu tượng (không có phần cài đặt mã của phương thức vì chưa biết đối tượng hình thuộc dạng nào nên không thểtính diện tích của nó). Như vậy, lớp HinhHoc là một lớp trừu tượng. abstract public double DienTich(); Trong lớp trên, ta cũng khai báo một phương thức tính chu vi là phương thức ảo với mục đích minh họa rằng trong lớp trừu tượng có thể có phương thức ảo (hoặc bất cứ một thành phần nào khác của một lớp). Vì đây là một phương thức không trừu tượng nên phải có phần cài đặt mã bên trong thân phương thức. Các lớp HinhChuNhat, HinhTron kế thừa từ lớp HinhHoc nên chúng buộc phải cài đặt lại phương thức tính diện tích. Trong hàm Main(), ta tạo ra đối tượng nthuộc lớp HinhChuNhat và đối tượng thuộc lớp HinhTron. Khi tham chiếu h thuộc lớp HinhHoc trỏ tới đối tượng (tương tự với đối tượng t), nó có thể gọi được phương thức tính diện tích của lớp HinhChuNhat (tương tự lớp HinhTron), điều này chứng tỏ phương thức trừu tượng cũng có thể dùng với mục đích đa hình. Chú ý: Phân biệt giữa từ khóa new và override • Từ khóa override dùng để định nghĩa lại (ghi đè) phương thức ảo (virtual) hoặc phương thức trừu tượng (abstract) của lớp cơ sở, nó được dùng với mục đích đa hình. 64 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  20. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện • Từ khóa new để che dấu thành viên của lớp cơ sở trùng tên với thành viên của lớp dẫn xuất. b. Lớp cô lập Ngược với các lớp trừu tượng là các lớp cô lập. Một lớp trừu tượng được thiết kế cho các lớp dẫn xuất và cung cấp các khuôn mẫu cho các lớp con theo sau. Trong khi một lớp cô lập thì không cho phép các lớp dẫn xuất từ nó. Để khai báo một lớp cô lập ta dùng từ khóa sealed đặt trước khai báo của lớp không cho phép dẫn xuất. Hầu hết các lớp thường được đánh dấu sealed nhằm ngăn chặn các tai nạn do sự kế thừa gây ra. Nếu khai báo của lớp Window trong ví dụ 5.3 được thay đổi từ khóa abstract bằng từ khóa sealed (cũng có thể loại bỏ từ khóa trong khai báo của phương thức rawWindow()). Chương trình sẽ bị lỗi khi biên dịch. Nếu chúng ta cố thử biên dịch chương trình thì sẽ nhận được lỗi từ trình biên dịch: ‘ListBox’ cannot inherit from sealed class ‘Window’ Đây chỉ là một lỗi trong số những lỗi như ta không thể tạo một phương thức thành viên protected trong một lớp khai báo là sealed. c. Giao diện Các subclass trong C# không thể kế thừa nhiều hơn một class. Chính bởi điều này C# không hỗ trợ đa kế thừa. Từ hạn chế này interface được giới thiệu. Một class trong C# thực thi được nhiều interface. Giao diện là một dạng của lớp trừu tượng được sử dụng với mục đích hỗ trợ tính đa hình. Trong giao diện không có bất cứ một cài đặt nào, chỉ có nguyên mẫu của các phương thức, chỉmục, thuộc tính mà một lớp khác khi kếthừa nó thì phải có cài đặt cụ thể cho các thành phần này (tức là lớp kế thừa giao diện tuyên bố rằng nó hỗ trợ các phương thức, thuộc tính, chỉ mục được khai báo trong giao diện). Khi một lớp kế thừa một giao diện ta nói rằng lớp đó thực thi (implement) giao diện. Ta dùng từ khóa interface để khai báo một giao diện với cú pháp sau: [MứcĐộTruyCập] interface TênGiaoDiện [:CácGiaoDiệnCơSở] { //Nội dung } MứcĐộTruyCập: public hoặc internal. CácGiaoDiệnCơSở: danh sách các interface khác mà nó kế thừa. Về mặt cú pháp, một giao diện giống như một lớp chỉ có phương thức trừu trượng. Nó có thể chứa khai báo của phương thức, thuộc tính, chỉ mục, sự kiện (events) nhưng không được chứa biến dữ liệu. Khi kế thừa một giao diện ta phải thực thi mọi phương thức, thuộc tính, của giao diện. 65 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  21. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Chú ý: • Mặc định, tất cả các thành phần khai báo trong giao diện đều là public. Nếu có từ khóa public đứng trước sẽ bị báo lỗi. Các thành phần trong giao diện chỉ là các khai báo, không có phần cài đặt mã. • Một lớp có thể kế thừa một lớp khác đồng thời kế thừa nhiều giao diện. Ví dụ: Mọi chiếc xe hơi hoặc xe máy đều có hành động (phương thức) khởi động và dừng. Ta có thể dùng định nghĩa một giao diện kèm thêm một thuộc tính để cho biết chiếc xe đã khởi động hay chưa: public interfaceIDrivable { void KhoiDong(); void Dung(); bool DaKhoiDong { get; } } Thuộc tính đã khởi động (DaKhoiDong) chỉ có phương thức get vì khi gọi phương thức KhoiDong() thì thuộc tính này sẽcó giá trị true, khi gọi phương thức Dung() thì thuộc tính này sẽ có giá trị false. Khi đó, một lớp Car thực thi giao diện này phải cài đặt các phương thức và thuộc tính đã khai báo trong giao diện Idrivable trên: public interface IDrivable { void KhoiDong(); void Dung(); bool DaKhoiDong { get; } } public classCar:IDrivable { public bool Started = false; public void KhoiDong() { Console.WriteLine("Xe ca khoi dong"); Started = true; 66 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  22. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện } public voidDung() { Console.WriteLine("Xe ca dung"); Started = false; } public boolDaKhoiDong { get{return Started;} } } public classTester { public static voidMain() { Car c = newCar(); c.KhoiDong(); Console.WriteLine("c.DaKhoiDong = " + c.DaKhoiDong); c.Dung(); Console.WriteLine("c.DaKhoiDong = " + c.DaKhoiDong); Console.ReadLine(); } } 3.3 Các lớp cơ sở trong .NET 3.3.1 System.Object Trong chương này , chúng ta đã xem xét về system.object. chúng ta biết rằng đó là lớp cơ sở chung mà mọi đối tượng khác được thừa kế và ta cũng xem xét về các phương thức thành viên chính của nó. tuy nhiên, ta chưa tìm hiểu kỹ về khả năng của tất cả các phương thức.trong phần này ta sẽ tìm hiểu các phương thức còn lại của system.object. Đầu tiên ta sẽ tìm hiểu tóm tắt của từng phương thức : Phương thức Truy xuất Mục đích public string ToString() Trả về 1 chuỗi đại diện cho đối tượng virtual trả về mã băm của đối tượng được thiết kế public int GetHashCode() cho phép ta tìm kiếm 1 cách hiệu quả các virtual thể hiện của đối tượng trong từ điền bool Equals(object obj) public so sánh đối tượng này với 1 đối tượng khác 67 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  23. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện virtual bool Equals(object objA, public object objB) so sánh 2 đối tượng static bool ReferenceEquals(object public so sánh các tham chiếu đối tượng để xem objA, object objB) static chúng có chỉ đến cùng đối tượng trả về 1 đối tượng dẫn xuất từ System.Type Type GetType() public mà đưa ra chi tiết kiểu dữ liệu Tạo ra một bản sao chép của đối tượng (nói cách khác, sao chép dữ liệu trong đối tượng nhưng không phải bất kỳ trường dữ liệu nào object MemberwiseClone() protected tham chiếu tới) Makes a shallow copy of the object (in other words, copies data in the object but not other objects any fields refer to) protected void Finalize() Hàm hủy ( estructor) virtual 4 trong những phương thức này khai báo là ảo (virtual) vì vậy ta có thể overload chúng. Các thành viên của system.object: ToString() - đây là cách trình bày chuỗi dễ dàng nhanh chóng và cơ bản. được dùng trong tình huống khi bạn muốn phác hoạ nhanh nội dung của một đối tượng,chẳng hạn để vá lỗi .nếu bạn muốn trình bày chuỗi phức tạp hơn bạn có thể dùng interface IFormatable. GetHashcode() - hữu ích nếu đối tượng được đặt trong cấu trúc dữ liệu map ( hay còn gọi là bảng băm hoặc từ điển).Nó được dùng bởi những lớp mà thao tác những cấu trúc này để quyết định nơi đặt đối tượng trong cấu trúc .nếu bạn định lớp của bạn được sử dụng như là khoá trong từ điển, thì bạn sẽ cần overload GetHashcode(). Equals() ( cả 2 phiên bản) và ReferenceEquals()- có những khác biệt tinh vi giữa 3 kiểu phương thức này, đi cùng với toán tử so sánh ,== ta sẽ xem xét sau. Finalize() - đây là 1 destructor và được gọi khi một đối tượng tham chiếu là rác được thu nhặt để dọn dẹp tài nguyên. thực sự thì finalize() không làm gì cả , bị bỏ qua bởi bộ gom rác, nhưng điều này không đúng với các overload . Ta nên overload chỉ khi cần thiết. Overload Finalize() trong C# không được làm tường minh (điều này hay gây ra lỗi biên dịch) bằng cách cung cấp destructor; destructor được chuyển bởi trình biên dịch thành phươngg thức Finalize(). 68 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  24. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện GetType() - phương thức này trả về thể hiện của một lớp được dẫn xuất từ System.type. đối tượng này có thể cung cấp một số thông tin mở rộng về lớp mà đối tượng là thành viên, bao gồm kiểu cơ sở, phương thức, thuộc tính MemberWiseClone() - đây chỉ là thành viên của System.Object mà ta không đi sâu trong cuốn sách này. nó đơn giản tạo một bản sao của đối tượng và trả về 1 tham khảo đến bản sao.lưu ý rằng bản sao được tạo như là bản sao bóng- nghĩa là nó sao chép tất cả các kiểu giá trị trong lớp.nếu lớp chứa đựng những tham khảo kèm theo thì chỉ có tham khảo được sao chép ,không phải là đối tượng. So sánh các đối tượng tham chiếu tương đương Ta thấy rằng trong C# có đến 4 kiểu so sánh tương đương ( gồm 3 phương thức so sánh và 1 toán tử = ). Ta sẽ tìm hiểu sự khác nhau giữa các loại so sánh này . ReferenceEquals() Tên của phương thức chính là ý nghĩa của nó . nó kiểm tra 2 tham chiếu liệu có quy vào cùng một thể hiện của một lớp: rằng 2 tham chiếu này chứa đựng cùng một địa chỉ trong bộ nhớ hay không.ReferenceEquals() luôn trả về true nếu được cung cấp 2 tham tham chiếu cùng quy vào một thể hiện đối tượng và sai nếu khác. tuy nhiên nó xem null là bằng null. SomeClass x, y; x = new SomeClass(); y = new SomeClass(); bool B1 = ReferenceEquals(null, null); // trả về true bool B2 = ReferenceEquals(null,x); // trả về false bool B3 = ReferenceEquals(x, y); // trả về false bởi x và y trỏ đến 2 đối tượng khác nhau. Phương thức ảo Equals() Là một phiên bản thể hiện của Equals() có thể được xem như là đối nghịch với ReferenceEquals(). Đúng là phương thức Equals() làm việc bằng cách so sánh những tham chiếu . phương thức này được cung cấp trong trường hợp ta muốn nạp chồng nó để so sánh giá trị của những thể hiện đối tượng.cụ thể ,nếu ta dự định thể hiện của lớp được sử dụng như khoá trong từ điển, ta cần nạp chồng phương thức để so sánh giá trị. nếu không thì tuỳ thuộc vào cách ta nạp chồng GetHashCode(), lớp từ điển chứa đựng những đối tượng của ta cũng không làm việc hoàn toàn, hoặc là làm việc thiếu khả năng.1 điểm mà ta chú ý khi nạp chồng Equals() là việc nạp chồng của ta sẽ không bao giờ tung ra biệt lệ (biệt lệ là lỗi sinh ra trong khi thực thi). Phương thức static Equals() Phương thức static Equals() thực sự thì cũng giống như phiên bản phương thức ảo Equals().điểm khác là phương thức static có thể đối phó khi đối tượng là null, và do 69 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  25. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện đó cung cấp 1 kiểu bảo vệ an toàn chống lại việc tung ra biệt lệ nếu đối tượng có thể là null.phương thức này đầu tiên sẽ kiểm tra khi overload thì tham chiếu được truyền có phải là null không.nếu cả hai là null, thì nó trả về true, ( vì null được xem là bằng null). nếu chỉ có một cái là null. nó trả về false.nếu cả hai tham chiếu thực sư quy đến một thứ,thì nó gọi phiên bản thể hiện ảo của Equals() nghĩa là khi bạn nạp chồng bản thể hiện của equals() ,kết quả cũng như bạn nạp chồng lên bản static. Toán tử so sánh (==) Toán tử so sánh có thể được xem như là bản trung gian giữa việc so sánh giá trị và việc so sánh tham chiếu. thường viết : bool b = (x == y); // x, y là đối tượng tham chiếu nghĩa là ta đang so sánh 2 tham chiếu. tuy nhiên,chấp nhận rằng có 1 vài lớp mà ý nghĩa của nó trực quan hơn nếu xem nó như là một giá trị.trong trường hợp đó, tốt hơn hết là nạp chồng toán tử so sánh thành so sánh giá trị. ví dụ như là những chuỗi mà microsoft nạp chồng toán tử này, bởi vì khi các nhà phát triển nghĩ đến việc so sánh chuỗi, họ luôn nghĩ rằng phải so sánh nội dung của chuỗi hơn là so sánh tham chiếu. Nếu ta muốn nạp chồng những hàm so sánh và Object.GetHashCode() cần lưu ý những hướng dẫn sau: không nên nạp chồng chỉ 1 Object.Equals() hay Object.GetHashCode() . nếu ta nạp chồng cái naỳ thì cũng phải nạp chồng cái kia.bởi vì việc thực thi từ điển đòi hỏi cả 2 phương thức hoạt động đồng nhất (nếu Obkect.Equals() làm việc so sánh giá trị, thì GetHashCode() cũng nên xây dựng mã dựa trên giá trị.) Nếu ta nạp chồng toán tử == , thì phải nạp chồng Object.Equals() ( và vì vậy cũng nạp chồng object.GetHashCode() ) bởi vì nếu == so sánh giá trị , thì object.Equals() cũng so sánh giá trị. So sánh các kiểu giá trị trong equal Khi so sánh kiểu giá trị trong equal , nguyên tắc cũng giống như kiểu tham chiếu: ReferenceEquals() được dùng để so sánh tham chiếu, Equals() được dùng cho so sánh giá trị, và toán tử so sánh được xem như trường hợp trung gian.tuy nhiên sự khác biệt ở đây là kiểu giá trị cần được bỏ vào hộp (boxed) để chuyển thành kiểu tham chiếu.do đó microdoft nạp chồng phương thức Equals() vào lớp System.ValueType để cung cấp ý nghĩa tương đương đến kiểu giá trị.nếu ta gọi sA.Equals(sB), sA, sB là thể hiện của một vài Struct, thì giá trị trả về sẽ là true hoặc false tuỳ theo liệu sA và sB chứa đựng cùng giá trị trong tất cà các trường. mặt khác, không có việc nạp chồng == bằng mặc định trong stuct riêng của ta.Viết ( sA==sB) trong bất kì biểu thức nào thì đều bị lỗi trừ khi ta cung cấp 1 overload == vào mã trong Struct. 1 điểm khác là ReferenceEquals() luôn trả về false khi ứng dụng vào kiểu giá trị, bởi vì để gọi phương thức, kiểu giá trị sẽ cần bỏ vào hộp thành đối tượng . thậm chí nếu ta viết: bool b = ReferenceEquals(v,v); // v là biến của một vài kiểu giá trị 70 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  26. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Ta sẽ có câu trả lời là false bởi vì v sẽ được đóng hộp riêng rẽ khi chuyển mỗi thông số,nghĩa là ta sẽ có sự tham chiếu khác nhau. Gọi referenceEquals() để so sánh kiểu giá trị không phải là 1 sự chọn lựa hay. Mặc dù nạp chồng mặc định của Equals() cung cấp bởi System.ValueType hầu như chắc chắn tương thích với số lượng lớn cấu trúc mà ta định nghĩa, ta có thể muốn nạp chồng nó lần nữa theo ý riêng của ta để cải thiện việc thực thi.nếu 1 kiểu giá trị chứa kiểu tham chiếu như 1 trường, ta có thể muốn nạp chồng Equals() để cung cấp ngữ nghĩa tương đương cho những trường này, mặc định thì Equals() đơn giản sẽ so sánh điạ chỉ của chúng. 3.3.2 Xử lý mảng Ngôn ngữ C# cung cấp cú pháp chuẩn cho việc khai báo những đối tượng Array. Tuy nhiên, cái thật sự được tạo ra là đối tượng của kiểu System.Array. Mảng trong ngôn ngữ C# kết hợp cú pháp khai báo mảng theo kiểu ngôn ngữ C và kết hợp với định nghĩa lớp do đó thể hiện của mảng có thể truy cập những phương thức và thuộc tính của System.Array. Một số các thuộc tính và phương thức của lớp System.Array Phương thức Truy xuất Mô tả BinarySearch() public Tìm kiếm một mảng một chiều đã sắp thứ tự. static Clear() public thiết lập các thành phần của mảng về 0 hay null. static Copy() public thực hiện sao chép một vùng của mảng vào static mảng khác. CreateInstance() public tạo một thể hiện mới cho mảng static IndexOf() public trả về chỉ mục của thể hiện đầu tiên static chứa giá trị trong mảng một chiều LastIndexOf() public trả về chỉ mục của thể hiện cuối cùng của giá trị static trong mảng một chiều Reverse() public đảo thứ tự của các thành phần trong mảng một static chiều Sort() public sắp xếp giá trị trong mảng một chiều. static Thuộc tính public trả về giá trị bool thể hiện mảng có kích thước cố IsFixedSize định hay không. Thuộc tính public trả về giá trị bool thể hiện mảng chỉ đọc hay IsReadOnly không 71 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  27. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện IsSynchronized public giá trị bool thể hiện mảng có hỗ trợ thread-safe Length public chiều dài của mảng Rank public chứa số chiều của mảng SyncRoot public chứa đối tượng dùng để đồng bộ truy cập trong mảng GetEnumerator() public trả về Ienumerator GetLength() public trả về kích thước của một chiều cố định trong mảng GetLowerBound() public trả về cận dưới của chiều xác định trong mảng GetUpperBound() public trả về cận trên của chiều xác định trong mảng Initialize() public Khởi tạo tất cả giá trị trong mảng kiểu giá trị bằng cách gọi bộ khởi dụng mặc định của từng giá trị. SetValue() public thiết lập giá trị cho một thành phần xác định trong mảng. 3.3.3 Xử lý chuỗi Qua chương 2, ta đã xem xét về chuỗi và thấy rằng từ khoá String trong C# thực sự tham khảo lớp cơ sở system.String. System.string là lớp rất linh hoạt và mạnh , không phải chỉ là lớp có liên quan đến chuỗi trong .NET. trong phần này ta sẽ xem lại những đặc tính của System.String, sau đó sử dụng chuỗi để ứng dụng trong môt số lớp .NET - cụ thể là lớp System.Text và namespace System.Text.RegularExpressions . Xây dựng chuỗi - nếu ta hay lặp lại việc thay đổi trên 1 chuỗi , ví dụ để định 1 độ dài cho chuỗi trước khi trình bày nó hoặc truyền nó đến vài phương thức hoặc phần mềm,lớp chuỗi có thể không đủ khả năng để làm.trong tình huống này, 1 lớp khác , System.Text.StringBuilder thích hợp hơn, bởi vì nó được thiết kế để làm trong các tình huống này. Các biểu thức định dạng - ta sẽ xem xét kĩ hơn những biểu thức định dạng sử dụng Console.Writeline(). những biểu thức định dạng này sử dụng vài interface. IFormatProvider và IFormattable,bằng việc sử dụng các interface này trong lớp riêng , ta có thể định nghĩa những chuỗi định dạng riêng để console.Writeline() và những lớp quen thuộc sẽ trình bày giá trị trong lớp của ta theo bất cứ cách nào mà ta chỉ định. Biểu thức chính quy ( regular expressions )- .NET cũng đưa ra một số lớp phức tạp mà đưọc dùng khi ta cần xác định hoặc trích ra chuỗi con thoả mãn 1 điều kiện phức tạp từ 1 chuỗi dài.ví dụ như cần tìm tất cả các lần xuất hiện của 1 kí tự hay 1 tập kí tự được lặp lại.hoặc cần tìm tất cả các từ bắt đầu với 's' và 72 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  28. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện chứa ít nhất 1 kí tự 'n'.mặc dù ta có thể viết phương thức để làm điều này chỉ bằng việc dùng lớp chuỗi ,nhựng nó rất cồng kềnh. thay vào đó , ta có thể dùng 1 vài lớp trong System.Text.RegularExpressions mà đưọc thiết kế để thực thi các quy trình này. System.String Trước khi kiểm tra các lớp chuỗi khác, ta sẽ xem lại nhanh những phương thức trong lớp chuỗi. System.String là lớp được thiết kế để lưu trữ chuỗi, bao gồm 1 số lớn các thao tác trên chuỗi.không chỉ thế mà còn bởi vì tầm quan trọng của kiểu dữ liệu này , C# có từ khoá riêng cho nó và kết hợp với cú pháp để tạo nên cách dễ dàng trong thao tác chuỗi. Ta có thể nối chuỗi : string message1 = "Hello"; message1 += ", There"; string message2 = message1 + "!"; Trích 1 phần chuỗi dùng chỉ mục: char char4 = message[4]; // trả về 'a', lưu ý rằng kí tự bắt đầu tính từ chỉ mục 0 Các phương thức khác (sơ lược): Phương thức Mục đích Compare so sánh nội dung của 2 chuỗi giống compare nhưng không kể đến ngôn ngữ bản địa hoặc văn hoá CompareOrdinal (as compare but doesn't take culture into account) định dạng một chuỗi chứa 1 giá trị khác và chỉ định cách mỗi giá trị Format nên được định dạng. IndexOf vị trí xuất hiện đầu tiên của 1 chuỗi con hoặc kí tự trong chuỗi IndexOfAny vị trí xuất hiện đầu tiên của bất kì 1 hoặc 1 tập kí tự trong chuỗi LastIndexOf giống indexof , nhưng tìm lần xuất hiện cuối cùng LastIndexOfAny giống indexofAny , nhưng tìm lần xuất hiện cuối cùng canh phải chuỗi điền chuỗi bằng cách thêm 1 kí tự được chỉ định lặp PadLeft lại vào đầu chuỗi canh trái chuỗi điền chuỗi bằng cách thêm 1 kí tự được chỉ định lặp PadRigth lại vào cuối chuỗi thay thế kí tự hay chuỗi con trong chuỗi với 1 kí tự hoặc chuỗi con Replace khác 73 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  29. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện chia chuỗi thành 2 mảng chuỗi con ,ngắt bởi sự xuất hiện của một kí Split tự nào đó Substring trả về chuỗi con bắt đầu ở một vị trí chỉ định trong chuỗi. ToLower chuyển chuỗi thành chữ thuờng ToUpper chuyển chuỗi thành chữ in Trim bỏ khoảng trắng ở đầu và cuối chuỗi Xây dựng chuỗi Chuỗi là 1 lớp mạnh với nhiều phương thức hữu ích , tuy nhiên chuỗi gặp khó khăn trong việc lặp lại sự thay đổi đến chuỗi ban đầu.nó thực sự là kiểu dữ liệu không biến đổi, nghĩa là mỗi lần ta khởi động 1 đối tượng chuỗi, thì đối tượng chuỗi đó không bao giờ được thay đổi.những phương thức hoặc toán tử mà cập nhật nội dung của chuỗi thực sự là tạo ra một chuỗi mới , sao chép chuỗi cũ vào nếu cần thiết. Ví dụ: string greetingText = "Hello from all the guys at Wrox Press. "; greetingText += "We do hope you enjoy this book as much as we enjoyed writing it."; Đầu tiên lớp system.String được tạo và khởi tạo giá trị “Hello from all the people at Wrox Press.” chú ý khoảng trắng sau sau dấu chấm. Khi điều này xảy ra, thời gian chạy .NET sẽ định vị đủ bộ nhớ trong chuỗi để chứa đoạn kí tự này (39 kí tự ) và tạo ra 1 biến greetingText để chuyển đến 1 thể hiện chuỗi. Ở dòng tiếp theo, khi ta thêm kí tự vào. Ta sẽ tạo ra một chuỗi mới với kích thước đủ để lưu trữ cả hai đoạn (103 kí tự ).đoạn gốc "Hello from all the people at Wrox Press. ", sẽ được sao chép vào chuỗi mới với đoạn thêm "We do hope you enjoy this book as much as we enjoyed writing it. " sau đó địa chỉ trong biến greetingText được cập nhật, vì vậy biến sẽ trỏ đúng đến đối tượng chuỗi mới.chuỗi cũ không còn được tham chiếu - không có biến nào truy vào nó- và vì vậy nó sẽ được bộ thu gom rác gỡ bỏ. Giả sử ta muốn mã hoá chuỗi bằng cách thay đổi từng kí tự với 1 kí tự ASCII. điều này sẽ trả vế chuỗi : "Ifmmp gspn bmm uif hvst bu Xspy Qsftt. Xf ep ipqf zpv fokpz uijt cppl bt nvdi bt xf fokpzfe xsjujoh ju." có nhiều cách làm điều này nhưng cách đơn giản nhất là dùng phương thức String.replace() mà thay thế chuỗi con này bằng chuỗi con khác, dùng Replace() ta viết đoạn mã mã hoá như sau : string greetingText = "Hello from all the guys at Wrox Press. "; greetingText += "We do hope you enjoy this book as much as we enjoyed writing it."; for(int i = (int)'z'; i>=(int)'a' ; i ) { char old = (char)i; 74 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  30. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện char new = (char)(i+1); greetingText = greetingText.Replace(old, new); } for(int i = (int)'Z'; i>=(int)'A' ; i ) { char old = (char)i; char new = (char)(i+1); greetingText = greetingText.Replace(old, new); } Console.WriteLine("Encoded:\n" + greetingText); Vậy cần bao nhiêu vùng nhớ làm điều này? Replace() làm việc theo cách thông minh, để mở rộng nó sẽ không tạo ra một chuỗi mới trừ khi nó thực sự phải thay đổi chuỗi cũ.chuỗi gốc có 23 kí tự chữ thường khác nhau và 3 kí tự in khác nhau,Replace() sẽ định vị 1 chuỗi mới tổng cộng 26 lần,mỗi chuỗi mới lưu trữ 103 kí tự.nghĩa là kết quả quy trình mã hoá sẽ là đối tượng chuỗi có khả năng lưu trữ kết hợp tổng cộng 2678 kí tự bây giờ đang nằm trong heap để được thu dọn.Rõ ràng nếu ta sử dụng chuỗi để làm điều này, ứng dụng của ta sẽ chạy không tốt. Để giải quyết Microsoft cung cấp lớp System.Text.StringBuilder . lớp System.Text.StringBuilder không mạnh như lớp chuỗi tính theo thuật ngữ là số phương thức nó có .tiến trình mà ta có thể làm trên Stringbuilder được giới hạn thành thay thế hoặc mở rộng hoặc bỏ đoạn từ chuỗi. Tuy nhiên nó làm việc theo cách hiệu quả hơn. Bất cứ nơi nào ta xây dựng chuỗi, chỉ đủ vùng nhớ được định vị để giữ chuỗi,Stringbuidler sẽ định vị vùng nhớ nhiều hơn cần.ta có thể chỉ định vùng nhớ được định vị , nếu không , thì số mặc định tuy thuộc vào kích cỡ của chuỗi mà StringBuilder được khởi động cùng .nó có 2 thuộc tính chính: - Length - độ dài của chuỗi thực sự chứa - Capacity - độ daì chuỗi mà nó chỉ định đủ vùng nhớ để lưu trữ. Bất kì cập nhật nào đến chuỗi sẽ được làm trong khối vùng nhớ này,như là viết thêm chuỗi con hoặc thay thế các kí tự riêng trong chuỗi rất tốt.Việc gỡ bỏ hoặc chèn chuỗi con vẫn không tốt, bởi vì những phần theo sau chuỗi phải bị di chuyển.chỉ nếu ta thực thi vài thao tác mà tăng dung lượng chuỗi sẽ tạo ra vùng nhớ mới cần được định vị và toàn bộ chuỗi được chứa có thể được di chuyển.vào lúc viết ,Microsoft không cho biết làm cách nào để dung lượng thêm được thêm vào, nhưng từ kinh nghiệm cho thấy StringBuilder xuất hiện với dung lượng gấp đôi của nó nếu nó thăm dò thấy dung lương bị vượt quá và không có giá trị mới của dung lương được thiết đặt rõ ràng. Ví dụ , nếu ta sử dụng đối tượng StringBuilder để tạo ra chuỗi chào đón gốc, ta viết: 75 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  31. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện StringBuilder greetingBuilder = new StringBuilder("Hello from all the guys at Wrox Press. ", 150); greetingBuilder.Append("We do hope you enjoy this book as much as we enjoyed writing it"); Trong mã này . ta thiết lập dung lương khởi tạo là 150 cho StringBuilder. nó luôn là ý tưởng hay để thiết lập dung lượng mà bao phủ độ dài lớn nhất của chuỗi , để bảo đảm String builder không cần tái định vị bởi vì dung lượng của nó lớn .vì vậy ta có thể thiết lập 1 số int cho dung lượng, mặc dù nếu ta cố định vị vượt quá 2 tỷ ký tự hệ thống sẽ cảnh báo. Khi mã trên thực thi , đầu tiên ta tạo ra 1 đối tượng StringBuilder khởi tạo như sau: Khi gọi phương thức append() ,đoạn còn lại đuợc đặt trong khoảng trống, không cần định vị thêm vùng nhớ.tuy nhiên khả năng thực sự của StringBuilder chỉ thấy rõ khi lặp lại việc thay thế đoạn. ví dụ, nếu ta cố mã hoá đoạn văn bản theo cách trên ,ta có thể mã hoá toàn bộ mà không cần định vị thêm bất kì vùng nhớ nào nữa : StringBuilder greetingBuilder = new StringBuilder("Hello from all the guys at Wrox Press. ", 150); greetingBuilder.Append("We do hope you enjoy this book as much as we enjoyed writing it"); for(int i = (int)'z'; i>=(int)'a' ; i ) { char old = (char)i; char new = (char)(i+1); greetingBuilder = greetingBuilder.Replace(old, new); } for(int i = (int)'Z'; i>=(int)'A' ; i ) { char old = (char)i; char new = (char)(i+1); greetingBuilder = greetingBuilder.Replace(old, new); } Console.WriteLine("Encoded:\n" + greetingBuilder.ToString()); Đoạn mã này dùng phương thức StringBuilder.Replace() mà giống như String.Replace() nhưng không có sao chép chuỗi trong lúc làm.tổng vùng nhớ được định vị để giữ chuỗi trong đoạn mã trên là 150 cho người xây dựng, cũng như vùng 76 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  32. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện nhớ chỉ định trong suốt việc thao tác chuỗi thi hành bên trong trong câu lệnh cuối Console.Writeline() thông thường , ta sử dụng StringBuilder để thi hành bất kì thao tác của chuỗi, và String để lưu trữ hoặc trình bày kết quả cuối cùng. Các thành viên của StringBuilder Chúng ta đã minh hoạ 1 hàm dựng của StringBuilder, mà lấy thông số khởi tạo là chuỗi và dung lượng.cũng có một vài cái khác , trong số chúng, ta có thể cung cấp chỉ 1 chuỗi : StringBuilder sb = new StringBuilder("Hello"); hoặc chỉ cung cấp dung luợng , chuỗi trống: StringBuilder sb = new StringBuilder(20); Ngoại trừ 2 thuộc tính trên ta còn có thuộc tính chỉ đọc MaxCapacity chỉ định giới hạn mà 1 thể hiện của StringBuilder cho phép .mặc định , số này là int.MaxValue ( khoảng 2 tỷ). // dung lượng khởi tạo là 100 nhưng lớn nhất là 500 // do đó StringBuilder không thể phát triển hơn 500 kí tự được. // chúng sẽ tung biệt lệ nếu ta cố làm điều đó. StringBuilder sb = new StringBuilder("Hello", 100, 500); StringBuilder sb = new StringBuilder(100, 500); Ta có thể thiết lập dung lượng ở bất cứ đâu .nếu dung lượng thiêt lập nhỏ hơn chuỗi hiện hành hoặc lớn hơn độ dài lớn nhất thì biệt lệ sẽ được tung ra. StringBuilder sb = new StringBuilder("Hello"); sb.Capacity = 100; Phương thức chính StringBuilder bao gồm : - Append() thêm 1 chuỗi vào chuỗi đương thời AppendFormat(): thêm 1 chuỗi mà được trình bày từ các chỉ định định dạng - Insert() :chèn 1 chuỗi con vào chuỗi đương thời - Remove() :bỏ các kí tự từ chuỗi đương thời - Replace() :thay thế kí tự này bằng kí tự khác hoặc chuỗi con này bằng chuỗi con khác trong chuỗi đương thời - ToString() :trả về chuỗi đương thời ép thành 1 đối tượng System.Object ( nạp chồn từ System.Object) Vào lúc viết, không thể ép kiểu (tường minh hay không tưòng minh) từ StringBuilder sang String. nếu ta muốn xuất nội dung của StringBuilder như là 1 String , cách duy nhất để làm là dùng phương thức Tostring() Định dạng Chuỗi 77 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  33. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Nếu ta muốn những lớp mà ta viết thân thiện với người sử dụng , thì chúng cần để trình bày chuỗi theo bất cứ cách nào mà người sử dụng muốn dùng.Thời gian chạy .NET định nghĩa 1 cách chuẩn để làm : dùng 1 interface hoặc IFormatable.biểu diễn làm thế nào để thêm những đặc tính quan trọng đến lớp của ta và những cấu trúc là chủ đề của phần này. Ta thường chỉ định định dạng của biến được trình bày khi gọi Console.Writeline. do đó ta sẽ lấy phương thức này làm ví dụ, mặc dù hầu hết những điều ta sắp học có thể ứng dụng trong bất cứ tình huống nào mà ta muốn định dạng chuỗi Ví dụ: double d = 13.45; int i = 45; Console.WriteLine("The double is {0,10:E} and the int contains {1}", d, i); Chuỗi định dạng tự nó bao gồm hầu hết văn bản được trình bày,nhưng bất cứ ở đâu có biến được định dạng , chỉ mục của nó trong danh sách thông số trong dấu ngoặc.có thể là thông tin khác bên trong dấu ngoặc về việc định dạng của mục đó: số kí tự được giữ bởi sự trình bày của mục có thể xuất hiện, thông tin này sẽ có dấu phảy đứng trước. Một số âm chỉ định rằng mục đó đưọc canh trái, trong khi 1 số dương chỉ định mục đó được canh phải. nếu mục đó giữ nhiều kí tự hơn được yêu cầu, nó vẫn xuất hiện đầy đủ. Một chỉ định định dạng cũng có thể xuất hiện.điều này sẽ được đặt trước bởi dấu hai chấm và chỉ định cách ta muốn mục được định dạng. ví dụ ta muốn định dạng số như kiểu tiền tệ hoặc trình bày theo ký hiệu khoa học ? Đặc tả Áp dụng đến Ý nghĩa Ví dụ C numeric types locale-specific $4834.50 (USA)£4834.50 monetary value (UK) D integer types only general integer 4834 E numeric types scientific notation 4.834E+003 F numeric types fixed point decimal 4384.50 G numeric types general number 4384.5 N numeric types usual locale specific 4,384.50 (UK/USA)4 384,50 format for numbers (continental Europe) P numeric types Percentage notation 432,000.00% X integer types only hexadecimal format 1120 (NB. If you want to display 0x1120, you'd need to write out the 0x separately) 78 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  34. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Làm thế nào chuỗi được định dạng Xem ví dụ sau: Console.WriteLine("The double is {0,10:E} and the int contains {1}", d, i); Thực vậy, Console.Writeline() chỉ việc kiểm soát toàn bộ tập thông số bằng cách chuyển đến phương thức static, String.Format()- cũng phương thức này được gọi nếu ta muốn định dạng các giá trị trong chuỗi theo các mục đích khác , như là trình bày trong textbox.Để làm rõ những gì mã nguồn thực sự thực thi phương thức này là gì ,ta sẽ xem xét việc thi hành phương thức oveload với 3 thông số của phương thức Writeline() như sau : // giống như thực thi Console.Writeline() public void WriteLine(string format, object arg0, object arg1) { Console.WriteLine(string.Format(format, arg0, arg1)); } Overload 1 thông số của phương thức này, đơn giản viết nội dung của chuỗi được trình bày, không làm bất cứ định dạng gì trên nó. String.format() cần xây dựng chuỗi cuối bằng cách thay thế mỗi phần đặc tả định dạng bằng việc trình bày chuỗi thích hợp của đối tượng tương ứng.tuy nhiên như đã biết , chính xác trong tình huống này ta cần thể hiện Stringbuilder hơn là thể hiện string. trong ví dụ ta đang xét, 1 thể hiện StringBuilder sẽ được tạo ra và khởi tạo với phần đầu là chuỗi “The double is”. Phương thức StringBuilder.AppendFormat() sẽ được gọi, truyền phần đặc tả định dạng dầu tiên , {0,10:E} ,và đối tượng kết hợp kiểu double này sẽ được thêm vào chuỗi đang được xây dựng, quy trình này sẽ tiếp tục với việc gọi nhiều lần StringBuilder.Append() và StringBuilder.AppendFormat() cho đến khi toàn bộ chuỗi được định dạng đã xong. Bởi vì StringBuilder.AppendFormat() sẽ cần minh họa cách định dạng thực sự đối tượng. điều đầu tiên sẽ là thăm dò đối tượng để xem liệu nó có thực thi 1 interface IFormatable ( trong namespace System ) hay chưa.ta có thể thử ép kiểu 1 đối tượng thành 1 interface và xem coi ép kiểu được không. nếu kiểm tra thất bại, thì AppendFormat() đơn giản gọi phương thức toString() của đối tượng, (do phương thức này được tất cả các đối tượng cùng thừa kế từ System.Object hoặc do nạp chồng). IFormattable định nghĩa giống như một phương thức, mà cũng được goị ToString().tuy nhiên phương thức này lấy 2 thông số, đối lập với phiên bản System.Object , mà không lấy bất kì thông số nào. đây là định nghĩa cho IFormattable: interface IFormattable { 79 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  35. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện string ToString(string format, IFormatProvider formatProvider); } Thông số đầu tiên mà hàm overload của Tostring() này lấy là chuỗi mà đặc tả định dạng được yêu cầu.nói cách khác nó chỉ định phần chuỗi xuất hiện trong { } so với chuỗi gốc được truyền đến Console.WriteLine() hay String.Format(). Ví dụ: Console.WriteLine("The double is {0,10:E} and the int contains {1}", d, i); Khi tính thông số đầu tiên, {0,10:E}, hàm sẽ gọi biến double, d, và thông số đầu tiên đưọc truyền đến nó sẽ là E. những gì StringBuilder.AppendFormat() sẽ truyền ở đây là bất cứ đoạn văn bản nào xuất hiện sau dấu hai chấm trong phần đặc tả định dạng từ chuỗi gốc. Ta không quan tâm về thông số thứ 2 của Tostring(). Nó là 1 tham chiếu đến đối tượng mà thực thi interface IFormatProvider.Interface này gửi thông tin mà Tostring() có thể cần khi định dạng đối tượng. Quay trở lại ví dụ trên, mục đầu tiên ta muốn định dạng là double với phần đặc tả định dạng là E.như đã đề cập, phương thức StringBuilder.AppendFormat() sẽ thiết lập double thi hành IFormattable, và do đó sẽ gọi hàm overload Tostring() 2 thông số, truyền vào nó chuỗi E cho thông số đầu tiên và null cho thông số thứ hai.bây giờ nó tuỳ thuộc vào sự thi hành của phương thức này mà sẽ trả về 1 chuỗi trình bày kiểu double theo định dạng. Nếu cần StringBuilder.AppendFormat() sẽ lựa chọn điền vào chuỗi trả về với khoảng trắng, để đủ 10 kí tự trong phần đặc tả định dạng của chuỗi trong trường hợp này. Đối tượng kế tiếp được định dạng là kiểu int, mà không yêu cầu định dạng cụ thể.vì vậy StringBuilder.AppendFormat() sẽ truyền vào tham chiếu null cho định dạng chuỗi này. Toàn bộ quy trình có thể được tóm tắt như sau: Ví dụ FormattableVector: Trong ví dụ này phần đặc tả định dạng mà ta sẽ hổ trợ là: N - được phiên dịch như là yêu cầu cung cấp 1 số được biết như là Norm của Vector.là tổng bình phương của các thành phần của nó và luôn được trình bày giữa dấu || , ví dụ như || 34.5 || VE - được phiên dịch như là yêu cầu trình bày mỗi thành phần trong đặc tả định dạng.như đặc tả E đối với 1 số double chỉ định (2.3E+01, 4.5E+02, 1.0E+00). IJK - được phiên dịch như là yêu cầu trình bày vector dưới dạng 23i + 450i + 1k, việc trình bày mặc định sẽ có dạng Vector( 23,450,1.0) 80 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  36. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Để cho đơn giản ta sẽ không thi hành bất kì tuỳ chọn để trình bày Vector theo kết hợp giữa IJK và định dạng khoa học.tuy nhiên ta sẽ có thể kiểm tra đặc tả theo cách không phân biệt chữ hoa và chữ thường , để cho phép ijk thay IJK. lưu ý rằng hoàn toàn tuỳ thuộc vào chuỗi ta sử dụng để chỉ định đặc tả định dạng. Đầu tiên ta sẽ khai báo Vector thi hành Iformatable, sao đó thêm vào phương thức overload tostring() 2 thông số như mã code sau: struct Vector : IFormattable { public double x, y, z; public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return ToString(); string formatUpper = format.ToUpper(); switch (formatUpper) { case "N": return "|| " + Norm().ToString() + " ||"; case "VE": return String.Format("( {0:E}, {1:E}, {2:E} )", x, y, z); case "IJK": StringBuilder sb = new StringBuilder(x.ToString(), 30); sb.Append(" i + "); sb.Append(y.ToString()); sb.Append(" j + "); sb.Append(z.ToString()); sb.Append(" k"); return sb.ToString(); default: return ToString(); } } Đó là tất cả những gì ta phải làm. lưu ý cần kiểm tra định dạng null trước khi gọi bất cứ phương thức nào.trong ví dụ phần đặc tả VE , ta cần mỗi thành phần đưọc định dạng theo cú pháp khoa học,vì thế ta dùng String.Format()để làm.tất cả các trường x,y,z là double.Trong trường hợp định dạng IJK, có vài chuỗi con được thêm 81 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  37. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện vào chuỗi, vì thế ta dùng đối tượng StringBuilder để làm. Để hoàn chỉnh ta sẽ xây dựng lại phương thức overload Tostring() không thông số: public override string ToString() { return "( " + x + " , " + y + " , " + z + " )"; } cuối cùng ta thêm vào phương thức Norm() mà tính bình phương ( Norm) của Vector: public double Norm() { return x*x + y*y + z*z; } Bây giờ ta sẽ thử đoạn mã trên theo vài cách : static void Main() { Vector v1 = new Vector(1,32,5); Vector v2 = new Vector(845.4, 54.3, -7.8); Console.WriteLine("\nIn IJK format,\nv1 is {0,30:IJK}\nv2 is {1,30:IJK}", v1, v2); Console.WriteLine("\nIn default format,\nv1 is {0,30}\nv2 is {1,30}", v1, v2); Console.WriteLine("\nIn VE format\nv1 is {0,30:VE}\nv2 is {1,30:VE}", v1, v2); Console.WriteLine("\nNorms are:\nv1 is {0,20:N}\nv2 is {1,20:N}", v1, v2); } Kết quả trả về là : FormattableVector In IJK format, v1 is 1 i + 32 j + 5 k v2 is 845.4 i + 54.3 j + -7.8 k In default format, v1 is ( 1 , 32 , 5 ) v2 is ( 845.4 , 54.3 , -7.8 ) In VE format v1 is ( 1.000000E+000, 3.200000E+001, 5.000000E+000 ) 82 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  38. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện v2 is ( 8.454000E+002, 5.430000E+001, -7.800000E+000 ) Norm là : v1 is || 1050 || v2 is || 717710.49 || 3.3.4 Biểu thức chính quy ( Regular Expression) Biểu thức chính quy là 1 dạng kĩ thuật nhỏ mà hữu ích trong 1 vùng rộng lớp của chương trình, mặc dù không được nhiều nhà phát triển biết đến. nó có thể hiểu như là 1 ngôn ngữ nhỏ dùng cho mục đích: để tìm chuỗi con trong biểu thức chuỗi lớn.nó không phải là một kĩ thuật mới, xuất phát từ môi trường UNIX, đuợc dùng với PERL. Microsoft cho nó vào Windows,và cho đến giờ thì nó hầu như được sử dụng với những ngôn ngữ kịch bản.Biểu thức chính quy được hổ trợ bởi một số lớp .NET trong namespace: System.Text.RegularExpressions. Giới thiệu về Biểu thức chính quy. Ngôn ngữ biểu thức chính quy là ngôn ngữ được thiết kế đặc biệt cho việc xử lí chuỗi chứa đựng 2 đặc tính: - 1 tập mã escape cho việc xác định kiểu của các kí tự . ta quen với việc dùng kí tự * để trình bày chuỗi con bất kì trong biểu thức OS . biểu thức chính quy dùng nhiều chuỗi như thế để trình bày các mục như là 'bất kì 1 kí tự' ,'1 từ ngắt ','1 kí tự tuỳ chọn', - 1 hệ thống cho việc nhóm những phần chuỗi con, và trả về kết quả trong suốt thao tác tìm. Dùng biểu thức chính quy , có thể biểu diễn những thao tác ở cấp cao và phức tạp trên chuỗi.ví dụ : - Xác định tất cả các từ lặp lại trong chuỗi , chuyển “The computer books books” thành “The computer books”. - Chuyển tất cả các từ theo title case, như là chuyển "this is a Title" thàh "This Is A Title". - Chuyển những từ dài hơn 3 kí tự thành title case , ví dụ chuyển "this is a Title" to “This is a Title” - Bảo đảm các câu được viết hoa - Phân cách những phần tử của URL ( ví dụ cho chi tiết giao thức tên máy, tên file , ) mặc dù có thể sử dụng các phương thức System.String và System.Text.StringBuilder để làm các việc trên nhưng nếu dùng biểu thức chính quy thì mã có thể được giảm xuống còn vài dòng.ta khởi tạo 1 đối tượng System.Text.RegularExpressions.RegEx , truyền vào nó chuỗi được xử lí, và 1 biểu thức chính quy ( 1 chuỗi chứa đựng các lệnh trong ngôn ngữ biểu thức chính quy ). 1 chuỗi biểu thức chính quy nhìn giống 1 chuỗi bình thường nhưng có thêm 1 số chuỗi 83 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  39. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện hoặc kí tự khác làm cho nó có ý nghĩa đặc biệt hơn. Ví dụ chuỗi \b chỉ định việc bắt đầu hay kết thúc 1 từ, vì thế nếu ta muốn chỉ định tìm kí tự th bắt đầu 1 từ, ta có thể tìm theo biểu thức chính quy ,\b th. Nếu muốn tìm tất cả sự xuất hiện của th ở cuối từ ta viết th\b. Tuy nhiên, biểu thức chính quy có thể phức tạp hơn thế, ví dụ điều kiện để lưu trữ phần kí tự mà tìm thấy bởi thao tác tìm kiếm. 1 ví dụ khác giả sử như ta muốn chuyển 1 số diện thoại UK từ trong nước sang định dạng quốc tế. trong UK, định dạng ví dụ như là 01233 345532 hoặc (01233 345532) mà theo quốc tế sẽ là +44 12330345532, nói cách khác số 0 đầu sẽ được thay bằng +44 và các dấu ngặc phải được bỏ. Thao tác này không quá phức tạp, nhưng cũng hơi rắc rối nếu ta dùng lớp chuỗi để làm ( nghĩa là dùng các phương thức trong lớp chuỗi). ngôn ngữ biểu thức chính quy sẽ cho phép ta xây dựng 1 chuỗi ngắn mà sẽ được phiên dịch để đạt được yêu cầu trên. Ví dụ RegularExpressionsPlayaround Trong phần cuối của phần này ta sẽ phát triển 1 ví dụ ngắn thể hiện vài đặc tính của biểu thức chính quy và cách dùng biểu thức chính quy trong C# bằng biệc trình bày và biểu diễn kết quả của việc tìm kiếm.ta dùng 1 đoạn văn bản trong cuốn sách XML cho ví dụ của ta : string Text = @"XML has made a major impact in almost every aspect of software development Designed as an open, extensible, self-describing language, it has become the standard for data and document delivery on the web. The panoply of XML-related technologies continues to develop at breakneck speed, to enable validation, navigation, transformation, linking, querying, description, and messaging of data."; Ta xem đoạn văn bản này là chuỗi input.giả sử ta muốn tìm tất cả các lần xuất hiện của ion. ta sẽ viết như sau: string Pattern = "ion"; MatchCollection Matches = Regex.Matches(Text, Pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); foreach (Match NextMatch in Matches) { Console.WriteLine(NextMatch.Index); } Trong ví dụ này ta dùng phương thức tĩnh Matches() của lớp Regex trong namespace System.Text.RegularExpressions. Phương thức này có thông số là text, pattern, và tập cờ từ cấu trúc liệt kê RegexOptions.trong trường hợp này ta chỉ định tìm kiếm không phân biệt chữ hoa - thường. và cờ ExplicitCapture, cập nhật cách mà 84 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  40. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện match được thu thập ta sẽ thấy tại sao hàm Matches() trả về 1 tham chiếu đến đối tượng MatchCollection. 1 match là 1 thuật ngữ kĩ thuật cho những kết quả của việc tìm 1 thể hiện của pattern trong biểu thức. Được trình bày bởi lớp System.Text.RegularExpressions.Match do đó ta sẽ trả về 1 MatchCollection chứa tất cả các match, mỗi cái đưọc trình bày bởi một đối tượng Match.trong đoạn mã trên, ta đơn giản lặp trên tập thu được và dùng thuộc tính index của lớp Match, mà trả về chỉ mục trong đoạn input nơi mà match được tìm thấy.khi chạy nó sẽ tìm ra 4 match. Đến bây giờ ta vẫn chưa thấy gì thật sự mới ở đây .tuy nhiên sức mạnh của biểu thức chính quy nằm ở chuỗi pattern. lý do là chuỗi pattern không chỉ chứa văn bản kí tự.nó còn có thể chứa metacharacters, là những kí tự đặc biệt mà dùng trong lệnh, những chuỗi escape, làm việc giống như các chuỗi escape của C# là những kí tự đưọc bắt đầu bằng dấu \, và cũng có ý nghĩa đặc biệt. Ví dụ , ta muốn tìm từ bắt đầu vớ n. ta có thể dùng chuỗi \b, mà chỉ định 1 ranh giới từ ta đơn giản viết: string Pattern = @"\bn"; MatchCollection Matches = Regex.Matches(Text, Pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); Chú ý kí tự @ đứng trước chuỗi.nghĩa là ta muốn \b được truyền vào biểu thức chính quy .NET vào lúc chạy- ta không muốn dấu \ bị chặn bởi trình biên dịch của C# mà xem nó như là chuỗi escape của nó.nếu ta muốn tìm từ kết thúc với ion, ta viết: string Pattern = @"ion\b"; Nếu ta muốn tìm tất cả những từ bắt đầu bằng n và kết thúc với ion, rõ ràng ta cần 1 pattern bắt đầu với \bn và kết thúc với ion\b vậy chính giữa sẽ là gì ? Ta cần 1 cái gì đó để cho ứng dụng biết giữa n và ion có thể là bất cứ từ gì không phải khoảng trắng. ta viết : string Pattern = @"\bn\S*ion\b"; \S chỉ định bất kì kí tự nào không phải khoảng trắng , * được gọi là quantifier. nghĩa là kí tự đứng trước có thể lặp nhiều lần , kể cả 0 lần. chuỗi \s* nghĩa là “bất kì kí tự nào không phải là khoảng trắng”. Bảng sau thể hiên một số kí tự hoặc chuỗi escape mà ta có thể dùng Ý nghĩa Ví dụ Examples that this will match ^ Bắt đầu của chuổi nhập ^B B, nhưng chỉ nếu kí tự đầu tiên trong chuỗi $ Kết thúc của chuỗi nhập X$ X, nhưng chỉ nếu kí tự cuối 85 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  41. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện cùng trong chuỗi . Bất kì kí tự nào ngoại trừ kí tự i.ation isation, ization xuống dòng(\n) * Kí tự trước có thể được lặp lại 0 ra*t rt, rat, raat, raaat, and so on hoặc nhiều lần + Kí tự trước có thể được lặp lại 1 ra+t rat, raat, raaat and so on, hoặc nhiều lần (but not rt) ? Kí tự trước có thể được lặp lại 0 ra?t rt and rat only hoặc 1 lần \s Bất kì kí tự khoảng trắng \sa [space]a, \ta, \na (\t and \n có ý nghĩa giống như trong C#) \S Bất kì kí tự nào không phải là \SF aF, rF, cF, but not \tf khoảng trắng \b Từ biên ion\b any word ending in ion \B bất kì vị trí nào không phải là từ \BX\B bất kì kí tự X ở giữa của 1 biên từ Nếu ta thực sự muốn tìm 1 kí tự metacharacters , ta có thể làm bằng cách thêm vào kí tự tương ứng với dấu \ ví dụ . ( dấu chấm ) nghĩa là bất kì kí tự đơn nào ngoại trừ 1 kí tự xuống dòng, trong khi \ . nghĩa là 1 dấu chấm. ta có thể yêu cầu 1 match chứa đựng 1 kí tự thay thế bằng cách bỏ chúng trong dấu ngoặc. Ví dụ [1|c] nghĩa là 1 kí tự mà có thể là 1 hoặc c. nếu muốn tìm bất kì từ nào là map or man , ta dùng chuỗi ma[n|p].ta có thể kí hiệu 1 vùng ví dụ [1-z] để chỉ định kí tự thường , [A-E] chỉ định bất kì chữ hoa nào giữa A và E hoặc [0-9] trình bày 1 số đơn. Nếu ta muốn tìm 1 số integer ( 1 chuỗi chỉ chứa các số từ 0đến 9 ) ta viết [0-9]+ . dấu + chỉ định phải rằng phả có ít nhất 1 kí số. Trình bày kết quả Trong phần này ta sẽ xét ví dụ RegularExpressionsPlayaround để ta thiết lập 1 vài biểu thức chính quy và trình bày kết quả để thấy cách mà biểu thức chính quy làm việc tâm điểm là phương thức WriteMatches(), mà trình bày tất cả các match từ MatchCollection theo định dạng chi tiết hơn. Trong mỗi match, nó trình bày chỉ mục nơi mà match được tìm thấy trong chuỗi nhập,chuỗi của match bao gồm match cộng thêm 19 kí tự bao quanh nó trong chuỗi nhập - 5 kí tự đứng trước và 5 kí tự đứng sau (nhỏ hơn 5 kí tự nếu match xuất hiện trong 5 kí tự của phần đầu và kết thúc của đoạn nhập). Ví dụ match trên từ messaging mà xuất hiện gần cuối của chuỗi nhập được 86 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  42. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện đánh dấu sẽ trình bày “and messaging of d” (5 kí tự trước và sau match) nhưng 1 match trên từ cuối data sẽ trình bày “g of data.” ( chỉ 1 kí tự sau match). Bởi vì sao đó là cuối chuỗi 1 chuỗi dài hơn để ta thấy rõ nơi biểu thức chính quy định vị match: static void WriteMatches(string text, MatchCollection matches) { Console.WriteLine("Original text was: \n\n" + text + "\n"); Console.WriteLine("No. of matches: " + matches.Count); foreach (Match nextMatch in matches) { int Index = nextMatch.Index; string result = nextMatch.ToString(); int charsBefore = (Index < 5) ? Index : 5; int fromEnd = text.Length - Index - result.Length; int charsAfter = (fromEnd < 5) ? fromEnd : 5; int charsToDisplay = charsBefore + charsAfter + result.Length; Console.WriteLine("Index: {0}, \tString: {1}, \t{2}", Index, result, text.Substring(Index - charsBefore, charsToDisplay)); } } Phần lớn của quy trình trong phương thức này minh hoạ số kí tự đượctrình bày trong chuỗi con dài hơn mà nó có thể trình bày không quan tâm đến đầu hay cuối chuỗi.lưu ý ta sử dụng 1 thuộc tính khác của đối tượng Match , Value, chứa chuỗi xác định trong Match.RegularExpressionsPlayaround chứa 1 số phương thức với tên như là Find1, Find2 mà biểu diễn việc tìm kiếm dựa trên ví dụ trong phần này. Ví dụ find2 tìm bất kì chuỗi chứa n vào lúc đầu của 1 từ : static void Find2() { string text = @"XML has made a major impact in almost every aspect of software development. Designed as an open, extensible, self-describing language, it has become the standard for data and document delivery on the web. The panoply of XML-related technologies continues to develop at breakneck speed, to enable validation, navigation, transformation, linking, querying, description, and messaging of data."; string pattern = @"\bn"; MatchCollection matches = Regex.Matches(text, pattern, 87 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  43. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện RegexOptions.IgnoreCase); WriteMatches(text, matches); } Cùng với phương thức này là một phương thức main() mà ta có thể chỉnh sửa đề chọn 1 trong những phương thức Find ( ): static void Main() { Find1(); Console.ReadLine(); } 3.3.5 Nhóm các đối tượng Chúng ta đã khảo sát 1 số lớp cơ sở của .NET có cấu trúc dữ liệu trong đó một số đối tượng được nhóm với nhau.cấu trúc đơn giản mà ta đã học là mảng, đây là 1 thể hiện của lớp System.Array . mảng có lợi điểm là ta có thể truy nhập từng phần tử thông qua chỉ mục.tuy nhiên khuyết điểm của nó là ta phải khởi tạo kích thước của nó. không thể thêm ,chèn hoặc bỏ 1 phần tử sau đó.và phải có một chỉ mục số để truy nhập vào 1 phần tử.điều này không tiện lắm ví dụ như khi ta làm việc với 1 bản ghi nhân viên và muốn tìm bản ghi theo tên nhân viên. .NET có một số cấu trúc dữ liệu khác hổ trợ cho công việc này.ngoài ra còn có 1 số inteface , mà các lớp có thể khai báo chúng hổ trợ tất cả chức năng của một kiểu cụ thể cấu trúc dữ liệu. chúng ta sẽ xem xét 3 cấu trúc sau (các lớp cấu trúc dữ liệu này nằm trong namespace System.Collection): -Array lists - Collection - Dictionary ( hay maps) a. Array lists Array list giống như mảng, ngoại trừ nó có khả năng phát triển.được đại diện bởi lớp System.Collection.Arraylist. Lớp Arraylist cũng có một một vài điểm tương tự với lớp StringBuilder mà ta tìm hiểu trưóc đây.như StringBuilder cấp phát đủ chỗ trống trong vùng nhớ để lưu trữ 1 số kí tự, và cho phép ta thao tác các kí tự trong chỗ trống đó, Arraylist cấp đủ vùng nhớ để lưu trữ 1 số các tham chiếu đối tượng. ta có thể thao tác trên những tham chiếu đối tượng này.nếu ta thử thêm một đối tượng đến Arraylist hơn dung lượng cho phép của nó, thì nó sẽ tự động tăng dung lượng bằng cách cấp phát thêm vùng nhớ mới lớn đủ để giữ gấp 2 lần số phần tử của dung lượng hiện thời.Ta có thể khởi tạo 1 danh sách bằng cách chỉ định dung lượng ta muốn. Các lớp cấu trúc dữ liệu này nằm trong namespace System.Collection. Ví dụ , ta tạo ra một danh sách Vectors: 88 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  44. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện ArrayList vectors = new ArrayList(20); Nếu ta không chỉ định kích cỡ ban đầu , mặc định sẽ là 16: ArrayList vectors = new ArrayList(); // kích cỡ là 16 Ta có thể thêm phần tử bằng cách dùng phương thức Add(): vectors.Add(new Vector(2,2,2)); vectors.Add(new Vector(3,5,6)); Arraylist xem tất cả các phần tử của nó như là các tham chiếu đối tượng nghĩa là ta có thể lưu trữ bất kì đối tượng nào mà ta muốn trong 1 Arraylist. nhưng khi truy nhập đến đối tượng, ta sẽ cần ép kiểu chúng trở lại kiểu dữ liệu tương đương: Vector element1 = (Vector)vectors[1]; Ví dụ này cũng chỉ ra Arraylist định nghĩa 1 indexer, để ta có thể truy nhập những phần tử của nó với cấu trúc như mảng. ta cũng có thể chèn các phần tử vào array list: vectors.Insert(1, new Vector(3,2,2)); // chèn vào vị trí 1 Đây là phương thức nạp chồng có ích khi ta muốn chèn tất cả các phần tử trong 1 collection vào arraylist ta có thể bỏ 1 phần tử : vectors.RemoveAt(1); // bỏ đối tượng ở vị trí 1 Ta cũng có thể cung cấp 1 đối tượng tham chiếu đến 1 phương thức khác, Remove().nhưng làm điều này sẽ mất nhiều thời gian hơn vì arraylist phải quét qua toàn bộ mảng để tìm đối tượng. Lưu ý rằng việc thêm và bỏ 1 phần tử sẽ làm cho tất cả các phần tử theo sau phải bị thay đổi tương ứng trong bộ nhớ, thậm chí nếu cần thì có thể tái định vị toàn bộ Arraylist. Ta có thể cập nhật hoặc đọc dung lượng qua thuộc tính : vectors.Capacity = 30; Tuy nhiên việc thay đổi dung lương đó sẽ làm cho toàn bộ Arraylist được tái định vị đến một khối bộ nhớ mới với dung lượng đưọc yêu cầu. Để biết số phần tử thực sự trong arraylist ta dùng thuộc tính Count: int nVectors = vectors.Count; 1 arraylist có thể thực sự hữu ích nếu ta cần xây dựng 1 mảng đối tuợng mà ta không biết kích cỡ của mảng sẽ là bao nhiêu. trong trường hợp đó, ta có thể xây dựng ' mảng' trong Arraylist, sau đó sao chép Arraylist trở lại mảng khi ta hoàn thành xong nếu ta thực sự cần dữ liệu như là 1 mảng ( ví dụ nếu mảng được truyền đến 1 phương thức xem mảng là 1 thông số). mối quan hệ giữa Arraylist và Array theo 1 cách nào đó giống như mối quan hệ giữa StringBUilder và String. Không như lớp StringBuilder, không có phương thức đơn nào để làm việc chuyển đổi từ 1 arraylist sang array. Ta 89 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  45. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện phải dùng 1 vòng lặp để sao chép thủ công trở lại.tuy nhiên ta chỉ phải sao chép tham chiếu chứ không phải đối tượng: // vectors is an ArrayList instance being used to store Vector instances Vector [] vectorsArray = new Vector[vectors.Count]; for (int i=0 ; i< vectors.Count ; i++) vectorsArray[i] = (Vector)vectors [i]; b. Collections Ý tưởng của Collection là nó trình bày một tập các đối tượng mà ta có thể truy xuất bằng việc bước qua từng phần tử. cụ thể là 1 tập đối tượng mà ta có thể truy nhập sử dụng vòng lặp foreach. nói cách khác ,khi viết 1 thứ gì đó như: foreach (string nextMessage in messageSet) { DoSomething(nextMessage); } Ta xem biến messageSet là 1 collection . khả năng để dùng vòng lặp foreach là mục đích chính của collection tiếp theo ta tìm hiểu chi tiết collection là gì và thi hành 1 collection riêng bằng việc chuyển ví dụ Vector mà ta đã phát triển. Collection là gì ? 1 đối tượng là 1 collection nếu nó có thể cung cấp 1 tham chiếu đến một đối tượng có liên quan, được biết đến như là enumarator, mà có thể duyệt qua từng mục trong collection. đặc biệt hơn, 1 collection phải thi hành 1 interface System.Collections.IEnumerable. IEnumerable định nghĩa chỉ một phương thức như sau: interface IEnumerable { IEnumerator GetEnumerator(); } Mục đích của GetEnumarator() là để trả về đối tuợng enumarator. khi ta tập họp những đoạn mã trên đối tượng enumarator được mong đợi để thi hành 1 interface , System.Collections.IEnumerator. Ngoài ra còn có một interface khác , Icollection , đưọc dẫn xuất từ IEnumerable. những collection phức tạp hơn sẽ thi hành interface này.bên cạnh GetEnumerator(), nó thi hành một thuộc tính trả về trực tiếp số phần tử trong collection. nó cũng có đặc tính hổ trợ việc sao chép collection đến 1 mảng và có thể cung cấp thông tin đặc tả nếu đó là một luồng an toàn.tuy nhiên trong phần này ta chỉ xem xét interface IEnumerable. IEnumarator có cấu trúc sau: 90 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  46. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } IEnumarator làm việc như sau: đối tuợng thực thi nên được kết hợp với 1 collection cụ thể. khi đối tượng này được khởi động lần đầu tiên,nó chưa trỏ đến bất kì 1 phần tử nào trong collection, và ta phải gọi MoveNext(), mà sẽ di chuyển enumarator để nó chuyển đến phần tử đầu tiên trong collection. ta có thể nhận phần tử này với thuộc tính Current.Current trả về 1 tham chiếu đối tượng , vì thế ta sẽ ép kiểu nó về kiểu đối tượng mà ta muốn tìm trong Collection.ta có thể làm bất cứ điều gì ta muốn với đối tượng đó sau đó di chuyển đến mục tiếp theo trong collection bằng cách gọi MoveNext() lần nữa.ta lập lại cho đến khi hết mục trong collection- khi current trả về null.nếu muốn ta có thể quay trở về vị trí đầu trong collection bằng cách gọi Reset(). lưu ý rằng Reset() thực sự trả về trước khi bắt đầu collection , vì thế nếu muốn di chuyển đến phần tử đầu tiên ta phải gọi MoveNext(). 1 collection là 1 kiểu cơ bản của nhóm đối tượng.bởi vì nó không cho phép ta thêm hoặc bỏ mục trong nhóm.tất cả ta có thể làm là nhận các mục theo 1 thứ tự được quyết định bởi collection.và kiểm tra chúng.thậm chí ta không thể thay thế hoặc cập nhật mục vì thuộc tính current là chỉ đọc.hầu như cách dùng thường nhất của collection là cho ta sự thuận tiện trong cú pháp của lặp foreach. Mảng cũng là 1 collection,nhưng lệnh foreach làm việc tốt hơn mảng. Ta có thể xem vòng lặp foreach trong C# là cú pháp ngắn trong việc viết: { IEnumerator enumerator = MessageSet.GetEnumerator(); string nextMessage; enumerator.MoveNext(); while ( (nextMessage = enumerator.Current) != null) { DoSomething(nextMessage); // NB. We only have read access // toNextMessage enumerator.MoveNext(); } } 91 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  47. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện 1 khía cạnh quan trọng của collection là bộ đếm được trả về như là 1 đối tượng riêng biệt.lý do là để cho phép khả năng có nhiều hơn 1 bộ đếm có thể áp dụng đồng thời trong cùng collection. Thêm collection hỗ trợ cấu trúc Vector Trong lần cuối cùng ta nói về Vector , một thể hiện của Vector chứa đựng 3 phần, x,y,z và bởi vì ta đã định nghĩa 1 bộ chỉ mục ở chương 3, nó có thể đuợc xem 1 thể hiện Vector là 1 mảng , để ta có thể truy nhập vào phần x bằng cách viết someVector[0], phần y bằng cách viết someVecor[1] và z là someVector[2]. Bây giờ ta sẽ mở rộng cấu trúc vector, dự án VectorAsCollection mà cũng có thể quét qua các phần của 1 vector bằng cách viết : foreach (double component in someVector) Console.WriteLine("Component is " + component); Nhiệm vụ đầu tiên của ta là biểu thị vector như là 1 collection bằng việc cho nó thực thi interface IEnumerable, ta bắt đầu bằng việc cập nhật khai báo của cấu trúc vector: struct Vector : IFormattable, IEnumerable { public double x, y, z; Bây giờ ta thi hành interface IEnumerable : public IEnumerator GetEnumerator() { return new VectorEnumerator(this); } Việc thi hành GetEnumerator() hầu như là đơn giản, nhưng nó tuỳ thuộc trên sự tồn tại của 1 lớp mới, VectorEnumerator,mà ta cần định nghĩa. vì VectorEnumerator không phải là 1 lớp mà bất kì đoạn mã bên ngoài có thể thấy trực tiếp, ta khai báo nó là lớp private bên trong cấu trúc Vector. việc định nghĩa nó như sau: private class VectorEnumerator : IEnumerator { Vector theVector; // Vector object that this enumerato refers to int location; // which element of theVector the enumerator is // currently referring to public VectorEnumerator(Vector theVector) { this.theVector = theVector; location = -1; 92 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  48. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện } public bool MoveNext() { ++location; return (location > 2) ? false : true; } public object Current { get { if (location 2) throw new InvalidOperationException( "The enumerator is either before the first element or " + "after the last element of the Vector"); return theVector[(uint)location]; } } public void Reset() { location = -1; } } Khi được yêu cầu như 1 bộ đếm, VectorEnumerator thi hành interface IEnumerator. nó cũng chứa 2 trường thành viên, theVector,1 tham chiếu đến Vector ( collection) mà bộ đếm kết hợp, location, 1 số nguyên mà chỉ định nơi trong collection mà bộ đếm tham chiếu đến. Cách làm việc là xem location như là chỉ mục và thi hành enumerator để truy nhập Vector như mảng.khi truy nhập vector như mảng giá trị chỉ mục là 0,1,2 - tamở rộng bằng cách dùng -1 như là giá trị chỉ định bộ đếm trước khi bắt đầu collection,và 3 để chỉ nó đến cuối của collection. vì vậy , việc khởi tạo của trường nay là -1 trong hàm dựng VectorEnumerator: public VectorEnumerator(Vector theVector) { this.theVector = theVector; 93 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  49. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện location = -1; } Lưu ý rằng hàm dựng cũng lấy 1 tham chiếu đến thể hiện của Vector mà chúng ta định đếm - điều này được cung cấp trong phương thức Vector.GetEnumerator : public IEnumerator GetEnumerator() { return new VectorEnumerator(this); } 3.3.6 Dictionaries Từ điển trình bày 1 cấu trúc dữ liệu rất phức tạp mà cho phép ta truy nhập vào các phần tử dựa trên 1 khoá nào đó, mà có thể là kiểu dữ liệu bất kì.ta hay gọi là bảng ánh xạ hay bảng băm.Từ điển được dùng khi ta muốn lưu trữ dữ liệu như mảng nhưng muốn dùng 1 kiểu dữ liệu nào đó thay cho kiểu dữ liệu số làm chỉ mục.nó cũng cho phép ta thêm hoặc bỏ các mục , hơi giống danh sách mảng tuy nhiên nó không phải dịch chuyển các mục phía sau trong bộ nhớ. Ta minh họa việc dùng từ điển trong ví dụ sau: MortimerPhonesEmployees. Trong ví dụ này công ty điện thoại có vài phần mềm xử lí chi tiết nhân viên .ta cần 1 cấu trúc dữ liệu -hơi giống mảng- mà chứa dữ liệu của nhân viên. Ta giả sử rằng mỗi nhân viên trong công ty được xác định bởi I nhân viên, là tập kí tự như B342 và được lưu trữ thành đối tượng EmployyeeI . Chi tiết của nhân viên được lưu trữ thành đối tượng Employee ata, ví dụ chỉ chứa I ,tên, lương của nhân viên. Giả sử ta có EmployeeI : EmployeeID id = new EmployeeID("W435") và ta có 1 biến gọi là employees, mà ta có thể xem như 1 mảng đối tượng Employee ata.thực sự , nó không phải là mảng - nó là từ điển và bởi vì nó là từ điển nên ta có thể lấy chi tiết của 1 nhân viên thông qua I đuợc khai báo trên: EmployeeData theEmployee = employees[id]; // lưu ý rằng ID không phải kiểu số- nó là 1 thể hiện của EmployeeID Đó là sức mạnh của từ điển.Ta có thể dùng kiểu dữ liệu bất kì làm chỉ mục , lúc này ta gọi nó là khoá chứ không phải là chỉ mục nữa.khi ta cung cấp 1 khoá truy nhập vào 1 phần tử ( như I trên ), nó sẽ xử lí trên giá trị của khoá và trả về 1 số nguyên tuỳ thuộc vào khoá, và được dùng để truy nhập vào 'mảng' để lấy dữ liệu. Từ điển trong .NET Trong .NET , từ điển cơ bản được trình bày qua lớp Hasthable, mà cách làm việc cũng giống như từ điển thực, ngoại trừ nó xem khoá và mục có kiểu object.nghĩa là 1 bảng băm có thể lưu trữ bất kì cấu trúc dữ liệu nào ta muốn. ta có thể tự định nghĩa 1 lớp từ điển riêng cụ thể hơn.Microsoft cung cấp 1 lớp cơ sở trừu tượng, ictionaryBase,cung cấp những chức năng cơ bản của từ điển ,mà ta có thể 94 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  50. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện dẫn xuất đến lớp mà ta muốn tạo.nếu khoá là chuỗi ta có thể dùng lớp System.Collections.Specialized.StringDictionary thay cho Hasthable. khi tạo một Hasthable ta có thể chỉ định kích thước khởi tạo của nó: Hasthable employees = new Hasthable(53); Ở đây ta chọn số 53 bởi vì thuật toán bên trong được dùng cho từ điển làm việc hiệu quả hơn nếu kích thước của nó là 1 số nguyên tố. Thêm đối tượng vào từ điển ta dùng phương thức Add(), có 2 thông số kiểu object : thông số đầu là khoá, thứ hai là 1 tham chiếu đến dữ liệu. ví dụ: EmployeeID id; EmployeeData data; // khởi tạo id và dữ liệu. // giả sử employees là 1 thể hiện của bảng băm //mà chứa đựng các tham chiếu EmployeeData employees.Add(id, data); để nhận dữ liệu ta cung cấp khoá cho nó: EmployeeData data = employees[id]; để bỏ 1 mục ta cung cấp khoá và gọi : employees.Remove(id); Để đếm số mục trong từ điển ta dùng thuộc tính Count: int nEmployees = employees.Count; Việc lưu trữ trong từ điển không theo phải theo kiểu từ trên xuống, nghĩa là ta không thể tìm thấy 1 khối lớn dữ liệu ở phần đầu của cấu trúc và 1 khối rỗng ở phần cuối. biểu đồ sau minh hoạ cho việc lưu trữ trong từ điển, các phần không đánh dấu là rỗng: Cách từ điển làm việc 95 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  51. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Hasthable (hay bất kì lớp từ điển nào khác) sử dụng vài thuật toán để thực hiện việc đặt mỗi đối tượng dựa trên khoá. có 2 giai đoạn, và phần mã cho từng giai đoạn phải được cung cấp bởi lớp khoá.nếu sử dụng lớp do Microsoft viết, mà dùng làm khoá (như chuỗi), thì không có vấn đề gì (Microsoft đã viết sẵn rồi). Nhưng nếu lớp khoá do ta viết thì ta phải tự viết phần thuật toán này. 1 phần của thuật toán thực thi bởi lớp khoá gọi là băm (vì vậy có thuật ngữ bảng băm) và lớp Hasthable tìm 1 nơi cụ thể cho thuật toán băm, nhìn vào phương thức Gethashcode() trong đối tượng của ta, mà thừa kế từ System.Object() nếu ta nạp chồng GetHashCode(). Cách nó làm việc là Gethashcode() trả vế 1 số nguyên.bằng cách nào đó nó dùng giá trị của khoá để sinh ra 1 số nguyên.Hasthable sẽ lấy số nguyên này và làm các việc xử lí khác trên nó mà liên quan đến việc tính toán toán học phức tạp,và trả về chỉ mục của mục đưọc lưu trữ tương ứng với khóa trong từ điển.ta không đi sâu vào thuật toán này nhưng ta sẽ tìm hiểu tại sao nó liên quan đến số nguyên tố và tại sao dung lượng bảng băm nên là số nguyên tố. Có một số yêu cầu nghiêm ngặt khi ta nạp chồng GetHashCode(). Những yêu cầu này nghe có vẻ trừu tượng nhưng qua ví dụ MortimerPhonesEmployees ta sẽ thấy rằng không quá khó để viết lớp khoá thỏa mãn những đòi hỏi sau: - Nó phải nhanh ( bởi vì việc đặt và lấy các mục trong 1 từ điển được coi là nhanh) - Nó phải được đồng nhất - nếu ta cho 2 khoá cùng giá trị thì chúng phải cho cùng giá trị trong băm. - Cho những giá trị khả dĩ trong khoảng giá trị của 1 số kiểu int. Lí do của điều kiện cuối là : điều gì sẽ xảy ra nếu ta lấy 2 mục trong từ điển mà khi băm cả hai đều cho cùng 1 chỉ mục? Nếu điều này xảy ra, lớp từ điển sẽ phải bắt đầu tìm kiếm vị trí trống có giá trị gần nhất để lưu trữ mục thứ hai. Xung đột giữa các khóa cũng gia tăng khi từ điển đầy,vì thế cách tốt nhất là bảo đảm dung lượng lớn hơn số phần tử thực sự trong nó.vì lí do này mà Hasthable tự định vị lại kích cỡ của nó để tăng dung lượng trước khi nó đầy.tỷ lệ của bảng mà đầy gọi là load. ta có thể thiết lập giá trị lớn nhất mà ta muốn load đến trước khi Hasthable tái định vị theo hàm dựng Hasthable khác : // dung lượng =50, Max Load = 0.5 Hasthable employees = new Hasthable(50, 0.5); Max load càng nhỏ bảng băm làm việc càng hiệu quả nhưng càng cần nhiều vùng nhớ.khi bảng băm tái định vị để tăng dung lượng , nó luôn chọn 1 số nguyên tố làm dung lượng mới. 1 điểm quan trọng khác là thuật toán băm phải đồng nhất.nếu 2 đối tượng chứa những gì ta coi như là dữ liệu trùng, thì chúng phải cho cùng 1 giá trị băm, và điều này dẫn đến 1 giới hạn quan trọng trên cách nạp chồng phương thức 96 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông
  52. Bài giảng Ngôn ngữ lập trình ứng dụng – Ngành Truyền thông đa phương tiện Equals() và Gethashcode() của System.Object. cách mà Hasthable quyết định 2 khoá a và b là bằng nhau là nó gọi a.equals(b). nghĩa là ta phải chắc rằng điều sau luôn đúng : Nếu a.equals(b) là đúng thì a.gethashcode() và b.gethashcode() phải luôn trả về cùng mã băm. Nếu ta cố ý nạp chồng những phương thức này để những câu lệnh trên không đúng thì bảng băm sẽ không làm việc bình thường. ví dụ như ta đặt 1 đối tượng vào bảng băm nhưng không nhận lại được nó hay nhận lại được nhưng không đúng mục. trong system.object điều kiện này đúng , vì Equals() đơn giản so sánh 2 tham chiếu và gethashcode() thực sự trả về 1 băm dựa trên địa chỉ của đối tượng.nghĩa là bảng băm dựa trên 1 khoá mà không nạp chồng những phương thức này sẽ làm việc đúng.tuy nhiên ,vấn đề với cách làm này là những khóa coi là bằng chỉ nếu chúng là cùng đối tượng.nghĩa là khi đặt 1 đối tượng vào từ điển ta phải nối tham chiếu đến khóa.ta không thể khởi tạo 1 khóa khác sau đó mà có cùng giá trị,vì cùng giá trị được định nghĩa theo nghĩa là cùng một thực thể. nghĩa là nếu ta không nạp chồng bản object của Equals() và Gethashcode(), lớp của ta sẽ không thuận lợi để dùng trong bảng băm. tốt hơn nếu thi hành gethashcode() sinh ra 1 băm dựa trên giá trị của khoá hơn là điạ chỉ của nó trong bộ nhớ.do đó ta sẽ cần nạp chồng gethashcode() va equals() trong bất kì lớp nào mà ta muốn nó được sử dụng như khoá System.String có những phương thức nạp chồng tương đương, Equals() được nạp chồng để cung cấp giá trị so sánh, và gethashcode() được nạp chồng để trả về 1 băm dựa trên giá trị của chuỗi.vì lí do này thuận lợi để dùng chuỗi như là khoá trong từ điển. Ví dụ Đây là chương trình thiết lập từ điển nhân viên.chương trình khởi tạo từ điển , thêm vài nhân viên và sau đó mời người dùng gõ vào Id nhân viên. mỗi khi gõ , chương trình dùng I để trỏ vào tử điển và nhận chi tiết nhân ivên. quy trình lặp lại cho đến khi người dùng gõ X : MortimerPhonesEmployees Enter employee ID (format:A999, X to exit)> B001 Employee: B001: Mortimer £100,000.00 Enter employee ID (format:A999, X to exit)> W234 Employee: W234: Arabel Jones £10,000.00 Enter employee ID (format:A999, X to exit)> X Các lớp của chương trình : class EmployeeID { private readonly char prefix; 97 Bộ môn Truyền thông đa phương tiện – Trường Đại học Công nghệ thông tin và Truyền thông