Generics (lớp và phương thức)
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 42 trên 70.
Generic cho phép bạn viết các lớp và phương thức hoạt động với mọi kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu. Thay vì viết các lớp riêng cho IntList, StringList và DoubleList, bạn chỉ cần viết một lớp generic xử lý tất cả.
Lớp tổng quát sử dụng một tham số kiểu, thường được đặt tên là T và đặt trong dấu ngoặc nhọn:
public class Box<T>
{
private T content;
public void Store(T item) => content = item;
public T Retrieve() => content;
}
// Cách dùng với các kiểu dữ liệu khác nhau
Box<int> intBox = new Box<int>();
intBox.Store(42);
Box<string> stringBox = new Box<string>();
stringBox.Store("Hello");Khi tạo Box<int>, trình biên dịch thay mọi T bằng int. Cách này giúp kiểm tra kiểu dữ liệu ngay khi biên dịch, bạn không thể vô tình lưu chuỗi vào hộp int.
Phương thức generic (generic method) hoạt động tương tự, cho phép một phương thức xử lý nhiều kiểu dữ liệu khác nhau:
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y); // x=2, y=1Trình biên dịch suy luận kiểu dữ liệu từ các đối số, nên bạn không cần viết tường minh Swap<int>. Kiểu tổng quát là nền tảng của các cấu trúc tập hợp như List<T> và Dictionary<TKey, TValue>, giúp bạn viết code có thể tái sử dụng và an toàn về kiểu dữ liệu.
Thử thách
DễHãy xây dựng một hệ thống Storage<T> tổng quát có thể chứa và quản lý các phần tử thuộc mọi kiểu. Bạn sẽ tạo một lớp vùng chứa có thể tái sử dụng để minh họa sức mạnh của kiểu tổng quát: viết một lần, dùng với số nguyên, chuỗi hoặc bất kỳ kiểu nào khác!
Bạn sẽ sắp xếp code trong hai tệp:
Storage.cs: Tạo một lớpStorage<T>tổng quát trong không gian tênDataStorage. Vùng chứa lưu trữ của bạn cần:- Có một mảng kiểu
Tở phạm vi riêng tư để chứa các phần tử và thuộc tínhCounttheo dõi số phần tử đang được lưu trữ - Nhận sức chứa trong hàm khởi tạo để khởi tạo mảng bên trong
- Có phương thức
Add(T item)để thêm một phần tử nếu còn chỗ - Có phương thức
Get(int index)để trả về phần tử tại chỉ số được chỉ định - Có phương thức
GetAll()để trả về tất cả phần tử đã lưu trữ, nối chúng bằng", "
- Có một mảng kiểu
Program.cs: Trong tệp chính, hãy minh họa cách lớp tổng quát hoạt động với hai kiểu khác nhau. Tạo mộtStorage<int>và mộtStorage<string>, thêm các phần tử vào từng vùng chứa và cho thấy cùng một lớp hoạt động trơn tru với cả hai kiểu.
Bạn sẽ nhận bốn đầu vào:
- Số nguyên thứ nhất cần lưu trữ
- Số nguyên thứ hai cần lưu trữ
- Chuỗi thứ nhất cần lưu trữ
- Chuỗi thứ hai cần lưu trữ
Tạo một vùng chứa số nguyên có sức chứa 5 và một vùng chứa chuỗi có sức chứa 5. Thêm hai số nguyên vào vùng chứa số nguyên và hai chuỗi vào vùng chứa chuỗi.
In đầu ra theo định dạng này:
Int storage: {GetAll()}
Int count: {Count}
First int: {Get(0)}
String storage: {GetAll()}
String count: {Count}
First string: {Get(0)}Ví dụ, nếu đầu vào là 42, 100, Hello và World, đầu ra sẽ là:
Int storage: 42, 100
Int count: 2
First int: 42
String storage: Hello, World
String count: 2
First string: HelloLưu ý rằng bạn chỉ cần viết lớp Storage<T> một lần, nhưng lớp này hoạt động hoàn hảo với cả int và string. Đó chính là điều kỳ diệu của kiểu tổng quát! Trình biên dịch đảm bảo an toàn kiểu dữ liệu, vì vậy bạn không thể vô tình thêm một chuỗi vào vùng chứa số nguyên.
Tự làm thử
using System;
using DataStorage;
class Program
{
public static void Main(string[] args)
{
// Đọc dữ liệu đầu vào
int num1 = Convert.ToInt32(Console.ReadLine());
int num2 = Convert.ToInt32(Console.ReadLine());
string str1 = Console.ReadLine();
string str2 = Console.ReadLine();
// TODO: Tạo Storage<int> có sức chứa 5
// TODO: Thêm hai số nguyên vào nơi lưu trữ số nguyên
// TODO: Tạo Storage<string> có sức chứa 5
// TODO: Thêm hai chuỗi vào nơi lưu trữ chuỗi
// TODO: In đầu ra theo định dạng yêu cầu
// Int storage: {GetAll()}
// Số lượng số nguyên: {Count}
// Số nguyên đầu tiên: {Get(0)}
// String storage: {GetAll()}
// String count: {Count}
// First string: {Get(0)}
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online