Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.59 MB, 226 trang )
•
Ta cần lưu bao nhiêu biến
trong ví dụ , để cấp phát đủ vùng nhớ lưu trữ 10 số thập phân decimal , ta viết :
decimal *pDecimals = stackalloc decimal [10];
lệnh này chỉ đơn giản cấp phát vùng nhớ. không khởi tạo bất kì giá trị nào.
Để lưu 20 số double ta viết :
double *pDoubles = stackalloc double [20];
mặc dù dòng mã này đặc tả số biến được lưu là hằng, điều này có thể là 1 định giá số lượng vào lúc chạy. vì thế ta có thể viết
tương đương với ví dụ trên như sau :
int size;
size = 20; // or some other value calculated at run-time
double *pDoubles = stackalloc double [size];
Kiểu mảng cơ bản nhất mà có thể có là 1 khối bộ nhớ lưu các phần tử như sau :
Câu hỏi được đặt ra là làm thế nào ta sử dụng vùng nhớ mà ta vừa tạo.trở lại ví dụ ta vừa nói rằng giá trị trả về từ stackalloc trỏ
đến bắt đầu của vùng nhớ.do đó cho phép ta có thể lấy vị trí đầu tiên của vùng nhớ được cấp phát.ví dụ để cấp phát các số double
và thiết lập phần tử đầu tiên ( phàn tử 0 của mảng) giá trị 3.0 tacó thể viết :
double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;
Ta có thể thiết lập phần tử thứ 2 của mảng bằng cách dùng cách tính toán trên con trỏ mà ta đã biết .Ví dụ nếu ta muốn đặt giá trị
của phần tử thứ hai ta làm như sau :
double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;
*(pDoubles+1) = 8.4;
Nó chung ta có thể lấy phần tử thứ X của mảng với biểu thức *(pDoubles+X)
Bên cạnh đó C# cũng định nghĩa 1 cú pháp thay thế .Nếu p là con trỏ và X là kiểu số thì biểu thức p[X] tương đương với *(p+X).
double *pDoubles = stackalloc double [20];
pDoubles[0] = 3.0; // pDoubles[0] is the same as *pDoubles
pDoubles[1] = 8.4; // pDoubles[1] is the same as *(pDoubles+1)
Mặc dù mảng của ta có thể được truy xuất theo cùng cách như mảng bình thường, ta cần quan tâm đến cảnh báo sau . đoạn mã
sau đây sẽ gây ra 1 biệt lệ:
double [] myDoubleArray = new double [20];
myDoubleArray[50] = 3.0;
Biệt lệ xuất hiện vì ta cố truy xuất vào mảng dùng chỉ mục vượt quá mảng ( chỉ mục là 50 , nhưng giá trị lớn nhất cho phép là
19).Tuy nhiên ,nếu ta khai báo 1 mảng dùng stackalloc , điều đó sẽ không gây ra biệt lệ :
double *pDoubles = stackalloc double [20];
pDoubles[50] = 3.0;
Ví dụ QuickArray
Ta sẽ thảo luận về con trỏ với stackalloc trong ví dụ QuickArray. ví dụ hỏi người dùng bao nhiêu phần tử họ muốn cấp phát cho
mảng. sau đó ta dùng stackalloc để cấp phát mảng với độ dài đó.các phần tử của mảng này được gán giá trị là bình phương của
chỉ mục của nó .kết quả trình bày bên dưới :
using System;
namespace Wrox.ProCSharp.AdvancedCSharp
{
class MainEntryPoint
{
static unsafe void Main()
{
Console.Write("How big an array do you want? \n> ");
string userInput = Console.ReadLine();
uint size = uint.Parse(userInput);
long *pArray = stackalloc long [(int)size];
for (int i=0 ; i
pArray[i] = i*i;
for (int i=0 ; i
Console.WriteLine("Element {0} = {1}", i, *(pArray+i));
}
}
}
QuickArray
How big an array do you want?
> 15
Element 0 = 0
Element 1 = 1
Element 2 = 4
Element 3 = 9
Element 4 = 16
Element 5 = 25
Element 6 = 36
Element 7 = 49
Element 8 = 64
Element 9 = 81
Element 10 = 100
Element 11 = 121
Element 12 = 144
Element 13 = 169
Element 14 = 196
Code for Download :
PointerPlayrouund
PointerPlayrouund2
QuickArray
Tóm tắt
Chương này bao gồm một số chủ đề riêng biệt trong ngôn ngữ C#.Ta đã biết cơ chế của C# trong việc xử lí các trạng thái lỗi và
các biệt lệ,khả năng ép kiểu giữa các lớp và struct,truyền các phương thức như là thông số qua delegate,và thông báo các thông
điệp giữa các ứng dụng khi có điều gì đó xảy ra bằng các sự kiện ( event).Ta đã tìm hiểu 2 cách thức chính của C# để điều khiển
cách biên dịch , là các chỉ thị tiền xử lí và attribute ,trước khi hoàn tất chương này ta xem xét những gì xảy ra trong bộ nhớ bên
dưới C# và cách dùng con trỏ để tăng tốc sự thực thi khi ta thực sự cần.
Trong chương kết tiếp ta sẽ tìm hiểu cách C# tương tác với các lớp cơ sở để ta truy xuất đến các kĩ thật lập trình mạnh trong một
số lĩnh vực khác,bao gồm xử lí chuỗi,các attribute tùy biến và luồng.Ta sẽ xem xét kĩ thuật reflection cho phép 1 chương trình C#
truy xuất vào các lớp assembly mà mô tả các đối tượng riêng của nó và củ các ứng dụng khác.
System.object
Trong chương 3 , 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,trong chương trước ta chưa tìm hiểu kỉ về khả
năng của tất cả các phương thức,(chúng ta chỉ mới tìm hiểu chi tiết những phương thức tostring() và finalize() ).trong chương 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
string ToString()
public virtual Trả về 1 chuỗi đại diện cho đối tượng
int GetHashCode()
public virtual
bool Equals(object obj)
public virtual so sánh đối tượng này với 1 đối tượng khác
bool Equals(object objA, object objB)
Mục đích
trả về mã băm của đối tượng được thiết kế cho phép ta tìm kiếm 1 cách hiệu
quả các thể hiện của đối tượng trong từ điền
public static
so sánh 2 đối tượng
bool ReferenceEquals(object objA,
object objB)
public static
so sánh các tham chiếu đối tượng để xem chúng có chỉ đến cùng đối tượng
Type GetType()
public
trả về 1 đối tượng dẫn xuất từ System.Type mà đưa ra chi tiết kiểu dữ liệu
object MemberwiseClone()
protected
Makes a shallow copy of the object (in other words, copies data in the object
but not other objects any fields refer to)
void Finalize()
protected
virtual
Hàm hủy ( Destructor)
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().
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()
Ảo, 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ệ.
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 đó 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ị
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.
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à 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á (as compare but doesn't take culture into
CompareOrdinal account)
Format
định dạng một chuỗi chứa 1 giá trị khác và chỉ định cách mỗi giá trị 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
PadLeft
canh phải chuỗi
điền chuỗi bằng cách thêm 1 kí tự được chỉ định lặp lại vào đầu chuỗi
PadRigth
canh trái chuỗi
điền chuỗi bằng cách thêm 1 kí tự được chỉ định lặp lại vào cuối chuỗi
Replace
thay thế kí tự hay chuỗi con trong chuỗi với 1 kí tự hoặc chuỗi con khác
Split
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í 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;
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 :
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 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
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 monetary value
$4834.50 (USA)£4834.50 (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 format for
numbers
4,384.50 (UK/USA)4 384,50 (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)
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
{
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)
Để 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:
struct Vector : IFormattable
{
public double x, y, z;
sao đó thêm vào phương thức overload tostring() 2 thông số :
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 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ố mà ta đã phát triển ở chương 3 :
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 )
v2 is ( 8.454000E+002, 5.430000E+001, -7.800000E+000 )
Norm là :
v1 is || 1050 ||
v2 is || 717710.49 ||
Code for Download:
FormattableVector
StringEncoder
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 :
- Array lists
- Collection
- Dictionary ( hay maps)
Các lớp cấu trúc dữ liệu này nằm trong namespace System.Collection
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 đó , the 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 .ví dụ , ta tạo ra một danh sách Vectors:
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 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];
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:
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();
}
}
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;
}
public bool MoveNext()
{
++location;
return (location > 2) ? false : true;
}
public object Current
{
get
{
if (location < 0 || location > 2)
throw new InvalidOperationException(
"The enumerator is either before the first element or " +