GT C Sharp cơ bản – Bài 27 : Chương 26: System.Array và các lớp sưu tập
Chương 26: System.Array và
các lớp sưu tập
Tác giả: Sưu tầm
Khái quát
Về nội dung, chương này sẽ mang đến một khái quát về các lớp sẵn có. Sau đó sẽ nói rõ chúng theo lớp và làm một số ví dụ về các giao diện và phương thức được đòi hỏi để có chức năng nhất định
Sắp xếp và tìm kiếm
Các lớp sưu tập Frameworks cung cấp một số hỗ trợ hữu ích cho việc sắp xếp và tìm kiếm, với các phương thức dựng sẵn để sắp xếp và tìm kiếm nhị phân. Lớp Array cung cấp chức năng tương tự nhưng là các phương thức tĩnh hơn là các hàm thành phần.
Sắp xếp một mảng các số nguyên dễ dàng như sau:
using System;
class Test {
public static void
int[] arr = {5, 1, 10, 33, 100, 4};
Array.Sort(arr);
foreach (int v in arr)
Console.WriteLine(“Element: {0}”, v);
}
}
Mã trên có kết quả sau:
Element 1
Element 4
Element 5
Element 10
Element 33
Element 100
Điều này là tiện lợi cho các kiểu dựng sẵn, nhưng nó không làm việc với các lớp hay cấu trúc bởi vì thường trình sắp xếp không biết cách sắp thứ tự chúng.
Cài đặt giao diện IComparable
Frameworks có vài cách rất tốt cho một lớp hay cấu trúc để xác định cách sắp thứ tự các thể hiện của một lớp hay cấu trúc. Đơn giản nhất, đối tượng cài đặt giao diện IComparable:
using System;
public class Employee: IComparable {
public Employee(string name, int id) {
this.name = name;
this.id = id;
}
int IComparable.CompareTo(object obj) {
Employee emp2 = (Employee) obj;
if (this.id > emp2.id)
return(1);
if (this.id < emp2.id)
return(-1);
else
return(0);
}
public override string ToString() {
return(String.Format(“{0}:{1}”, name, id));
}
string name;
int id;
}
class Test {
public static void
Employee[] arr = new Employee[4];
arr[0] = new Employee(“George”, 1);
arr[1] = new Employee(“Fred”, 2);
arr[2] = new Employee(“Tom”, 4);
arr[3] = new Employee(“Bob”, 3);
Array.Sort(arr);
foreach (Employee emp in arr)
Console.WriteLine(“Employee: {0}”, emp);
}
}
Chương trình này cho kết quả sau:
Employee: George:1
Employee: Fred:2
Employee: Bob:3
Employee: Tom:4
Cài đặt này chỉ cho phép một thứ tự sắp xếp; lớp trên có thể định nghĩa sắp xếp dựa trên ID của nhân viên hay dựa trên tên, nhưng không có cách nào cho phép người sử dụng chọn thứ tự sắp xếp chúng theo ưu thích.
Sử dụng IComparer
Các nhà thiết kế Frameworks đã cung cấp khả năng để định nghĩa nhiều thứ tự sắp xếp. Mỗi thứ tự sắp xếp được trình bày thông qua giao diện IComparer, và giao diện thích hợp được chuyển đến cho hàm sắp xếp hoặc tìm kiếm.
Tuy nhiên, giao diện IComparer không được cài đặt trong Employee, bởi vì mỗi lớp chỉ có thể cài đặt một giao diện mỗi lần, mà điều này chỉ cho phép một thứ tự sắp xếp đơn lẻ. Một lớp tách biệt được cần cho mỗi thứ tự sắp xếp, với một lớp cài đặt IComparer. Lớp sẽ là rất đơn giản, vì tất cả những gì nó sẽ làm được cài đặt trong hàm Compare():
using System;
using System.Collections;
class Employee {
public string name;
}
class SortByNameClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, mp2.name));
}
}
Thành phần Compare() cần hai tham số kiểu object. Vì lớp chỉ nên được sử dụng cho việc sắp xếp các nhân viên, nên các tham số object được chuyển thành Employee. Hàm Compare() tạo ra các chuỗi mà sau đó sử dụng để so sánh.
Lớp Employee sau đó được sửa lại như sau. Các lớp thứ tự sắp xếp được đặt bên trong lớp Employee như các lớp lồng nhau:
using System;
using System.Collections;
public class Employee: IComparable {
public Employee(string name, int id) {
this.name = name;
this.id = id;
}
int IComparable.CompareTo(object obj) {
Employee emp2 = (Employee) obj;
if (this.id > emp2.id)
return(1);
if (this.id < emp2.id)
return(-1);
else
return(0);
}
public override string ToString() {
return(name + “:” + id);
}
public class SortByNameClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
}
public class SortByIdClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(((IComparable) emp1).CompareTo(obj2));
}
}
string name;
int id;
}
class Test {
public static void
Employee[] arr = new Employee[4];
arr[0] = new Employee(“George”, 1);
arr[1] = new Employee(“Fred”, 2);
arr[2] = new Employee(“Tom”, 4);
arr[3] = new Employee(“Bob”, 3);
Array.Sort(arr, (IComparer) new Employee.SortByNameClass());
// employees is now sorted by name
foreach (Employee emp in arr)
Console.WriteLine(“Employee: {0}”, emp);
Array.Sort(arr, (IComparer) new Employee.SortByIdClass());
// employees is now sorted by id
foreach (Employee emp in arr)
Console.WriteLine(“Employee: {0}”, emp);
ArrayList arrList = new ArrayList();
arrList.Add(arr[0]);
arrList.Add(arr[1]);
arrList.Add(arr[2]);
arrList.Add(arr[3]);
arrList.Sort((IComparer) new Employee.SortByNameClass());
foreach (Employee emp in arrList)
Console.WriteLine(“Employee: {0}”, emp);
arrList.Sort(); // default is by id
foreach (Employee emp in arrList)
Console.WriteLine(“Employee: {0}”, emp);
}
}
Người sử dụng bây giờ có thể chỉ định một thứ tự sắp xếp và chuyển đổi giữa các thứ tự sắp xếp khác nhau theo mong muốn. Ví dụ này biểu diễn cách các hàm tương tự làm việc bằng cách sử dụng lớp ArrayList, mặc dù Sort() là một hàm thành phần chứ không phải là một hàm tĩnh.
IComparer như một thuộc tính
Việc sắp xếp với lớp Employee là vẫn còn một chút cồng kềnh, vì người sử dụng phải tạo một thể hiện của lớp thứ tự thích hợp và sau đó chuyển đổi nó thành IComparer. Nó có thể được đơn giản hơn nữa bằng cách sử dụng các thuộc tính tĩnh để thực hiện điều này cho người sử dụng:
using System;
using System.Collections;
public class Employee: IComparable {
public Employee(string name, int id) {
this.name = name;
this.id = id;
}
int IComparable.CompareTo(object obj) {
Employee emp2 = (Employee) obj;
if (this.id > emp2.id)
return(1);
if (this.id < emp2.id)
return(-1);
else
return(0);
}
public static IComparer SortByName {
get {
return((IComparer) new SortByNameClass());
}
}
public static IComparer SortById {
get {
return((IComparer) new SortByIdClass());
}
}
public override string ToString() {
return(name + “:” + id);
}
class SortByNameClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
}
class SortByIdClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(((IComparable) emp1).CompareTo(obj2));
}
}
string name;
int id;
}
class Test {
public static void
Employee[] arr = new Employee[4];
arr[0] = new Employee(“George”, 1);
arr[1] = new Employee(“Fred”, 2);
arr[2] = new Employee(“Tom”, 4);
arr[3] = new Employee(“Bob”, 3);
Array.Sort(arr, Employee.SortByName);
// employees is now sorted by name
foreach (Employee emp in arr)
Console.WriteLine(“Employee: {0}”, emp); Array.Sort(arr, Employee.SortById);
// employees is now sorted by id
foreach (Employee emp in arr)
Console.WriteLine(“Employee: {0}”, emp);
ArrayList arrList = new ArrayList();
arrList.Add(arr[0]);
arrList.Add(arr[1]);
arrList.Add(arr[2]);
arrList.Add(arr[3]);
arrList.Sort(Employee.SortByName);
foreach (Employee emp in arrList)
Console.WriteLine(“Employee: {0}”, emp);
arrList.Sort(); // default is by id
foreach (Employee emp in arrList)
Console.WriteLine(“Employee: {0}”, emp);
}
}
Các thuộc tính tĩnh SortByName và SortById tạo một thể hiện của lớp sắp xếp thích hợp, chuyển đổi nó thành IComparer, và trả ra cho người sử dụng. Điều này đơn giản hoá mô hình người sử dụng hơn một chút; các thuộc tính SortByName và SortById trả ra một IComparer, nên hiển nhiên chúng có thể được sử dụng để sắp xếp, và tất cả mọi việc người sử dụng phải làm là xác định thuộc tính sắp xếp thích hợp cho tham số IComparer.
Chồng toán tử quan hệ
Nếu một lớp có một thứ tự được trình bày trong IComparable, nó cũng có thể chồng các toán tử quan hệ khác. Như với == và !=, các toán tử khác phải được khai báo theo cặp, với < và > là một cặp, và >= và <= là một cặp:
using System;
public class Employee: IComparable {
public Employee(string name, int id) {
this.name = name;
this.id = id;
}
int IComparable.CompareTo(object obj) {
Employee emp2 = (Employee) obj;
if (this.id > emp2.id)
return(1);
if (this.id < emp2.id)
return(-1);
else
return(0);
}
public static bool operator <( Employee emp1, Employee emp2) {
IComparable icomp = (IComparable) emp1;
return(icomp.CompareTo (emp2) < 0);
}
public static bool operator >( Employee emp1, Employee emp2) {
IComparable icomp = (IComparable) emp1;
return(icomp.CompareTo (emp2) > 0);
}
public static bool operator <=( Employee emp1, Employee emp2) {
IComparable icomp = (IComparable) emp1;
return(icomp.CompareTo (emp2) <= 0);
}
public static bool operator >=( Employee emp1, Employee emp2) {
IComparable icomp = (IComparable) emp1;
return(icomp.CompareTo (emp2) >= 0);
}
public override string ToString() {
return(name + “:” + id);
}
string name;
int id;
}
class Test {
public static void
Employee george = new Employee(“George”, 1);
Employee fred = new Employee(“Fred”, 2);
Employee tom = new Employee(“Tom”, 4);
Employee bob = new Employee(“Bob”, 3);
Console.WriteLine(“George < Fred: {0}”, g orge < fred);
Console.WriteLine(“Tom >= Bob: {0}”, tom >= bob);
}
}
Ví dụ này cho ra kết quả sau:
George < Fred: false
Tom >= Bob: true
Sử dụng bảng băm cao cấp
Trong một số tình huống, có lẽ sẽ có mong ước để định nghĩa nhiều hơn một mã băm cho một đối tượng xác định. Ví dụ, nó có thể được sử dụng để cho phép một Employee được tìm kiếm dựa trên mã ID của nhân viên hay tên của nhân viên đó. Điều này được thực hiện bằng cách cài đặt giao diện IHashCodeProvider để cung cấp một hàm băm luân phiên, và nó cũng đòi hỏi một đối sánh cài đặt của IComparer. Các cài đặt mới này được chuyển đến cấu tử của Hashtable:
using System;
using System.Collections;
public class Employee: IComparable {
public Employee(string name, int id) {
this.name = name;
this.id = id;
}
int IComparable.CompareTo(object obj) {
Employee emp2 = (Employee) obj;
if (this.id > emp2.id)
return(1);
if (this.id < emp2.id)
return(-1);
else
return(0);
}
public override int GetHashCode() {
return(id);
}
public static IComparer SortByName {
get {
return((IComparer) new SortByNameClass());
}
}
public static IComparer SortById {
get {
return((IComparer) new SortByIdClass());
}
}
public static IHashCodeProvider HashByName {
get {
return((IHashCodeProvider) new HashByNameClass());
}
}
public override string ToString() {
return(name + “:” + id);
}
class SortByNameClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(String.Compare(emp1.name, emp2.name));
}
}
class SortByIdClass: IComparer {
public int Compare(object obj1, object obj2) {
Employee emp1 = (Employee) obj1;
Employee emp2 = (Employee) obj2;
return(((IComparable) emp1).CompareTo(obj2));
}
}
class HashByNameClass: IhashCodeProvider {
public int GetHashCode(object obj) {
Employee emp = (Employee) obj;
return(emp.name.GetHashCode());
}
}
string name;
int id;
}
class Test {
public static void
Employee herb = new Employee(“Herb”, 555);
Employee george = new Employee(“George”, 123);
Employee frank = new Employee(“Frank”, 111);
Hashtable employees =
new Hashtable(Employee.HashByName, Employee.SortByName);
employees.Add(herb, “414 Evergreen Terrace”);
employees.Add(george, “
employees.Add(frank, “
Employee herbClone = new Employee(“Herb”, 000);
string address = (string) employees[herbClone];
Console.WriteLine(“{0} lives at {1}”, h rbClone, address);
}
}
Kỹ thuật này nên được sử dụng một cách rải rác. Thường đơn giản hơn để trình bày một giá trị, như tên của nhân viên như một thuộc tính, và cho phép sử dụng như một khoá băm thay thế.
IClonable
Hàm object.MemberWiseClone() có thể được sử dụng để tạo một đối tượng nhái của một đối tượng. Cài đặt mặc định của hàm này tạo ra một sao chép hời hợt của một đối tượng; các trường của đối tượng được sao chép chính xác chứ không phải chỉ là bản sao. Hãy xem mã sau:
using System;
class ContainedValue {
public ContainedValue(int count) {
this.count = count;
}
public int count;
}
class MyObject {
public MyObject(int count) {
this.contained = new ContainedValue(count);
}
public MyObject Clone() {
return((MyObject) MemberwiseClone());
}
public ContainedValue contained;
}
class Test {
public static void
MyObject my = new MyObject(33);
MyObject myClone = my.Clone();
Console.WriteLine( “Values: {0} {1}”, my.contained.count,
myClone.contained.count);
myClone.contained.count = 15;
Console.WriteLine( “Values: {0} {1}”, my.contained.count,
myClone.contained.count);
}
}
Ví dụ này cho ra kết quả sau:
Values: 33 33
Values: 15 15
Bởi vì bản sao được tạo bởi MemberWiseClone() là một bản sao hời hợt, nên giá trị của contained là cũng như thế trong cả hai đối tượng, và sự thay đổi một giá trị bên trong đối tượng ContainedValue ảnh hưởng đến cả hai thể hiện của MyObject.
Cái gì cần thiết là một sao chép sâu, nơi mà một thể hiện mới của ContainedValue được tạo ra cho một thể hiện mới của MyObject. Điều này được thực hiện bằng cách cài đặt giao diện IClonable:
using System;
class ContainedValue {
public ContainedValue(int count) {
this.count = count;
}
public int count;
}
class MyObject: ICloneable {
public MyObject(int count) {
this.contained = new ContainedValue(count);
}
public object Clone() {
Console.WriteLine(“Clone”);
return(new MyObject(this.contained.count));
}
public ContainedValue contained;
}
class Test {
public static void
MyObject my = new MyObject(33);
MyObject myClone = (MyObject) my.Clone();
Console.WriteLine( “Values: {0} {1}”, my.contained.count,
myClone.contained.count);
myClone.contained.count = 15;
Console.WriteLine( “Values: {0} {1}”, my.contained.count,
myClone.contained.count);
}
}
Ví dụ này cho ra kết quả:
Values: 33 33
Values: 33 15
Lời gọi đến MemberWiseClone() bây giờ sẽ trả ra kết quả trong một thể hiện mới của ContainedValue, và các nội dung của thể hiện này có thể được sửa đổi mà không ảnh hưởng đến nội dung của my. Không giống các giao diện khác mà có thể được định nghĩa trong một đối tượng, IClonable không được gọi bởi thời gian chạy; nó được cung cấp đơn thuần để bảo đảm hàm Clone() có chứ ký thích hợp.
Các nguyên tắc thiết kế
Việc dự định sử dụng một đối tượng nên được xem xét khi quyết định các hàm ảo và các giao diện để cài đặt. Bảng sau cung cấp các nguyên tắc chỉ đạo cho điều này:
ĐỐI TƯỢNG SỬ DỤNG |
HÀM HAY GIAO DIỆN |
Chung |
ToString() |
Các mảng hay sưu tập |
Equals(), opeartor==(), operator!=(), GetHashCode() |
Sắp xếp và tìm kiếm nhị phân |
IComparable |
Nhiều thứ tự sắp xếp |
IComparer |
Đa tìm kiếm |
IhashCodeProvider |
Hàm và giao diện bằng lớp Frameworks
Các bảng sau tóm tắt các hàm hay giao diện cho các đối tượng được sử dụng bởi mỗi lớp sưu tập.
Array
HÀM |
SỬ DỤNG |
IndexOf() |
Equals() |
LastIndexOf() |
Equals() |
Contains() |
Equals() |
Sort() |
Equals(), IComparable |
BinarySearch() |
Equals(), IComparable |
ArrayList
HÀM |
SỬ DỤNG |
IndexOf() |
Equals() |
LastIndexOf() |
Equals() |
Contains() |
Equals() |
Sort() |
Equals(), IComparable |
BinarySearch() |
Equals(), IComparable |
Hashtable
HÀM |
SỬ DỤNG |
HashTable() |
IhashCodeProvider, IComparable (tuỳ chọn) |
Contains() |
GetHashCode(), Equals() |
Item |
GetHashCode(), Equals() |
SortedList
HÀM |
SỬ DỤNG |
SortedList() |
IComparable |
Contains() |
IComparable |
ContainsKey() |
IComparable |
ContainsValue() |
Equals() |
IndexOfKey() |
IComparable |
IndexOfValue() |
Equals() |
Item |
IComparable |