Events, Delegates và Collections trong C#
- 30-11-2023
- Toanngo92
- 0 Comments
Delegates trong C# được sử dụng để tham chiếu đến các phương thức được định nghĩa trong một lớp. Chúng cung cấp khả năng tham chiếu đến một phương thức dưới dạng tham số. Sự kiện là các hành động gây ra việc thực thi một tập hợp câu lệnh của phương thức. Delegates có thể được sử dụng cùng với sự kiện để gắn chúng với các phương thức xử lý các sự kiện. Bộ sưu tập cho phép bạn kiểm soát và điều chỉnh một nhóm các đối tượng một cách linh hoạt tại thời điểm chạy. Namespace System.Collections bao gồm các bộ sưu tập của mảng, danh sách, bảng băm và từ điển. Namespace System.Collections.Generic bao gồm các bộ sưu tập generic, cung cấp tính an toàn kiểu và hiệu suất tốt hơn.
Mục lục
Delegates
Trong .NET Framework, một delegate trỏ đến một hoặc nhiều phương thức. Một khi bạn khởi tạo delegate, các phương thức tương ứng sẽ được gọi. Delegates là các đối tượng chứa tham chiếu đến các phương thức mà phải được gọi thay vì chứa tên thực sự của các phương thức. Sử dụng delegates, bạn có thể gọi bất kỳ phương thức nào, chỉ được xác định tại thời gian chạy. Một delegate giống như một tên phương thức tổng quát mà trỏ đến các phương thức khác nhau tại các thời điểm khác nhau và gọi phương thức cần thiết tại thời gian chạy. Trong C#, việc gọi delegate sẽ thực thi phương thức tham chiếu tại thời gian chạy.
Để liên kết một delegate với một phương thức cụ thể, phương thức đó phải có cùng kiểu trả về và kiểu tham số với delegate.
Delegates trong C#
Xem xét hai phương thức, Add()
và Subtract()
. Phương thức Add()
nhận hai tham số kiểu số nguyên và trả về tổng của chúng dưới dạng số nguyên. Tương tự, phương thức Subtract()
nhận hai tham số kiểu số nguyên và trả về hiệu của chúng dưới dạng số nguyên. Vì cả hai phương thức đều có cùng kiểu tham số và kiểu trả về một delegate, Calculation
có thể được tạo ra để sử dụng để trỏ đến Add()
hoặc Subtract()
. Tuy nhiên, khi delegate được gọi trong khi trỏ đến Add()
, các tham số sẽ được cộng. Tương tự, nếu delegate được gọi trong khi trỏ đến Subtract()
, các tham số sẽ được trừ.
Delegates trong C# có một số tính năng khác biệt so với các phương thức thông thường. Những tính năng này bao gồm:
- Phương thức có thể được chuyển làm tham số cho một delegate. Ngoài ra, delegate có thể chấp nhận một khối mã làm tham số. Các khối này được gọi là anonymous methods vì chúng không có tên phương thức.
- Một delegate có thể gọi đồng thời nhiều phương thức. Điều này được gọi là multicasting.
- Một delegate có thể đóng gói các phương thức tĩnh.
- Delegates đảm bảo an toàn kiểu dữ liệu khi kiểu trả về và kiểu tham số của delegate giống với kiểu của phương thức được tham chiếu. Điều này đảm bảo dữ liệu an toàn và tin cậy được truyền đến phương thức được gọi.
Khai báo Delegates
Delegates trong C# được khai báo bằng từ khóa delegate
theo sau là kiểu trả về và các tham số của phương thức được tham chiếu. Việc khai báo delegate khá giống việc khai báo một phương thức ngoại trừ việc không có triển khai. Do đó, câu lệnh khai báo phải kết thúc bằng dấu chấm phẩy. Hình bên dưới hiển thị một ví dụ về cách khai báo delegates.
Cú pháp sau được sử dụng để khai báo một delegate:
<access_modifier> delegate <return_type>DelegateName([list_of_parameters]);
trong đó,
access_modifler
: Chỉ định phạm vi truy cập cho delegate. Nếu được khai báo bên ngoài lớp, phạm vi sẽ luôn là public.return_type
: Xác định kiểu dữ liệu của giá trị được trả về bởi phương thức.DelegateName
: Chỉ định tên của delegate.List_of_paraneters
: Xác định các loại dữ liệu và tên của các tham số cần được truyền vào phương thức.
Đoạn mã bên dưới khai báo delegate Calculation
với kiểu trả về và các kiểu tham số là int
.
public delegate int Calculation(int numOne, int numTwo);
Ghi chú: Nếu delegate được khai báo bên ngoài lớp, bạn không thể khai báo một delegate khác có cùng tên trong không gian tên đó.
Khởi tạo Delegates
Bước tiếp theo sau khi khai báo delegate là khởi tạo delegate và liên kết nó với phương thức cần thiết. Ở đây, bạn phải tạo một đối tượng của delegate. Giống như tất cả các đối tượng khác, một đối tượng của delegate được tạo bằng từ khóa new
. Đối tượng này lấy tên của phương thức làm tham số và phương thức này có chữ ký tương tự như của delegate. Đối tượng được tạo được sử dụng để gọi phương thức tương ứng tại thời điểm chạy. Hình bên dưới hiển thị một ví dụ về việc khởi tạo delegate.
Cú pháp sau được sử dụng để khởi tạo một delegate:
<DelegateName> <objectName> = new <DelegateName>(<MethodName>);
trong đó,
DelegateName
: Đặc tên của delegate.objName
: Đặc tên của đối tượng delegate.MethodName
: Đặc tên của phương thức được tham chiếu bởi đối tượng delegate.
Đoạn mã bên dưới khai báo một delegate Calculation
bên ngoài lớp Mathematics
và khởi tạo nó trong lớp.
public delegate int Calculation(int numOne, int numTwo);
class Mathematics
{
static int Addition(int numOne, int numTwo)
{
return numOne + numTwo;
}
static int Subtraction(int numOne, int numTwo)
{
return numOne - numTwo;
}
static void Main(string[] args)
{
int valOne = 5;
int valTwo = 23;
Calculation objCalculation = new Calculation(Addition);
Console.WriteLine(valOne + " + " + valTwo + " = " + objCalculation(valOne, valTwo));
}
}
Trong đoạn mã trên, delegate có tên là Calculation
được khai báo bên ngoài lớp Mathematics
.
Trong phương thức Main()
, một đối tượng của delegate được tạo ra và nhận phương thức Addition()
làm tham số. Kiểu tham số của phương thức và kiểu của delegate là giống nhau, đó là kiểu int
.
Kết quả:
5+23=28
Ghi chú: Trong các phiên bản gần đây của C#, các hàm ẩn danh gọi là ‘biểu thức lambda’ được sử dụng để tạo ra các delegate. Điều này sẽ được khám phá chi tiết trong module tiếp theo.
Sử dụng Delegates
Một delegate có thể được khai báo trước khi tạo lớp (có phương thức được tham chiếu) hoặc có thể được định nghĩa trong lớp.
Để triển khai delegates trong C# bao gồm bốn bước như sau:
- Khai báo một delegate.
- Tạo phương thức được tham chiếu bởi delegate.
- Khởi tạo delegate.
- Gọi phương thức bằng đối tượng của delegate.
Mỗi bước này được minh họa bằng một ví dụ trong hình bên dưới.
Một phương thức ẩn danh là một khối mã nguồn được viết ngay tại chỗ và có thể được truyền như một tham số của delegate. Bằng cách sử dụng phương thức ẩn danh, bạn có thể tránh việc tạo các phương thức có tên. Hình bên dưới hiển thị một ví dụ về việc sử dụng phương thức ẩn danh.
Mô hình Delegate-Event
Ở mô hình delegate-event (delegate-event model), đây là một mô hình lập trình cho phép người dùng tương tác với máy tính và các thiết bị được điều khiển bằng giao diện người dùng đồ họa. Mô hình này bao gồm:
- Nguồn sự kiện (event source), là cửa sổ console trong trường hợp của ứng dụng dựa trên console.
- Người nghe (listeners) nhận các sự kiện từ nguồn sự kiện.
- Kênh truyền thông (medium) cung cấp giao thức cần thiết để mỗi sự kiện được truyền đạt.
Trong mô hình này, mỗi người nghe phải thực hiện một kênh truyền thông cho sự kiện mà nó muốn lắng nghe. Sử dụng kênh truyền thông này, mỗi khi nguồn tạo ra một sự kiện, sự kiện sẽ được thông báo đến các người nghe đã đăng ký.
Hãy xem xét một khách đang bấm chuông cửa ở bên cửa nhà. Chủ nhà trong nhà nghe tiếng chuông và phản ứng bằng cách mở cửa. Ở đây, tiếng chuông là một sự kiện dẫn đến việc mở cửa. Tương tự, trong C#, một sự kiện là một hành động được tạo ra gây ra một phản ứng. Ví dụ, bấm phím Ctrl + Break
trên cửa sổ máy chủ dựa trên console là một sự kiện sẽ khiến máy chủ kết thúc.
Sự kiện này dẫn đến việc lưu thông tin vào cơ sở dữ liệu, đó là phản ứng được kích hoạt. Ở đây, người nghe là đối tượng gọi phương thức cần thiết để lưu thông tin vào cơ sở dữ liệu.
Delegate có thể được sử dụng để xử lý các sự kiện. Với tham số là các phương thức cần được gọi khi sự kiện xảy ra. Những phương thức này được gọi là các trình xử lý sự kiện.
Về việc sử dụng nhiều delegate trong C#, người dùng có thể gọi nhiều delegate trong một chương trình duy nhất. Tùy thuộc vào tên của delegate hoặc loại tham số được truyền cho delegate, delegate phù hợp sẽ được gọi.
Multiple Delegates
Trong C#, người dùng có thể gọi nhiều delegate trong một chương trình duy nhất. Tùy thuộc vào tên của delegate hoặc loại tham số được truyền cho delegate, delegate phù hợp sẽ được gọi.
Mã nguồn bên dưới thể hiện việc sử dụng nhiều delegate bằng cách tạo hai delegate CalculateArea
và CalculateVolume
có kiểu trả về và kiểu tham số là double.
using System;
public delegate double CalculateArea(double val);
public delegate double CalculateVolume(double val);
class Cube
{
static double Area(double val)
{
return 6 * (val * val);
}
static double Volume(double val)
{
return val * val * val;
}
static void Main(string[] args)
{
CalculateArea objCalculateArea = new CalculateArea(Area);
CalculateVolume objCalculateVolume = new CalculateVolume(Volume);
Console.WriteLine("Surface Area of Cube: " + objCalculateArea(200.32));
Console.WriteLine("Volume of Cube: " + objCalculateVolume(20.56));
}
}
Trong đoạn mã trên, khi các delegate CalculateArea
và CalculateVolume
được khởi tạo trong phương thức Main()
, tham chiếu của các phương thức Area
và Volume
được truyền như tham số tương ứng cho các delegate CalculateArea
và CalculateVolume
. Các giá trị được truyền cho các thể hiện của các delegate thích hợp, sau đó các delegate này kích hoạt các phương thức tương ứng.
Hình bên dưới mô tả việc sử dụng nhiều delegate.
Multicast Delegate trong C#
Một delegate duy nhất có thể bao gồm các tham chiếu của nhiều phương thức cùng một lúc. Nói cách khác, một delegate có thể chứa một số lượng tham chiếu của các phương thức. Các delegate như vậy được gọi là ‘Multicast Delegate’. Một delegate đa điểm duy trì một danh sách các phương thức (danh sách gọi) sẽ được tự động gọi khi delegate được gọi.
Các delegate đa điểm trong C# là các loại con của lớp System.MulticastDelegate
. Delegate đa điểm được định nghĩa giống như các delegate đơn giản, tuy nhiên, kiểu trả về của delegate đa điểm chỉ có thể là void
. Nếu kiểu trả về khác được chỉ định, một ngoại lệ thời gian chạy sẽ xảy ra. Điều này là do nếu delegate trả về một giá trị, giá trị trả về của phương thức cuối cùng trong danh sách gọi của delegate sẽ trở thành kiểu trả về của delegate. Điều này sẽ dẫn đến kết quả không phù hợp. Do đó, kiểu trả về luôn luôn là void
.
Để thêm các phương thức vào danh sách gọi của một delegate đa điểm, người dùng có thể sử dụng toán tử ‘+=’ hoặc ‘+=’. Tương tự, để loại bỏ một phương thức khỏi danh sách gọi của delegate, người dùng có thể sử dụng toán tử ‘-=’ hoặc ‘=’. Khi một delegate đa điểm được gọi, tất cả các phương thức trong danh sách được gọi theo thứ tự tuần tự theo cùng thứ tự mà chúng được thêm vào.
Đoạn mã bên dưới tạo ra một delegate đa điểm là Maths
. Delegate này bao gồm tham chiếu của các phương thức Addition
, Subtraction
, Multiplication
, và Division
.
using System;
public delegate void Maths(int valOne, int valTwo);
class MathsDemo
{
static void Addition(int valOne, int valTwo)
{
int result = valOne + valTwo;
Console.WriteLine("Addition:" +valOne+ "+" +valTwo+ "=" +result);
}
static void Subtraction(int valOne, int valTwo)
{
int result = valOne - valTwo;
Console.WriteLine("Subtraction:" +valOne+ "-" +valTwo+ "=" +result);
}
static void Multiplication(int valOne, int valTwo)
{
int result = valOne * valTwo;
Console.WriteLine("Multiplication:" +valOne+ "*" +valTwo+ "=" +result);
}
static void Division(int valOne, int valTwo)
{
int result = valOne / valTwo;
Console.WriteLine("Division:" +valOne+ "/" +valTwo+ "=" +result);
}
static void Main(string[] args)
{
Maths objMaths = new Maths(Addition);
objMaths += new Maths(Subtraction);
objMaths += new Maths(Multiplication);
objMaths += new Maths(Division);
if(objMaths != null){
objMaths(20,10);
}
}
}
Trong đoạn mã trên, delegate Maths
được khởi tạo trong phương thức Main()
. Sau khi đối tượng được tạo, các phương thức được thêm vào delegate bằng cách sử dụng toán tử ‘+=’, điều này khiến delegate trở thành một delegate đa điểm.
Hình bên dưới cho thấy việc tạo một delegate multicast.
Lớp System.Delegate
Lớp Delegate
trong namespace System
là một lớp tích hợp được định nghĩa để tạo ra các delegate trong C#. Tất cả các delegate trong C# ngầm kế thừa từ lớp Delegate
. Điều này là do từ khóa delegate
cho biết với trình biên dịch rằng delegate
được định nghĩa trong chương trình là được dẫn xuất từ lớp này. Lớp này cung cấp các hàm tạo, phương thức và thuộc tính khác nhau để tạo ra, thao tác và truy xuất các delegate
được định nghĩa trong chương trình.
Bảng bên dưới liệt kê các hàm tạo được định nghĩa trong lớp Delegate
.
Hàm khởi tạo | Mô tả |
Delegate(object, string) | Gọi một phương thức được tham chiếu bởi đối tượng của lớp được cung cấp dưới dạng tham số |
Delegate(type, string) | Gọi một phương thức tĩnh của lớp được đưa ra dưới dạng tham số |
Bảng kế tiếp liệt kê các thuộc tính được định nghĩa trong lớp Delegate
.
Thuộc tính | Mô tả |
Method | Truy xuất phương thức được tham chiếu |
Target | Truy xuất đối tượng của lớp mà đại biểu gọi phương thức được tham chiếu |
Bảng bên dưới liệt kê một số phương thức được định nghĩa trong lớp Delegate
.
Phương thức | Mô tả |
Clone | Tạo một bản sao của delegate hiện tại |
Combine | Hợp nhất danh sách gọi của các delegates multicast |
CreateDelegate | Khai báo và khởi tạo một delegate |
DynamicInvoke | Gọi phương thức được tham chiếu vào thời gian chạy |
GetInvocationList | Truy xuất danh sách yêu cầu của delegate hiện tại |
Đoạn mã bên dưới minh họa việc sử dụng một số thuộc tính và phương thức của lớp Delegate
tích hợp sẵn.
using System;
public delegate void Messenger(int value);
class CompositeDelegates
{
public static void EvenNumbers(int value)
{
Console.Write("Even Numbers: ");
for (int i = 2; i <= value; i += 2)
{
Console.Write(i + " ");
}
}
public void OddNumbers(int value)
{
Console.WriteLine();
Console.Write("Odd Numbers: ");
for (int i = 1; i <= value; i += 2)
{
Console.Write(i + " ");
}
}
public static void Start(int number)
{
CompositeDelegates objComposite = new CompositeDelegates();
Messenger objDisplayOne = new Messenger(EvenNumbers);
Messenger objDisplayTwo = new Messenger(objComposite.OddNumbers);
Messenger objDisplayComposite = (Messenger)Delegate.Combine(objDisplayOne, objDisplayTwo);
objDisplayComposite(number);
Console.WriteLine();
object obj = objDisplayComposite.Method.ToString();
if (obj != null)
{
Console.WriteLine("The delegate invokes an instance method: " + obj);
}
else
{
Console.WriteLine("The delegate invokes only static methods");
}
}
public static void Main(string[] args)
{
int value = 0;
Console.WriteLine("Enter the value till which you want to display even and odd numbers");
try
{
value = Convert.ToInt32(Console.ReadLine());
}
catch (FormatException objFormat)
{
Console.WriteLine("Error: " + objFormat);
}
Start(value);
}
}
Trong đoạn mã trên, delegate Messenger
được khởi tạo trong phương thức Start()
. Một thể hiện của delegate, objDisplayOne
nhận phương thức tĩnh, EvenNumbers()
, làm tham số, và một thể hiện khác của delegate, objDisplayTwo
, nhận phương thức không tĩnh, OddNumberss()
, làm tham số bằng cách sử dụng thể hiện của lớp. Phương thức Combine()
kết hợp các delegate được cung cấp trong danh sách trong dấu ngoặc đơn.
Thuộc tính Method
kiểm tra xem chương trình có chứa phương thức thể hiện hay phương thức tĩnh. Nếu chương trình chỉ chứa các phương thức tĩnh, thuộc tính Method
sẽ trả về giá trị null. Phương thức Main()
cho phép người dùng nhập giá trị. Phương thức Start() được gọi bằng cách truyền giá trị này làm tham số. Giá trị này lại được truyền cho thể hiện của lớp CompositeDelegates làm tham số, từ đó kích hoạt cả hai delegate. Mã nguồn hiển thị các số chẵn và lẻ trong phạm vi chỉ định bằng cách gọi các phương thức tương ứng.
Hình bên dưới mô tả việc sử dụng một số thuộc tính và phương thức của lớp Delegate
.
Sự kiện (Events)
Hãy tưởng tượng một nhóm người tại một buổi tiệc đang chơi Bingo. Khi một số được gọi, những người tham gia kiểm tra xem số đó có trên thẻ của họ hay không, trong khi những người không tham gia tiếp tục hoạt động của họ, thưởng thức các hoạt động khác. Nếu tình huống này được phân tích từ góc nhìn của một lập trình viên, việc gọi số tương đương với việc xảy ra một sự kiện. Thông báo về sự kiện được thực hiện bởi người thông báo. Ở đây, những người chơi trò chơi đang chú ý (đăng ký) vào những gì người thông báo (nguồn của sự kiện) muốn nói (thông báo).
Tương tự, trong C# sự kiện cho phép một đối tượng (nguồn của sự kiện) thông báo cho các đối tượng khác (người đăng ký) về sự kiện (một thay đổi đã xảy ra).
Hình bên dưới mô tả khái niệm về sự kiện.
Đặc điểm
Sự kiện là một hành động được tạo ra bởi người dùng hoặc hệ thống, cho phép các đối tượng cần thiết thông báo cho các đối tượng hoặc lớp khác để xử lý sự kiện. Sự kiện trong C# có những đặc điểm sau:
- Có thể được khai báo trong các lớp và giao diện.
- Có thể được khai báo là trừu tượng hoặc kín.
- Có thể được khai báo là ảo.
- Được thực hiện bằng cách sử dụng delegate.
Sự kiện có thể được sử dụng để thực hiện các hành động tùy chỉnh mà không được hỗ trợ sẵn trong C#. Sự kiện được rộng rãi sử dụng trong việc tạo các ứng dụng dựa trên giao diện người dùng (GUI), nơi các sự kiện như việc chọn một mục từ danh sách và đóng cửa sổ được theo dõi.
Tạo và Sử dụng Sự kiện (Events)
Có bốn bước để triển khai sự kiện trong C#:
- Định nghĩa một delegate công cộng cho sự kiện.
- Tạo sự kiện bằng cách sử dụng delegate.
- Đăng ký để lắng nghe và xử lý sự kiện.
- Kích hoạt sự kiện.
Sự kiện sử dụng delegate để gọi các phương thức trong các đối tượng đã đăng ký sự kiện. Khi một sự kiện chứa một số lượng người đăng ký được kích hoạt, nhiều delegates sẽ được gọi.
Khai báo Sự kiện (Events)
Một khai báo sự kiện bao gồm hai bước: tạo delegate và tạo sự kiện. Một delegate được khai báo bằng từ khóa delegate
. Delegate chuyển các tham số của phương thức thích hợp để được gọi khi sự kiện được tạo ra. Phương thức này được gọi là trình xử lý sự kiện. Sau đó, sự kiện được khai báo bằng từ khóa event
theo sau là tên của delegate và tên của sự kiện. Việc khai báo này liên kết sự kiện với delegate. Hình bên dưới hiển thị cú pháp để khai báo delegate và sự kiện.
Khai Báo Delegate:
<access_modifier> delegate <return type> <Identifier> (parameters);
Khai Báo Sự Kiện:
<access_modifier> event <DelegateName> <EventName>;
Một đối tượng có thể đăng ký cho một sự kiện chỉ khi sự kiện tồn tại. Để đăng ký cho sự kiện, đối tượng thêm một delegate gọi một phương thức khi sự kiện được kích hoạt. Điều này được thực hiện bằng cách liên kết trình xử lý sự kiện với sự kiện đã được tạo, sử dụng toán tử +=. Đây được gọi là đăng ký cho một sự kiện.
Để hủy đăng ký khỏi sự kiện, sử dụng toán tử -=. Cú pháp sau được sử dụng để tạo một phương thức trong lớp nhận:
<access_modifier><return_type> <MethodName> (parameters);
Cú pháp sau được sử dụng để liên kết phương thức với sự kiện:
<objectName>.<EventName> += new <DelegateName> MethodName);
trong đó,
objectName
: Là đối tượng của lớp mà trong đó trình xử lý sự kiện được định nghĩa.
Đoạn mã bên dưới liên kết trình xử lý sự kiện với sự kiện được khai báo.
using System;
public delegate void PrintDetails();
class TestEvent
{
event PrintDetails Print;
void Show()
{
Console.WriteLine("This program illustrates how to subscribe objects to an event");
Console.WriteLine("This method will not execute since the event has not been raised");
}
static void Main(string[] args)
{
TestEvent objTestEvent = new TestEvent();
objTestEvent.Print += new PrintDetails(objTestEvent.Show);
}
}
Trong Đoạn mã trên, delegate gọi là PrintDetails()
được khai báo mà không có bất kỳ tham số nào. Trong lớp TestEvent, sự kiện Print
được tạo ra và được liên kết với delegate. Trong phương thức Main()
, đối tượng của lớp TestEvent được sử dụng để đăng ký trình xử lý sự kiện gọi là Show()
với sự kiện Print
.
Kích hoạt Sự kiện
Một sự kiện được kích hoạt để thông báo cho tất cả các đối tượng đã đăng ký với sự kiện đó. Sự kiện có thể được kích hoạt bởi người dùng hoặc hệ thống. Khi một sự kiện được tạo ra, tất cả các trình xử lý sự kiện liên kết sẽ được thực thi. Delegate gọi tất cả các trình xử lý đã được thêm vào sự kiện. Tuy nhiên, trước khi kích hoạt một sự kiện, việc quan trọng là bạn cần tạo trình xử lý và đảm bảo rằng sự kiện được liên kết với các trình xử lý sự kiện thích hợp. Nếu sự kiện không được liên kết với bất kỳ trình xử lý sự kiện nào, sự kiện đã được khai báo sẽ được coi là ‘null’. Hình bên dưới hiển thị về việc kích hoạt sự kiện.
Đoạn mã bên dưới có thể được sử dụng để kiểm tra một điều kiện cụ thể trước khi đưa ra sự kiện.
if (condition)
{
eventMe();
}
Trong Đoạn mã trên, nếu điều kiện được kiểm tra được thỏa mãn, sự kiện eventMe được kích hoạt.
Cú pháp để kích hoạt một sự kiện tương tự như cú pháp để gọi một phương thức. Khi sự kiện được kích hoạt, nó sẽ gọi tất cả các delegate của các đối tượng đã đăng ký với nó. Nếu không có đối tượng nào đã đăng ký với sự kiện và sự kiện đã được kích hoạt, một ngoại lệ sẽ được ném ra.
Sự Kiện và Kế Thừa
Sự kiện trong C# chỉ có thể được kích hoạt trong lớp mà nó được khai báo và định nghĩa. Do đó, sự kiện không thể được kích hoạt trực tiếp bởi các lớp dẫn xuất. Tuy nhiên, sự kiện có thể được kích hoạt một cách gián tiếp trong C# bằng cách tạo một phương thức bảo vệ trong lớp cơ sở sẽ kích hoạt sự kiện được định nghĩa trong lớp cơ sở.
Đoạn mã bên dưới minh họa cách một sự kiện có thể được kích hoạt một cách gián tiếp.
using System;
public delegate void Display(string msg);
public class Parent
{
event Display Print;
protected void InvokeMethod()
{
Print += new Display(PrintMessage);
Check();
}
void Check()
{
if (Print != null)
{
PrintMessage("Welcome to C#");
}
}
void PrintMessage(string msg)
{
Console.WriteLine(msg);
}
}
class Child : Parent
{
static void Main(string[] args)
{
Child objChild = new Child();
objChild.InvokeMethod();
}
}
Trong đoạn mã trên, lớp Child được kế thừa từ lớp Parent. Một sự kiện có tên là Print được tạo ra trong lớp Parent và được liên kết với delegate Display. Phương thức bảo vệ InvokeMethod() liên kết sự kiện với delegate và truyền phương thức PrintMessage() như một tham số cho delegate. Phương thức Check() kiểm tra xem có bất kỳ phương thức nào đăng ký với sự kiện hay không. Vì phương thức PrintMessage() đăng ký với sự kiện Print, phương thức này được gọi. Phương thức Main() tạo một thể hiện của lớp Child. Thể hiện này kích hoạt phương thức InvokeMethod(), cho phép lớp con Child truy cập vào sự kiện Print được khai báo trong lớp cơ sở Parent.
Hình bên dưới mô tả kết quả của việc kích hoạt sự kiện.
Lưu ý: Một sự kiện có thể được khai báo là abstract
nhưng chỉ trong các lớp trừu tượng. Sự kiện trừu tượng này phải được ghi đè trong các lớp dẫn xuất của lớp trừu tượng. Sự kiện Abstract
được sử dụng để tùy chỉnh sự kiện khi được thực hiện trong các lớp dẫn xuất. Một sự kiện có thể được khai báo là sealed
trong một lớp cơ sở để ngăn việc kích hoạt nó từ bất kỳ lớp dẫn xuất nào. Một sự kiện sealed
không thể bị ghi đè trong bất kỳ lớp dẫn xuất nào để đảm bảo hoạt động an toàn của sự kiện.
Các Bộ Sưu Tập (Collections)
Một bộ sưu tập là một tập hợp dữ liệu liên quan nhau có thể không nhất thiết thuộc cùng loại dữ liệu. Nó có thể được thiết lập hoặc thay đổi động trong thời gian chạy. Truy cập vào các bộ sưu tập tương tự như truy cập vào mảng, trong đó các phần tử được truy cập bằng số chỉ mục của chúng. Tuy nhiên, có sự khác biệt giữa mảng và bộ sưu tập trong C#. Bảng bên dưới liệt kê các sự khác biệt giữa mảng và bộ sưu tập.
Mảng | Bộ Sưu Tập |
Không thể thay đổi kích thước trong thời gian chạy. | Có thể thay đổi kích thước trong thời gian chạy. |
Các phần tử riêng lẻ thuộc cùng một kiểu dữ liệu. | Các phần tử riêng lẻ có thể thuộc các kiểu dữ liệu khác nhau. |
Không chứa bất kỳ phương thức nào để thực hiện các thao tác trên các phần tử. | Chứa các phương thức để thực hiện các thao tác trên các phần tử. |
Namespace System.Collections
Namespace System.Collections
trong C# cho phép bạn xây dựng và điều khiển một bộ sưu tập các đối tượng. Bộ sưu tập này có thể bao gồm các phần tử của các loại dữ liệu khác nhau. Namespace System.Collections
định nghĩa các bộ sưu tập khác nhau như mảng động, danh sách và từ điển. Namespace System.Collections
bao gồm các lớp và giao diện định nghĩa các bộ sưu tập khác nhau.
Bảng bên dưới liệt kê các lớp và giao diện thông dụng trong namespace System.Collections
.
Lớp/Giao Diện | Mô tả |
Lớp ArrayList | Cung cấp một bộ sưu tập tương tự như một mảng ngoại trừ việc các mục có thể được thêm và truy xuất động từ danh sách và nó có thể chứa các giá trị của các loại khác nhau. |
Lớp Stack | Cung cấp một bộ sưu tập tuân theo nguyên tắc Last-In-First-Out (LIFO), nghĩa là mục cuối cùng được chèn vào bộ sưu tập sẽ được loại bỏ đầu tiên. |
Lớp Hashtable | Cung cấp một bộ sưu tập các cặp khóa và giá trị được sắp xếp dựa trên mã băm của khóa. |
Lớp SortedList | Cung cấp một bộ sưu tập các cặp khóa và giá trị trong đó các mục được sắp xếp dựa trên các khóa. |
Giao Diện IDictionary | Đại diện cho một bộ sưu tập gồm các cặp khóa/giá trị. |
Giao Diện IDictionaryEnumerator | Liệt kê các phần tử của từ điển. |
Giao Diện IEnumerable | Định nghĩa một bộ duyệt để thực hiện lặp qua một bộ sưu tập. |
Giao Diện ICollection | Chỉ định kích thước và các phương thức đồng bộ hóa cho tất cả các bộ sưu tập. |
Giao Diện IEnumerator | Hỗ trợ việc lặp qua các phần tử của bộ sưu tập. |
Giao Diện IList | Đại diện cho một bộ sưu tập các mục có thể được truy cập thông qua số chỉ mục của chúng. |
Đoạn mã bên dưới trình bày cách sử dụng các lớp và giao diện thường được sử dụng của System.Collections
namespace
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
class Employee : DictionaryBase
{
public void Add(int id, string name)
{
Dictionary.Add(id, name);
}
public void OnRemove(int id)
{
Console.WriteLine("You are going to delete record containing ID: " + id);
Dictionary.Remove(id);
}
public void GetDetails()
{
IDictionaryEnumerator objEnumerate = Dictionary.GetEnumerator();
while (objEnumerate.MoveNext())
{
Console.WriteLine(objEnumerate.Key.ToString() + "\t\t" + objEnumerate.Value);
}
}
static void Main(string[] args)
{
Employee objEmployee = new Employee();
objEmployee.Add(102, "John");
objEmployee.Add(105, "James");
objEmployee.Add(106, "Peter");
Console.WriteLine("Original values stored in Dictionary:");
objEmployee.GetDetails();
objEmployee.OnRemove(106);
Console.WriteLine("Modified values stored in Dictionary:");
objEmployee.GetDetails();
}
}
Trong đoạn mã trên, lớp Employee được kế thừa từ lớp DictionaryBase
. Lớp DictionaryBase
là một lớp trừu tượng. Chi tiết về các nhân viên được chèn sử dụng các phương thức có sẵn trong lớp DictionaryBase
. Phương thức Add()
được định nghĩa bởi người dùng, nhận hai tham số là id và name. Những tham số này được truyền vào phương thức Add()
của lớp Dictionary
, trong đó lớp Dictionary
lưu giữ các giá trị này dưới dạng cặp khóa/giá trị. Phương thức OnRemove()
của DictionaryBase
được ghi đè. Nó nhận một tham số chỉ định khóa mà cặp khóa/giá trị tương ứng sẽ được xóa khỏi lớp Dictionary
. Phương thức này sau đó in một thông báo cảnh báo trên bảng điều khiển trước khi xóa bản ghi khỏi lớp Dictionary
. Phương thức Dictionary.Remove()
được sử dụng để xóa cặp khóa/giá trị. Phương thức GetEnumerator()
trả về một IDictionaryEnumerator
, được sử dụng để duyệt qua danh sách.
Hình bên dưới hiển thị kết quả của ví dụ.
Namespace System.Collections.Generic
Hãy xem xét một biểu mẫu đăng ký trực tuyến được sinh viên sử dụng để đăng ký thi do một trường đại học tổ chức. Biểu mẫu đăng ký có thể được sử dụng để đăng ký thi của bất kỳ khóa học nào được cung cấp bởi trường đại học. Tương tự, trong C# generics cho phép bạn xác định cấu trúc dữ liệu bao gồm các chức năng có thể được triển khai cho bất kỳ loại dữ liệu nào. Do đó, generics cho phép bạn tái sử dụng mã cho các loại dữ liệu khác nhau.
Để tạo ra generics, bạn nên sử dụng các lớp có sẵn trong namespace System.Collections.Generic
. Những lớp này đảm bảo tính an toàn kiểu dữ liệu, một tính năng của C# đảm bảo rằng một giá trị được xử lý như kiểu dữ liệu mà nó được khai báo.
Ghi chú: Namespace System.Collections.Generic
tương tự như namespace System.Collections
vì cả hai đều cho phép bạn tạo ra các bộ sưu tập. Tuy nhiên, các bộ sưu tập generic được bảo đảm an toàn kiểu dữ liệu.
Các Lớp và Giao Diện
Namespace System.Collections.Generic
bao gồm các lớp và giao diện định nghĩa các bộ sưu tập generic khác nhau.
- Các lớp (Classes)
Namespace System.Collections.Generic
bao gồm các lớp cho phép bạn tạo ra các bộ sưu tập an toàn kiểu dữ liệu. Bảng bên dưới liệt kê các lớp thông dụng trong namespace System.Collections.Generic
.
Lớp | Mô Tả |
List<T> | Cung cấp một bộ sưu tập generic của các mục có thể được thay đổi kích thước động. |
Stack<T> | Cung cấp một bộ sưu tập generic tuân theo nguyên tắc LIFO (Last-In-First-Out), nghĩa là mục cuối cùng được chèn vào sẽ bị xóa đầu tiên. |
Queue<T> | Cung cấp một bộ sưu tập generic tuân theo nguyên tắc FIFO (First-In-First-Out), nghĩa là mục đầu tiên được chèn vào sẽ bị xóa đầu tiên. |
Dictionary<K, V> | Cung cấp một bộ sưu tập generic của các cặp khóa và giá trị. |
SortedDictionary<K, V> | Cung cấp một bộ sưu tập generic của các cặp khóa và giá trị được sắp xếp theo khóa của chúng, bao gồm các mục được sắp xếp theo khóa tương ứng của chúng. |
LinkedList<T> | Thực hiện danh sách liên kết đôi bằng cách lưu trữ các phần tử trong đó. |
- Giao diện và Cấu trúc (Interfaces & Structures)
Namespace System.Collections.Generic
bao gồm các giao diện và cấu trúc có thể được triển khai để tạo các bộ sưu tập an toàn về kiểu. Bảng bên dưới liệt kê một số cái thường được sử dụng.
Giao Diện/Cấu Trúc | Mô Tả |
ICollection Interface | Định nghĩa các phương thức để điều khiển các bộ sưu tập generic khác nhau. |
IEnumerable Interface | Là một giao diện xác định một trình duyệt để thực hiện vòng lặp qua một bộ sưu tập của một loại cụ thể. |
IComparer Interface | Là một giao diện xác định một phương thức để so sánh hai đối tượng. |
IDictionary Interface | Đại diện cho một bộ sưu tập generic bao gồm các cặp khóa và giá trị. |
IEnumerator Interface | Hỗ trợ lặp đơn giản qua các phần tử của một bộ sưu tập generic. |
IList Interface | Đại diện cho một bộ sưu tập generic các mục có thể được truy cập bằng vị trí chỉ mục. |
Dictionary.Enumerator Structure | Liệt kê các phần tử của một Dictionary. |
Dictionary.KeyCollection.Enumerator Structure | Liệt kê các phần tử của Dictionary.KeyCollection. |
Dictionary.ValueCollection.Enumerator Structure | Liệt kê các phần tử của Dictionary.ValueCollection. |
Key/ValuePair Structure | Xác định một cặp khóa/giá trị. |
Đoạn mã bên dưới trình bày cách sử dụng các lớp, giao diện và cấu trúc thường được sử dụng của System.Collection.Generic
namespace.
using System;
using System.Collections;
using System.Collections.Generic;
class Student : IEnumerable
{
LinkedList<string> objList = new LinkedList<string>();
public void StudentDetails()
{
objList.AddFirst("James");
objList.AddFirst("John");
objList.AddFirst("Patrick");
objList.AddFirst("Peter");
objList.AddFirst("James");
Console.WriteLine("Number of elements stored in the list: " + objList.Count);
}
public void Display(string name)
{
LinkedListNode<string> objNode;
int count = 0;
for (objNode = objList.First; objNode != null; objNode = objNode.Next)
{
if (objNode.Value.Equals(name))
{
count++;
}
}
Console.WriteLine("The value " + name + " appears " + count + " times in the list");
}
public IEnumerator GetEnumerator()
{
return objList.GetEnumerator();
}
static void Main(string[] args)
{
Student objStudent = new Student();
objStudent.StudentDetails();
foreach (string str in objStudent)
{
Console.WriteLine(str);
}
objStudent.Display("James");
}
}
Trong đoạn mã trên, Lớp Student
thực hiện giao diện IEnumerable
. Một danh sách liên kết kép kiểu cụ thể được tạo. Phương thức StudentDetails()
được định nghĩa để chèn các giá trị vào danh sách liên kết. Phương thức AddFirst()
của lớp LinkedList
được sử dụng để chèn các giá trị vào danh sách liên kết. Phương thức Display()
chấp nhận một đối số kiểu chuỗi duy nhất được sử dụng để tìm kiếm một giá trị cụ thể. Một tham chiếu lớp LinkNode
kiểu chuỗi được tạo bên trong phương thức Display()
. Tham chiếu này được sử dụng để duyệt qua danh sách liên kết. Mỗi khi tìm thấy một phần tử khớp với đối số chuỗi được chấp nhận trong phương thức Display()
, một biến đếm được tăng lên. Biến đếm này được sử dụng để hiển thị số lần xuất hiện của chuỗi cụ thể đã được tìm thấy trong danh sách liên kết. Phương thức GetEnumerator()
được triển khai, trả về một IEnumerator
. IEnumerator
được sử dụng để duyệt qua danh sách và hiển thị tất cả các giá trị được lưu trữ trong danh sách liên kết.
Hình bên dưới hiển thị ví dụ về System.Collections
namespace.
Lớp ArrayList
Lớp ArrayList
là một mảng có độ dài biến thiên, có thể tăng hoặc giảm kích thước một cách linh hoạt. Không giống như lớp Array
, lớp này có thể lưu trữ các phần tử thuộc các loại dữ liệu khác nhau. Lớp ArrayList
cho phép bạn chỉ định kích thước của bộ sưu tập trong quá trình thực thi chương trình.
Lớp này cho phép bạn xác định dung lượng, tức là số lượng phần tử mà một danh sách mảng có thể chứa. Tuy nhiên, dung lượng mặc định của một mảng là 16. Nếu số lượng phần tử trong danh sách đạt đến dung lượng đã xác định, dung lượng của danh sách sẽ tự động tăng gấp đôi. Nó có thể chứa giá trị null và cũng có thể bao gồm các phần tử trùng lặp.
Lớp ArrayList
cho phép bạn thêm, sửa đổi và xóa bất kỳ loại phần tử nào trong danh sách ngay cả khi đang thực thi. Các phần tử trong ArrayList
có thể được truy cập bằng cách sử dụng vị trí chỉ mục. Khi làm việc với lớp này, bạn không cần phải lo lắng về việc giải phóng bộ nhớ.
Lớp ArrayList
bao gồm các phương thức và thuộc tính khác nhau. Các phương thức và thuộc tính này được sử dụng để thêm và điều chỉnh các phần tử trong danh sách.
- Phương thức (Methods)
Các phương thức của lớp ArrayList
cho phép bạn thực hiện các hành động như thêm, xóa và sao chép các phần tử trong danh sách. Bảng bên dưới hiển thị các phương thức thông dụng của lớp ArrayList
.
Phương thức | Mô tả |
Add | Thêm một phần tử vào cuối danh sách. |
Remove | Xóa phần tử được chỉ định xuất hiện lần đầu tiên trong danh sách. |
RemoveAt | Xóa phần tử có vị trí chỉ mục được chỉ định trong danh sách. |
Insert | Chèn một phần tử vào danh sách tại vị trí chỉ định. |
Contains | Xác định sự tồn tại của một phần tử cụ thể trong danh sách. |
IndexOf | Trả về vị trí chỉ mục của một phần tử xuất hiện lần đầu tiên trong danh sách. |
Reverse | Đảo ngược các giá trị được lưu trữ trong ArrayList . |
Sort | Sắp xếp lại các phần tử theo thứ tự tăng dần. |
- Thuộc tính (Properties)
Các thuộc tính của lớp ArrayList
cho phép bạn đếm hoặc lấy các phần tử trong danh sách. Bảng bên dưới hiển thị các thuộc tính thông dụng của lớp ArrayList
.
Thuộc tính | Mô tả |
Capacity | Xác định số lượng phần tử mà danh sách có thể chứa. |
Count | Xác định số lượng phần tử hiện có trong danh sách. |
Item | Truy xuất hoặc thiết lập giá trị tại vị trí chỉ định. |
Đoạn mã bên dưới thể hiện việc sử dụng các phương thức và thuộc tính của lớp ArrayList
.
using System;
using System.Collections;
class ArrayCollection {
static void Main(string[] args) {
ArrayList objArray = new ArrayList();
objArray.Add("John");
objArray.Add("James");
objArray.Add("Peter");
objArray.RemoveAt(2);
objArray.Insert(2, "William");
Console.WriteLine("Capacity: " + objArray.Capacity);
Console.WriteLine("Count: " + objArray.Count);
Console.WriteLine();
Console.WriteLine("Elements of the ArrayList:");
foreach (string str in objArray) {
Console.WriteLine(str);
}
}
}
Trong đoạn mã trên, phương thức Add()
chèn các giá trị vào thể hiện của lớp tại các vị trí chỉ mục khác nhau. Phương thức RemoveAt()
xóa giá trị James từ vị trí chỉ mục 2 và phương thức Insert()
chèn giá trị “Williams” tại vị trí chỉ mục 2. Phương thức WriteLine()
được sử dụng để hiển thị số lượng phần tử mà danh sách có thể chứa và số lượng phần tử hiện có trong danh sách sử dụng các thuộc tính Capacity
và Count
tương ứng.
Kết quả:
Capacity: 4
Count: 3
Elements of the ArrayList
John
James
William
Lưu ý: Khi bạn cố gắng tham chiếu đến một phần tử ở vị trí lớn hơn kích thước của danh sách, trình biên dịch C# sẽ tạo ra một lỗi.
Đoạn mã bên dưới thể hiện việc sử dụng các phương thức của lớp ArrayList
using System;
using System.Collections;
class Customers {
static void Main(string[] args) {
ArrayList objCustomers = new ArrayList();
objCustomers.Add("Nicole Anderson");
objCustomers.Add("Ashley Thomas");
objCustomers.Add("Garry White");
Console.WriteLine("Fixed Size: " + objCustomers.IsFixedSize);
Console.WriteLine("Count: " + objCustomers.Count);
Console.WriteLine("List of customers:");
foreach (string names in objCustomers) {
Console.WriteLine(names);
}
objCustomers.Sort();
Console.WriteLine("\nList of customers after sorting:");
foreach (string names in objCustomers) {
Console.WriteLine(names);
}
objCustomers.Clear();
Console.WriteLine("Count after removing all elements: " + objCustomers.Count);
}
}
Trong đoạn mã trên, phương thức Insert()
chèn giá trị vào cuối mảng. Các giá trị được chèn vào mảng được hiển thị theo cùng thứ tự trước khi sử dụng phương thức. Sau đó, phương thức hiển thị các giá trị theo thứ tự đã được sắp xếp. Thuộc tính FixedSize
kiểm tra xem mảng có kích thước cố định không. Khi gọi phương thức Reverse()
, nó hiển thị các giá trị theo thứ tự ngược lại. Phương thức Clear()
xóa tất cả các giá trị khỏi mảng.
Hình bên dưới hiển thị việc sử dụng các phương thức của lớp ArrayList
Lớp Hashtable
Lớp Hashtable trong ngôn ngữ lập trình C# tương tự như khu vực lễ tân của một khách sạn, nơi bạn có thể tìm thấy người giữ chìa khóa đang lưu trữ một số lượng lớn các chìa khóa. Mỗi chìa khóa trong ngăn chứa chìa khóa định danh duy nhất một căn phòng, và do đó, mỗi căn phòng được xác định duy nhất bằng chìa khóa của nó. Hình bên dưới minh họa một ví dụ thực tế về các chìa khóa duy nhất.
Tương tự như người giữ chìa khóa, lớp Hashtable
trong C# cho phép bạn tạo các bộ sưu tập dưới dạng các cặp key và value. Nó tạo ra một bảng băm (hashtable) kết hợp các key với giá trị tương ứng của chúng. Lớp Hashtable
sử dụng bảng băm để truy xuất các giá trị được liên kết với key duy nhất của chúng. Bảng băm được tạo ra bởi lớp Hashtable
sử dụng kỹ thuật băm để truy xuất giá trị tương ứng với một key. Băm là quá trình tạo ra mã băm cho key. Mã này được sử dụng để xác định giá trị tương ứng với key đó.
Đối tượng Hashtable
lấy key để tìm kiếm giá trị, thực hiện một hàm băm và tạo ra một mã băm cho key đó. Khi bạn tìm kiếm một giá trị cụ thể bằng cách sử dụng key, mã băm được sử dụng như một chỉ số để định vị bản ghi mong muốn. Ví dụ, tên của một học sinh có thể được sử dụng làm key để lấy số ID học sinh và địa chỉ cư trú tương ứng. Hình bên dưới đại diện cho lớp Hashtable
.
Lớp Hashtable
bao gồm các phương thức và thuộc tính khác nhau được sử dụng để thêm và điều khiển dữ liệu trong hashtable. Các phương thức của lớp Hashtable
cho phép bạn thực hiện một số hành động cụ thể trên dữ liệu trong hashtable. Bảng bên dưới hiển thị các phương thức thông dụng của lớp Hashtable
.
Phương thức | Mô tả |
Add | Thêm một phần tử với key và value được chỉ định |
Remove | Xóa phần tử có key được chỉ định |
CopyTo | Sao chép các phần tử của hashtable vào một mảng tại chỉ số được chỉ định |
ContainsKey | Kiểm tra xem hashtable có chứa key được chỉ định không |
ContainsValue | Kiểm tra xem hashtable có chứa giá trị được chỉ định không |
GetEnumerator | Trả về một DictionaryEnumerator để duyệt qua hashtable |
- Thuộc tính
Thuộc tính của lớp Hashtable
cho phép bạn truy cập và thay đổi dữ liệu trong hashtable. Bảng bên dưới hiển thị các thuộc tính thông dụng của lớp Hashtable
.
Thuộc tính | Mô tả |
Count | Xác định số lượng cặp key và value trong hashtable |
Item | Xác định giá trị, thêm một giá trị mới hoặc sửa đổi giá trị hiện có cho key đã chỉ định |
Keys | Cung cấp một ICollection bao gồm các key trong hashtable |
Values | Cung cấp một ICollection bao gồm các giá trị trong hashtable |
IsReadOnly | Kiểm tra xem Hashtable có phải là chỉ đọc không |
Đoạn mã bên dưới minh họa việc sử dụng các phương thức và thuộc tính của lớp Hashtable
.
using System;
using System.Collections;
class HashCollection {
static void Main(string[] args) {
Hashtable objTable = new Hashtable();
objTable.Add(001, "John");
objTable.Add(002, "Peter");
objTable.Add(003, "James");
objTable.Add(004, "Joe");
Console.WriteLine("Number of elements in the Hashtable: " + objTable.Count);
ICollection objCollection = objTable.Keys;
Console.WriteLine("Original values stored in hashtable are: ");
foreach (int i in objCollection) {
Console.WriteLine(i + ": " + objTable[i]);
}
if (objTable.ContainsKey(002)) {
objTable[002] = "Patrick";
Console.WriteLine("Values stored in the hashtable after removing values:");
foreach (int i in objCollection) {
Console.WriteLine(i + ": " + objTable[i]);
}
}
}
}
Trong đoạn mã trên, phương thức Add()
chèn các khóa và các giá trị tương ứng của chúng vào thể hiện. Thuộc tính Count
hiển thị số lượng phần tử trong bảng băm. Thuộc tính Keys
cung cấp số lượng khóa cho thể hiện của giao diện ICollection
. Phương thức ContainsKey()
kiểm tra xem bảng băm có chứa khóa cụ thể không. Nếu bảng băm chứa khóa cụ thể 002
, thuộc tính Item
mặc định được gọi bằng cách sử dụng cú pháp dấu ngoặc vuông ([]) để thay thế giá trị Peter
bằng giá trị Patrick
. Kết quả này được hiển thị theo thứ tự giảm dần của khóa. Tuy nhiên, kết quả có thể không luôn hiển thị theo thứ tự này. Nó có thể là thứ tự tăng dần hoặc ngẫu nhiên tùy thuộc vào mã băm.
Kết quả:
Number of elements in the hashtable: 4
Original values stored in hashtable are:
4: Joe
3: James
2: Peter
1: John
Values stored in the hashtable after removing values
4: Joe
3: James
2: Patrick
1: John
Đoạn mã bên dưới thể hiện việc sử dụng phương thức và thuộc tính của lớp Hashtable
.
using System;
using System.Collections;
class Authors {
static void Main(string[] args) {
Hashtable objAuthors = new Hashtable();
objAuthors.Add("AU01", "John");
objAuthors.Add("AU04", "Mary");
objAuthors.Add("AU05", "William");
objAuthors.Add("AU11", "Rodrick");
Console.WriteLine("Read-only: " + objAuthors.IsReadOnly);
Console.WriteLine("Count: " + objAuthors.Count);
IDictionaryEnumerator objCollection = objAuthors.GetEnumerator();
Console.WriteLine("\nList of authors:\n");
Console.WriteLine("Author ID \tName");
while (objCollection.MoveNext()) {
Console.WriteLine(objCollection.Key + "\t\t" + objCollection.Value);
}
if (objAuthors.Contains("AU01")) {
Console.WriteLine("\nList contains author with ID AU01");
} else {
Console.WriteLine("List does not contain author with ID AU01");
}
}
}
Trong đoạn mã trên phương thức Add()
chèn các giá trị vào Hashtable
. Phương thức IsReadOnly()
kiểm tra xem các giá trị trong mảng có thể được sửa đổi hay không. Phương thức Contains()
kiểm tra xem giá trị AUO1
có hiện diện trong danh sách không.
Hình bên dưới hiển thị ví dụ về Hashtable
.
Lớp SortedList
The SortedList
class trong C# đại diện cho một bộ sưu tập các cặp khóa và giá trị, trong đó các phần tử được sắp xếp theo khóa. Mặc định, lớp này sắp xếp các phần tử theo thứ tự tăng dần, tuy nhiên điều này có thể thay đổi nếu một đối tượng IComparer
được truyền vào hàm tạo của lớp SortedList
. Các phần tử này có thể được truy cập bằng cách sử dụng các khóa tương ứng hoặc số chỉ mục.
Khi truy cập các phần tử bằng khóa của chúng, lớp SortedList
hoạt động giống như một hashtable, trong khi nếu truy cập các phần tử dựa trên số chỉ mục của chúng, nó hoạt động giống như một mảng.
Lớp SortedList
bao gồm các phương thức và thuộc tính khác nhau được sử dụng để thêm và thao tác dữ liệu trong sorted list.
- Phương thức (Methods)
Các phương thức của lớp SortedList
cho phép bạn thực hiện các hành động trên dữ liệu trong sorted list. Bảng bên dưới hiển thị các phương thức thông thường của lớp SortedList
.
Phương thức | Mô tả |
Add | Thêm một phần tử vào sorted list với khóa và giá trị được chỉ định |
Remove | Xóa phần tử có khóa được chỉ định khỏi sorted list |
GetKey | Trả về khóa tại vị trí chỉ mục được chỉ định |
GetValue | Trả về giá trị tại vị trí chỉ mục được chỉ định |
ContainsKey | Kiểm tra xem instance của lớp SortedList có chứa khóa được chỉ định hay không |
ContainsValue | Kiểm tra xem instance của lớp SortedList có chứa giá trị được chỉ định hay không |
RemoveAt | Xóa phần tử tại chỉ mục được chỉ định |
- Thuộc tính (Properties)
Các thuộc tính của lớp SortedList
cho phép bạn truy cập và sửa đổi dữ liệu trong sorted list. Bảng bên dưới hiển thị các thuộc tính thông thường của lớp SortedList
.
Thuộc tính | Mô tả |
Capacity | Chỉ định số lượng phần tử mà sorted list có thể chứa |
Count | Chỉ định số lượng phần tử trong sorted list |
Item | Trả về giá trị, thêm một giá trị mới hoặc sửa đổi giá trị hiện có cho khóa được chỉ định |
Keys | Trả về các khóa trong sorted list |
Values | Trả về các giá trị trong sorted list |
Đoạn mã bên dưới thể hiện việc sử dụng các phương thức và thuộc tính của lớp SortedList
.
using System;
using System.Collections;
class SortedCollection {
static void Main(string[] args) {
SortedList objSortList = new SortedList();
objSortList.Add("John", "Administration");
objSortList.Add("Jack", "Human Resources");
objSortList.Add("Peter", "Finance");
objSortList.Add("Joel", "Marketing");
Console.WriteLine("Original values stored in the sorted list");
Console.WriteLine("Key \t\t Values");
for (int i = 0; i < objSortList.Count; i++) {
Console.WriteLine(objSortList.GetKey(i) + " \t\t " + objSortList.GetByIndex(i));
}
if (!objSortList.ContainsKey("Jerry")) {
objSortList.Add("Jerry", "Construction");
}
objSortList["Peter"] = "Engineering";
objSortList["Jerry"] = "Information Technology";
Console.WriteLine();
Console.WriteLine("Updated values stored in hashtable");
Console.WriteLine("Key \t\t Values");
for (int i = 0; i < objSortList.Count; i++) {
Console.WriteLine(objSortList.GetKey(i) + " \t\t " + objSortList.GetByIndex(i));
}
}
}
Trong đoạn mã trên, phương thức Add()
chèn các khóa và giá trị tương ứng của chúng vào thể hiện và thuộc tính Count
đếm số phần tử trong sorted list. Phương thức GetKey()
trả về các khóa theo thứ tự từ sorted list trong khi phương thức GetValueByIndex()
trả về các giá trị tại vị trí chỉ mục được chỉ định. Nếu sorted list không chứa khóa được chỉ định Jerry
, thì phương thức Add()
sẽ thêm khóa Jerry
với giá trị tương ứng của nó.
Thuộc tính Item
mặc định được gọi bằng cách sử dụng cú pháp dấu ngoặc vuông ([]) thay thế các giá trị được liên kết với các khóa được chỉ định, Peter
và Jerry
.
Kết quả:
Original values stored in the sorted list
Key Values
Jack Human Resources
Joel Marketing
John Administration
Peter Finance
Updated values stored in hashtable
Key Values
Jack Human Resources
Jerry Information Technology
Joel Marketing
John Administration
Peter Engineering
Đoạn mã bên dưới trình bày cách sử dụng các phương thức trong lớp SortedList
.
using System;
using System.Collections;
class Countries
{
static void Main(string[] args)
{
SortedList objCountries = new SortedList();
objCountries.Add("UK", "United Kingdom");
objCountries.Add("GER", "Germany");
objCountries.Add("USA", "United States of America");
objCountries.Add("AUS", "Australia");
Console.WriteLine("Read-only: " + objCountries.IsReadOnly);
Console.WriteLine("Count: " + objCountries.Count);
Console.WriteLine("List of countries: \n");
Console.WriteLine("Country Code \t Name");
for (int i = 0; i < objCountries.Count; i++)
{
Console.WriteLine(objCountries.GetKey(i) + "\t\t" + objCountries.GetByIndex(i));
}
objCountries.RemoveAt(1);
Console.WriteLine("\nList of countries after removing element at index 1:\n");
Console.WriteLine("Country Code \t Name");
for (int i = 0; i < objCountries.Count; i++)
{
Console.WriteLine(objCountries.GetKey(i) + "\t\t" + objCountries.GetByIndex(i));
}
}
}
Hình bên dưới hiển thị ví dụ về lớp SortedList
.
Trong đoạn mã trên, phương thức Add()
chèn các giá trị vào danh sách. Phương thức IsReadOnly()
kiểm tra xem các giá trị trong danh sách có thể được sửa đổi hay không. Phương thức GetByIndex()
trả về giá trị tại chỉ mục cụ thể. Phương thức RemoveAt()
xóa giá trị tại chỉ mục cụ thể.
Lớp Generic Dictionary
Namespace System.Collections.Generic
này chứa một số lượng lớn các bộ sưu tập generics. Một trong những loại phổ biến nhất trong số này là lớp Dictionary
generic. Nó bao gồm một bộ sưu tập generic các phần tử được tổ chức theo cặp key và value. Nó ánh xạ các key đến các giá trị tương ứng của chúng. Khác với các bộ sưu tập khác trong namespace System.Collections
, nó được sử dụng để tạo ra một bộ sưu tập của một loại dữ liệu duy nhất mỗi lần.
Mỗi phần tử bạn thêm vào từ điển bao gồm một giá trị, được liên kết với khóa tương ứng của nó.
Bạn có thể lấy giá trị từ dictionary bằng cách sử dụng khóa của nó.
Cú pháp sau đây khai báo một lớp Dictionary
kiểu lớp generic:
Dictionary<TKey, TValue>
trong đó,
TKey
: Là tham số kiểu của các khóa được lưu trữ trong thể hiện của lớpDictionary
.TValue
: Là tham số kiểu của các giá trị được lưu trữ trong thể hiện của lớpDictionary
.
Ghi chú: Lớp Dictionary
không cho phép các giá trị null làm thành phần. Sức chứa của lớp Dictionary
là số phần tử mà nó có thể chứa. Tuy nhiên, khi các phần tử được thêm vào sức chứa sẽ tự động tăng.
Lớp generic Dictionary
bao gồm các phương thức và thuộc tính khác nhau được sử dụng để thêm và điều chỉnh các phần tử trong một bộ sưu tập.
- Phương thức (Methods)
Các phương thức của lớp generic Dictionary
cho phép bạn thực hiện các hành động cụ thể trên dữ liệu trong bộ sưu tập. Bảng bên dưới hiển thị các phương thức thông thường của lớp generic Dictionary
.
Phương thức | Mô tả |
Add | Thêm khóa và giá trị cụ thể vào bộ sưu tập |
Remove | Loại bỏ giá trị được liên kết với khóa cụ thể |
ContainsKey | Kiểm tra xem bộ sưu tập có chứa khóa cụ thể không |
ContainsValue | Kiểm tra xem bộ sưu tập có chứa giá trị cụ thể không |
GetEnumerator | Trả về một bộ liệt kê để duyệt qua Dictionary |
GetType | Lấy loại của thể hiện hiện tại |
- Thuộc tính (Properties)
Các thuộc tính của lớp generic Dictionary
cho phép bạn sửa đổi dữ liệu trong bộ sưu tập. Bảng bên dưới hiển thị các thuộc tính thông thường của lớp generic Dictionary
.
Thuộc tính | Mô tả |
Count | Xác định số cặp khóa và giá trị trong bộ sưu tập |
Item | Trả về giá trị, thêm giá trị mới hoặc sửa đổi giá trị hiện có cho khóa cụ thể |
Keys | Trả về bộ sưu tập chứa tất cả các khóa |
Values | Trả về bộ sưu tập chứa tất cả các giá trị |
Đoạn mã bên dưới trình bày cách sử dụng các phương thức và thuộc tính của lớp Dictionary
.
using System;
using System.Collections;
class DictionaryCollection
{
static void Main(string[] args)
{
Dictionary<int, string> objDictionary = new Dictionary<int, string>();
objDictionary.Add(25, "Hard Disk");
objDictionary.Add(30, "Processor");
objDictionary.Add(15, "MotherBoard");
objDictionary.Add(65, "Memory");
ICollection objCollect = objDictionary.Keys;
Console.WriteLine("Original values stored in the collection");
Console.WriteLine("Keys \tValues");
foreach (int i in objCollect)
{
Console.WriteLine(i + "\t" + objDictionary[i]);
}
objDictionary.Remove(65);
Console.WriteLine();
if (objDictionary.ContainsValue("Memory"))
{
Console.WriteLine("Value 'Memory' could not be deleted");
}
else
{
Console.WriteLine("Value 'Memory' deleted successfully");
}
Console.WriteLine();
Console.WriteLine("Values stored after removing element");
Console.WriteLine("Keys \tValues");
Console.WriteLine("----------------");
foreach (int i in objCollect)
{
Console.WriteLine(i + "\t" + objDictionary[i]);
}
}
}
Trong đoạn mã trên, lớp Dictionary
được khởi tạo bằng cách chỉ định hai tham số là int
và string
. Kiểu dữ liệu int
chỉ định các khóa và kiểu dữ liệu string
chỉ định các giá trị. Phương thức Add()
chèn các khóa và giá trị vào thể hiện của lớp Dictionary
. Thuộc tính Keys
cung cấp số lượng khóa cho thể hiện của giao diện ICollection
. Giao diện ICollection
định nghĩa kích thước và các phương thức đồng bộ hóa để thao tác trên bộ sưu tập generic được chỉ định. Phương thức Remove()
loại bỏ giá trị Memory
bằng cách chỉ định khóa liên kết với nó, có giá trị là 65. Phương thức ContainsValue()
kiểm tra xem giá trị Memory có hiện diện trong bộ sưu tập không và hiển thị thông báo phù hợp.
Kết quả:
Original values stored in the collection
Keys Values
----------------
25 Hard Disk
30 Processor
15 MotherBoard
65 Memory
Value Memory deleted successfully
Values stored after removing element
Keys Values
----------------
25 Hard Disk
30 Processor
15 MotherBoard
Đoạn mã bên dưới thể hiện việc sử dụng các phương thức trong lớp generic Dictionary
.
using System;
using System.Collections;
using System.Collections.Generic;
class Car
{
static void Main(string[] args)
{
Dictionary<int, string> objDictionary = new Dictionary<int, string>();
objDictionary.Add(201, "Gear Box");
objDictionary.Add(220, "OilFilter");
objDictionary.Add(330, "Engine");
objDictionary.Add(305, "Radiator");
objDictionary.Add(303, "Steering");
Console.WriteLine("Dictionary class contains values of type");
Console.WriteLine(objDictionary.GetType());
Console.WriteLine("Keys \t\tValues");
Console.WriteLine("__________________________");
IDictionaryEnumerator objDictionaryEnumerator = objDictionary.GetEnumerator();
while (objDictionaryEnumerator.MoveNext())
{
Console.WriteLine(objDictionaryEnumerator.Key.ToString() + "\t\t" + objDictionaryEnumerator.Value);
}
}
}
Trong đoạn mã trên, phương thức Add()
chèn các giá trị vào danh sách. Phương thức GetType()
trả về loại của đối tượng.
Hình bên dưới hiển thị việc sử dụng lớp generic Dictionary
.
Khởi tạo tập hợp (Collection Initializers)
Collection initializers cho phép thêm các phần tử vào các lớp collection thuộc các namespace System.Collections
và System.Collections.Generic
thực hiện giao diện IEnumerable
bằng cách sử dụng các trình khởi tạo phần tử. Các trình khởi tạo phần tử có thể là giá trị đơn giản, một biểu thức hoặc một trình khởi tạo đối tượng. Khi một lập trình viên sử dụng collection initializer, không cần phải cung cấp nhiều phương thức Add()
để thêm các phần tử vào collection, điều này làm cho code ngắn gọn hơn. Trách nhiệm của trình biên dịch là cung cấp các phương thức Add()
khi chương trình được biên dịch.
Đoạn mã bên dưới sử dụng collection initializer để khởi tạo một ArrayList
với các số nguyên.
using System;
using System.Collections;
class Car {
static void Main(string[] args) {
ArrayList nums = new ArrayList { 1, 2, 3 * 6, 4, 5 };
foreach (int num in nums) {
Console.WriteLine("{0}", num);
}
}
}
Trong đoạn mã trên, phương thức Main()
sử dụng collection initializer để tạo một ArrayList
được khởi tạo với các giá trị số nguyên và biểu thức có giá trị là số nguyên.
Kết quả:
1
2
18
4
5
Bởi vì collection thường chứa các đối tượng, collection initializer chấp nhận trình khởi tạo đối tượng để khởi tạo một collection. Đoạn mã bên dưới đây thể hiện một collection initializer khởi tạo một đối tượng Dictionary
generic với các khóa là số nguyên và các đối tượng Employee
.
using System;
using System.Collections;
using System.Collections.Generic;
class Employee
{
public string Name { get; set; }
public string Designation { get; set; }
}
class CollectionInitializerDemo
{
static void Main(string[] args)
{
Dictionary<int, Employee> dict = new Dictionary<int, Employee>()
{
{ 1, new Employee { Name = "Andy Parker", Designation = "SalesPerson" } },
{ 2, new Employee { Name = "Patrick Elvis", Designation = "Marketing Manager" } }
};
// Your further code...
}
}
Đoạn mã trên tạo một lớp Employee
với hai thuộc tính công khai: Name
và Designation
. Phương thức Main()
tạo một đối tượng Dictionary<int, Employee>
và bao gồm trình khởi tạo collection bên trong một cặp dấu ngoặc nhọn. Đối với mỗi phần tử được thêm vào collection, cặp dấu ngoặc nhọn bên trong nhất bao quanh trình khởi tạo đối tượng của lớp Employee
.
Bài tập
Bài 1:
Một thư viện cần quản lý các tài liệu bao gồm Sách, Tạp chí, Báo. Mỗi tài liệu gồm có các thuộc tính sau: Mã tài liệu(Mã tài liệu là duy nhất), Tên nhà xuất bản, số bản phát hành.
Các loại sách cần quản lý thêm các thuộc tính: tên tác giả, số trang.
Các tạp chí cần quản lý thêm: Số phát hành, tháng phát hành.
Các báo cần quản lý thêm: Ngày phát hành.
Yêu cầu 1: Xây dựng các lớp để quản lý tài liệu cho thư viện một cách hiệu quả.
Yêu cầu 2: Xây dựng lớp QuanLySach có các chức năng sau
- Thêm mới tài liêu: Sách, tạp chí, báo.
- Xoá tài liệu theo mã tài liệu.
- Hiện thị thông tin về tài liệu.
- Tìm kiếm tài liệu theo loại: Sách, tạp chí, báo.
- Thoát khỏi chương trình
Bài 2:
Các thí sinh dự thi đại học bao gồm các thí sinh thi khối A, B, và khối C. Các thí sinh cần quản lý các thông tin sau: Số báo danh, họ tên, địa chỉ, mức ưu tiên.
Thí sinh thi khối A thi các môn: Toán, Lý, Hoá.
Thí sinh thi khối B thi các môn: Toán, Hoá, Sinh.
Thí sinh thi khối C thi các môn: Văn, Sử, Địa.
Yêu cầu 1: Xây dựng các lớp để quản lý các thi sinh dự thi đại học.
Yêu cầu 2: Xây dựng lớp TuyenSinh có các chức năng:
- Thêm mới thí sinh.
- Hiện thị thông tin của thí sinh và khối thi của thí sinh.
- Tìm kiếm theo số báo danh.
- Thoát khỏi chương trình.