Enums
Định nghĩa
Một enum (kiểu liệt kê - Enumeration) là một kiểu giá trị (Value Type) định nghĩa một tập hợp các hằng số có tên (Named Constants). Bên trong, mỗi thành viên enum ánh xạ đến một giá trị số nguyên, biến chúng thành các lựa chọn an toàn kiểu (Type-Safe) và dễ đọc thay cho các "con số ma thuật" (Magic Numbers).
Khái niệm cốt lõi
Khai báo và sử dụng cơ bản
public enum Season
{
Spring,
Summer,
Autumn,
Winter
}
Season current = Season.Summer;
Console.WriteLine(current); // "Summer"
Console.WriteLine((int)current); // 1 (zero-based by default)
Kiểu dữ liệu nền tảng (Underlying Type)
Mặc định, enum sử dụng int. Bạn có thể chỉ định bất kỳ kiểu số nguyên nào: byte, sbyte, short, ushort, int, uint, long, ulong.
public enum HttpStatus : ushort
{
OK = 200,
NotFound = 404,
InternalServerError = 500
}
Giá trị tùy chỉnh (Custom Values)
Bạn có thể gán giá trị rõ ràng. Các thành viên không được gán sẽ tiếp tục từ giá trị trước đó + 1.
public enum HttpStatusCode
{
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404
}
Ví dụ mã
Các phương thức Enum
public enum Color { Red, Green, Blue }
// Get name from value
string name = Enum.GetName(typeof(Color), 1); // "Green"
string name2 = Enum.GetName<Color>(2); // "Blue" (generic overload)
// Get all names
string[] names = Enum.GetNames<Color>(); // ["Red", "Green", "Blue"]
// Get all values
Color[] values = Enum.GetValues<Color>(); // [Red, Green, Blue]
// Parse from string
Color parsed = Enum.Parse<Color>("Green"); // Color.Green
// Try parse (safe)
if (Enum.TryParse<Color>("RED", ignoreCase: true, out var result))
Console.WriteLine(result); // Color.Red
// Check if a value is defined
bool exists = Enum.IsDefined(typeof(Color), 5); // false
bool exists2 = Enum.IsDefined<Color>(Color.Red); // true
// Get underlying numeric value
int numeric = Convert.ToInt32(Color.Blue); // 2
Ép kiểu giữa Enum và kiểu nền tảng (Casting)
Color color = Color.Green;
int value = (int)color; // 1
Color fromInt = (Color)1; // Color.Green
// Be careful — invalid casts compile but produce undefined enum values
Color invalid = (Color)99; // compiles! IsDefined returns false
C# cho phép ép bất kỳ giá trị số nguyên nào sang enum, ngay cả khi không có thành viên nào có giá trị đó. Luôn xác thực bằng Enum.IsDefined khi nhận giá trị enum từ các nguồn bên ngoài (API, cơ sở dữ liệu, đầu vào người dùng).
Thuộc tính [Flags]
Thuộc tính [Flags] cho phép một biến enum duy nhất chứa sự kết hợp của nhiều giá trị bằng các phép toán bit (Bitwise Operations).
[Flags]
public enum Permissions
{
None = 0b_0000, // 0
Read = 0b_0001, // 1
Write = 0b_0010, // 2
Execute = 0b_0100, // 4
All = Read | Write | Execute // 7
}
// Combining flags
Permissions myPerms = Permissions.Read | Permissions.Write;
Console.WriteLine(myPerms); // "Read, Write"
// Checking with HasFlag
bool canRead = myPerms.HasFlag(Permissions.Read); // true
bool canExec = myPerms.HasFlag(Permissions.Execute); // false
// Bitwise check (equivalent, slightly faster)
bool canRead2 = (myPerms & Permissions.Read) == Permissions.Read; // true
Switch với Enum
public string GetSeasonMessage(Season season) => season switch
{
Season.Spring => "Flowers are blooming",
Season.Summer => "Time for the beach",
Season.Autumn => "Leaves are falling",
Season.Winter => "Bundle up!",
_ => "Unknown season" // handles future/invalid values
};
Khi sử dụng switch trên enum, trình biên dịch sẽ cảnh báo nếu bạn bỏ sót một thành viên. Tuy nhiên, luôn luôn bao gồm trường hợp _ (discard) hoặc default vì một phép ép kiểu như (Season)99 có thể vượt qua các ràng buộc enum tại thời điểm chạy.
Enum vs Lớp hằng số vs Smart Enum
// Approach 1: Enum — simple, fast, but no behavior
public enum OrderStatus { Pending, Shipped, Delivered, Cancelled }
// Approach 2: Constant class — groups constants but no type safety
public static class OrderStatuses
{
public const string Pending = "Pending";
public const string Shipped = "Shipped";
}
// Approach 3: Smart Enum pattern — encapsulates behavior with each value
public abstract class SmartOrderStatus : Enumeration
{
public static readonly SmartOrderStatus Pending = new PendingStatus();
public static readonly SmartOrderStatus Shipped = new ShippedStatus();
public abstract bool CanTransitionTo(SmartOrderStatus next);
private class PendingStatus : SmartOrderStatus
{
public override bool CanTransitionTo(SmartOrderStatus next) =>
next == Shipped || next == Cancelled;
}
private class ShippedStatus : SmartOrderStatus
{
public override bool CanTransitionTo(SmartOrderStatus next) =>
next == Delivered;
}
}
Khi nào sử dụng Enum
| Cách tiếp cận | Khi nào sử dụng |
|---|---|
| Enum | Tập hợp cố định các hằng số có tên, không cần hành vi, so sánh nhanh |
| Lớp hằng số (Constant class) | Giá trị chuỗi, API tương thích ngược, các tình huống tuần tự hóa |
| Smart Enum | Mỗi giá trị cần hành vi hoặc quy tắc xác thực đi kèm |
Thực hành tốt nhất
- Sử dụng tên số ít cho enum thông thường (
Color,Season). - Sử dụng tên số nhiều cho enum có
[Flags](Permissions,Options). - Đối với enum
[Flags], luôn bao gồm thành viênNone = 0. - Gán lũy thừa của 2 (1, 2, 4, 8...) cho các thành viên
[Flags]. - Sử dụng
Enum.IsDefinedđể xác thực giá trị enum từ đầu vào bên ngoài. - Tránh sử dụng enum trong ranh giới API công khai giữa các dịch vụ — ưu tiên chuỗi hoặc số nguyên cho tuần tự hóa, vì giá trị enum có thể thay đổi giữa các phiên bản.
[Flags]
public enum FileAttributes
{
None = 0,
ReadOnly = 1 << 0, // 1
Hidden = 1 << 1, // 2
System = 1 << 2, // 4
Archive = 1 << 3, // 8
Compressed = 1 << 4 // 16
}
Lỗi thường gặp
- Ép số nguyên không hợp lệ —
(MyEnum)999biên dịch được nhưng tạo ra một giá trị không có tên định nghĩa. Xác thực bằngEnum.IsDefined. - Hiệu suất của Enum.IsDefined — sử dụng reflection bên trong. Đối với các đường dẫn nóng (Hot Paths), cân nhắc sử dụng
HashSet<T>hoặc xác thực dựa trên switch. - Enum trong API công khai — nếu bạn thêm hoặc đổi tên một thành viên, nó có thể làm hỏng tuần tự hóa. Cân nhắc sử dụng số nguyên hoặc chuỗi tại ranh giới dịch vụ và ánh xạ sang enum nội bộ.
- Sự mơ hồ của giá trị 0 (Zero Value) — giá trị mặc định của mọi enum là
0. Nếu không có thành viên nào được gán0, một biến được khởi tạo mặc định sẽ giữ một giá trị không có tên. Luôn định nghĩa rõ ràngNone = 0hoặc một thành viên 0 có ý nghĩa.
public enum Status
{
Active = 1, // no zero value defined
Inactive = 2
}
Status s = default; // s == 0, which is NOT Active or Inactive — potential bug
Điểm chính cần nhớ
- Enum ánh xạ các hằng số có tên sang kiểu số nguyên, cung cấp an toàn kiểu (Type Safety) và khả năng đọc.
- Sử dụng
[Flags]cho các kết hợp bit và luôn sử dụng lũy thừa của 2. - Luôn xác thực giá trị enum từ các nguồn bên ngoài bằng
Enum.IsDefined. - Ưu tiên tên số ít cho enum thông thường, số nhiều cho cờ (Flags).
- Cân nhắc mẫu Smart Enum khi giá trị enum cần hành vi đi kèm.
Câu hỏi phỏng vấn
H: Enum là gì? Đ: Enum là một kiểu giá trị (Value Type) định nghĩa một tập hợp các hằng số số nguyên có tên. Nó cải thiện khả năng đọc mã và an toàn kiểu bằng cách thay thế các con số ma thuật bằng các tên có ý nghĩa.
H: Enum có [Flags] hoạt động như thế nào?
Đ: Thuộc tính [Flags] chỉ ra rằng enum đại diện cho một tập hợp các trường bit. Mỗi thành viên được gán một lũy thừa của 2 để chúng có thể được kết hợp bằng | (OR) và kiểm tra bằng HasFlag hoặc & (AND). Đầu ra ToString() hiển thị tất cả các tên khớp dưới dạng danh sách phân tách bằng dấu phẩy.
H: Giá trị mặc định của enum là gì?
Đ: Giá trị mặc định luôn là 0 (giá trị 0 của kiểu số nguyên nền tảng). Nếu không có thành viên nào được gán rõ ràng 0, giá trị mặc định sẽ là một giá trị không có tên. Thực hành tốt nhất là luôn định nghĩa thành viên None = 0.
H: Bạn có thể mở rộng enum không? Đ: Không. Enum là sealed và không thể kế thừa hoặc mở rộng tại thời điểm biên dịch. Bạn có thể thêm giá trị mới vào định nghĩa enum, nhưng đây là một thay đổi phá vỡ (Breaking Change) cho người sử dụng. Đối với hành vi có thể mở rộng, sử dụng mẫu Smart Enum hoặc cách tiếp cận dựa trên chiến lược (Strategy Pattern).
H: Sự khác biệt giữa Enum.Parse và Enum.TryParse là gì?
Đ: Enum.Parse ném ngoại lệ nếu chuỗi không khớp với bất kỳ thành viên nào. Enum.TryParse trả về giá trị boolean cho biết thành công và xuất kết quả qua tham số out — đây là lựa chọn an toàn hơn cho đầu vào tại thời điểm chạy.