Generics
Định nghĩa
Generics cho phép bạn định nghĩa các lớp, giao diện, phương thức và ủy quyền có tham số kiểu (Type Parameters). Bằng cách giới thiệu các tham số kiểu (ví dụ <T>), bạn viết mã hoạt động với bất kỳ kiểu dữ liệu nào trong khi vẫn giữ an toàn kiểu tại thời điểm biên dịch (Compile-Time Type Safety) và tránh chi phí boxing/unboxing.
Tại sao Generics tồn tại
| Vấn đề không có Generics | Giải pháp với Generics |
|---|---|
ArrayList lưu trữ object — boxing kiểu giá trị gây chi phí | List<T> lưu trữ giá trị trực tiếp — không boxing |
Ép kiểu từ object có thể thất bại tại thời điểm chạy | Lỗi kiểu được phát hiện tại thời điểm biên dịch |
| Mã trùng lặp cho mỗi kiểu dữ liệu | Một triển khai cho mọi kiểu |
// Without generics — runtime risk, boxing overhead
ArrayList list = new ArrayList();
list.Add(42); // boxes int to object
int value = (int)list[0]; // unboxes, runtime cast
// With generics — compile-time safety, no boxing
List<int> list = new List<int>();
list.Add(42); // no boxing
int value = list[0]; // no cast needed
- An toàn kiểu (Type Safety) tại thời điểm biên dịch
- Tái sử dụng mã (Code Reuse) mà không trùng lặp
- Hiệu suất tốt hơn — không boxing cho kiểu giá trị
- Mã tự tài liệu thông qua các tham số kiểu rõ ràng
Khái niệm cốt lõi
Lớp Generic (Generic Classes)
Các lớp generic sử dụng một hoặc nhiều tham số kiểu trong định nghĩa của chúng. Thư viện .NET Base Class Library đi kèm nhiều bộ sưu tập generic.
// Built-in generic classes
List<string> names = new List<string> { "Alice", "Bob" };
Dictionary<string, int> ages = new Dictionary<string, int>
{
["Alice"] = 30,
["Bob"] = 25
};
// Custom generic class
public class Repository<T>
{
private readonly List<T> _items = new();
public void Add(T item) => _items.Add(item);
public T? FindById(Func<T, bool> predicate) =>
_items.FirstOrDefault(predicate);
public IReadOnlyList<T> GetAll() => _items.AsReadOnly();
}
Phương thức Generic (Generic Methods)
Các phương thức có thể giới thiệu tham số kiểu riêng độc lập với lớp chứa. Trình biên dịch thường suy luận các đối số kiểu từ cách sử dụng.
public class Utilities
{
// Generic method with type inference
public T GetDefault<T>() => default(T);
// Explicit type arguments when inference is not possible
public T ConvertTo<T>(object value) => (T)value;
// Generic method with constraint
public T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
}
// Usage
Utilities util = new Utilities();
int max = util.Max(10, 20); // type inferred as int
string def = util.GetDefault<string>(); // explicit — returns null
Giao diện Generic (Generic Interfaces)
// Built-in
public interface IComparable<T>
{
int CompareTo(T? other);
}
// Custom generic interface
public interface IRepository<T> where T : class
{
T GetById(int id);
void Add(T entity);
void Delete(T entity);
IEnumerable<T> GetAll();
}
public class InMemoryRepository<T> : IRepository<T> where T : class
{
private readonly List<T> _store = new();
public T GetById(int id) => throw new NotImplementedException();
public void Add(T entity) => _store.Add(entity);
public void Delete(T entity) => _store.Remove(entity);
public IEnumerable<T> GetAll() => _store;
}
Ủy quyền Generic (Generic Delegates)
// Built-in generic delegates
Func<int, int, int> add = (a, b) => a + b; // returns int
Action<string> log = msg => Console.WriteLine(msg); // returns void
Predicate<int> isEven = n => n % 2 == 0; // returns bool
// Custom generic delegate
public delegate TResult Transformer<TInput, TResult>(TInput input);
Ràng buộc (Constraints)
Ràng buộc giới hạn các kiểu có thể được sử dụng làm đối số kiểu, cho phép trình biên dịch biết những thao tác nào có sẵn.
| Ràng buộc | Mô tả |
|---|---|
where T : struct | T phải là kiểu giá trị (Value Type) |
where T : class | T phải là kiểu tham chiếu (Reference Type) |
where T : new() | T phải có hàm tạo không tham số |
where T : BaseClass | T phải kế thừa từ BaseClass |
where T : IInterface | T phải triển khai IInterface |
where T : unmanaged | T phải là kiểu không quản lý (không có trường tham chiếu) |
where T : notnull | T phải là kiểu không nullable |
where T : Enum | T phải là enum |
// Multiple constraints
public class Factory<T> where T : class, new()
{
public T Create() => new T();
}
// Constraint on multiple type parameters
public class Pair<TFirst, TSecond>
where TFirst : struct
where TSecond : class
{
public TFirst First { get; }
public TSecond Second { get; }
}
Biểu thức giá trị mặc định (Default Value Expression)
T value = default(T); // null for reference types, zero-initialized for value types
// In generic context
public T? FindOrDefault<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (T item in source)
if (predicate(item)) return item;
return default; // default(T) — shorthand
}
Đồng biến và Nghịch biến (Covariance and Contravariance)
Các chú thích phương sai (Variance Annotations) cho phép tham số kiểu generic được sử dụng đa hình.
// Covariance (out) — type can only appear as output
IEnumerable<Derived> derived = new List<Derived>();
IEnumerable<Base> bases = derived; // valid because IEnumerable<out T>
// Contravariance (in) — type can only appear as input
IComparer<Base> baseComparer = new BaseComparer();
IComparer<Derived> derivedComparer = baseComparer; // valid because IComparer<in T>
// Custom covariant interface
public interface IProducer<out T>
{
T Produce();
}
// Custom contravariant interface
public interface IConsumer<in T>
{
void Consume(T item);
}
- Phương sai chỉ áp dụng cho kiểu giao diện và ủy quyền, không phải lớp hoặc struct.
- Tham số
outchỉ dùng cho đầu ra; tham sốinchỉ dùng cho đầu vào. - Kiểu giá trị không hỗ trợ phương sai —
IEnumerable<int>không thể gán choIEnumerable<object>.
Suy luận kiểu Generic (Generic Type Inference)
Trình biên dịch có thể suy luận đối số kiểu từ các đối số phương thức, làm cho các lệnh gọi gọn gàng hơn.
// Compiler infers T from arguments
var result = Max(3, 5); // T inferred as int
var names = new[] { "a", "b" };
var first = names.FirstOrDefault(); // T inferred as string
Khi nào sử dụng
- Bộ sưu tập và cấu trúc dữ liệu — bất kỳ khi nào bạn cần một container hoạt động với nhiều kiểu.
- Repository và service — mẫu
IRepository<T>cho tầng truy cập dữ liệu. - Phương thức tiện ích — hoán đổi (swap), max, min, chuyển đổi (convert) và các thao tác không phụ thuộc kiểu.
- Xử lý sự kiện và callback —
EventHandler<TEventArgs>,Func<T>,Action<T>. - Tránh khi chỉ có một hoặc hai kiểu cụ thể và không có lợi ích tái sử dụng — sự đơn giản thắng.