Sử dụng Enum trong CSharp

Kiểu dữ liệu enum giúp ta định nghĩa một tập các giá trị nguyên khó nhớ thành một tập các định danh bằng chữ gợi ý nghĩa, giúp ta dễ đọc, hiểu mã nguồn và dễ sử dụng hơn.

Với đoạn mã sau:

int GetToday() {
	// ...
}

// .....
int today = GetToday();
if ( today == 8 ) Output("Hôm nay được nghỉ vì là chủ nhật");

với enum, đoạn code trên được viết lại tường minh hơn:

DayOfWeek GetToday() {
	// ...
}

// .....
DayOfWeek today = GetToday();
if (today == DayOfWeek.Sunday) Output("Hôm nay được nghỉ vì là chủ nhật");

Khai báo

Ta sử dụng từ khóa enum (như trong C và C++) để khai báo kiểu dữ liệu enum. Cũng giống như class và struct, enum có thể khai báo độc lập, khai báo trong một namespace, trong class hoặc trong struct.

namespace tek {
	public enum DayOfWeek {
		Monday,
		Tuesday,
		Wednesday,
		Thursday,
		Friday,
		Saturday,
		Sunday
	}
}

Về bản chất các giá trị enum Mon, Tue… là các số nguyên được gắn mác. Mặc định, Mon có giá trị bằng 0, Tue bằng 1, các giá trị sau tăng dần 1 đơn vị. Có thể chỉ định tường minh giá trị có một số hoặc tất cả:

public enum DayOfWeek {
	Monday = 2,
	Tuesday = 3,
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday
}

Ta có thể dùng các giá trị enum được khai báo trước đó để định nghĩa cho các enum ở sau:

public enum DayOfWeek {
	Monday = 2,
	Tuesday = 3,
	Wednesday,
	Thursday = Wednesday + 1,
	Friday,
	Saturday,
	Sunday
}

Mặc định, giá trị của các enum là số nguyên với khai báo trên là ngầm định của khai báo sau:
public enum DayOfWeek : int { /* … */ }
nếu muốn chỉ định các kiểu dữ liệu nguyên khác làm dữ liệu lưu trữ cho enum thì phải khai báo tường minh. Ví dụ:

public enum DayOfWeek : byte { /* ... */ }

Các cách sử dụng

Enum có một vài cách sử dụng:

Giới hạn giá trị nguyên

Với một kiểu enum xác định thì nó được định nghĩa rõ ràng các giá trị được chấp nhận.
Trong Java với ví dụ trên để viết code được rõ ràng, dễ hiểu thì phải cài đặt như sau:

class Program {
	public static final int MONDAY = 2;
	public static final int TUESDAY = 3;
	public static final int WEDNESDAY = 4;
	public static final int THURSDAY = 5;
	public static final int FRIDAY = 6;
	public static final int SATURDAY = 7;
	public static final int SUNDAY = 8;

	int getToday() {
		// .....
	}

	boolean isSunday(int day) {
		return today == Program.SUNDAY;
	}

	public static void main(String []args) {
		if (isSunday(Program.SUNDAY)) System.out.print("Hôm nay được nghỉ vì là chủ nhật");
	}
}

Với hàm isSunday ở trên, ta hoàn toàn có thể truyền một số nguyên bất kỳ, trong quá trình dịch hoàn toàn không có lỗi hay cảnh báo gì vì nó hoàn toàn hợp lệ.
Với cách sử dụng enum, hàm isSunday được viết lại thành:

bool IsSunday(DayOfWeek day){ ... }

lỗi sẽ sinh ra nếu ta không truyền đúng các giá trị ngày hợp lệ!

(Trong Java cũng đã có hỗ trợ kiểu enum nhưng không được đầy đủ như trong C#)

Sử dụng giá trị nguyên

Bản chất lưu trữ bên dưới của enum là số nguyên nên ta dễ dàng lấy lại giá trị của nó bằng cách ép kiểu

var x = (int)DayOfWeek.Sunday;
//  x = 8
var y = (DayOfWeek)7
//  y = DayOfWeek.Saturday

Ví dụ tình huống sau sử dụng giá trị nguyên:
Có bảng Person lưu trữ trong SQL gồm vài trường cơ bản trong đó có trường Giới tính (Gender) nhận các giá trị: 0 – chưa có thông tin, 1 – nam, 2 – nữ. Có lớp Person để chứa thông tin về 1 người. Lớp Person có thuộc tính Gender kiểu enum GenderType {Unknown, Male, Female} để tường minh hơn thay vì dùng kiểu nguyên (0, 1, 2). Nhưng khi lưu trữ từ class xuống database buộc phải sử dụng (int)person.Gender để lấy về giá trị nguyên; dùng person.Gender = (GenderType)dbGenderValue khi lấy dữ liệu từ database cho vào class.
Lưu ý: nếu giá dbGenderValue không nằm trong giá trị được định nghĩa trong enum thì sẽ có Exception

Sử dụng tên của enum

Để lấy được tên của enum khi biết giá trị của nó, đơn giản ta dùng hàm ToString

var x = DayOfWeek.Sunday;
var s = x.ToString()
//  s = Sunday

Tình huống trên thường gặp khi ta muốn hiển thị giá trị của biến kiểu enum cho người dùng thay vì hiển thị giá trị nguyên 0, 1, 2 không có mấy ý nghĩa.

Khi có giá trị chuỗi, muốn chuyển đổi thành giá trị enum, ta làm như sau:

var s = "Friday";
var x = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), s);
//  x = DayOfWeek.Friday

Lưu ý: nếu s không nằm trong một trong các tên của DayOfWeek thì sẽ sinh ra Exception. Có thể dùng hàm TryParse để vừa kiểm tra, vừa chuyển đổi.

Tình huống trên thường gặp trong khi nhập dữ liệu do người dùng nhập vào.

Enum và bit

Với khai báo Atttribute [Flags] cho enum, ta có thể định nghĩa được một enum thực hiện được các thao tác bitwise và dùng làm biến lưu nhiều lựa chọn được:

[Flags]
public enum DayOfWeek {
	Monday = 1 << 1,
	Tuesday = 1 << 2,
	Wednesday = 1 << 3,
	Thursday = 1 << 4,
	Friday = 1 << 5,
	Saturday = 1 << 6,
	Sunday = 1 << 7
}

Các giá trị của enum đều có giá trị 2^n:

Tên Bit 31 7 6 5 4 3 2 1 0
Monday 0 0 0 0 0 0 0 0 1
Tuesday 0 0 0 0 0 0 0 1 0
Wednesday 0 0 0 0 0 0 1 0 0
Thursday 0 0 0 0 0 1 0 0 0
Friday 0 0 0 0 1 0 0 0 0
Saturday 0 0 0 1 0 0 0 0 0
Sunday 0 0 1 0 0 0 0 0 0

Khi đó ta có thể lưu danh sách các ngày: ví dụ như các ngày đi học (Thứ 2, 3, 5, 7), ngày nghỉ (thứ 7, CN) … trong một biến enum DayOfWeek duy nhất:

var schoolDay = DayOfWeek.Monday | DayOfWeek.Tuesday | DayOfWeek.Thursday | DayOfWeek.Saturday;
var weekend = DayOfWeek.Saturday | DayOfWeek.Sunday;

Cách thực hiện và lưu trữ cho trường hợp trên là sử dụng các toán tử đối với bit (bitwise):

Tên Bit 31 7 6 5 4 3 2 1 0
Monday 0 0 0 0 0 0 0 0 1
Tuesday 0 0 0 0 0 0 0 1 0
Thursday 0 0 0 0 0 1 0 0 0
schoolDay 0 0 0 1 0 1 0 1 1

Khi đó schoolDay có dạng nhị phân là 101011 = 43

Khi có giá trị schoolDay, muốn kiểm tra xem có chứa ngày nào đó không ta thực hiện AND bit:

bool checkDay(DayOfWeek day, DayOfWeek single){
	return day & single != 0;
}

checkDay(schoolDay, DayOfWeek.Tuesday); // true
checkDay(schoolDay, DayOfWeek.Friday); // false
Tên Bit 31 7 6 5 4 3 2 1 0
schoolDay 0 0 0 1 0 1 0 1 1
Tuesday 0 0 0 0 0 0 0 1 0
Kết quả
0 0 0 0 0 0 0 1 0

Kết quả là 10 khác 0 nên schoolDay có ngày thứ 3

Tên Bit 31 7 6 5 4 3 2 1 0
schoolDay 0 0 0 1 0 1 0 1 1
Fridy 0 0 0 0 1 0 0 0 0
Kết quả
0 0 0 0 0 0 0 0 0

Kết quả bằng 0 nên schoolDay không có ngày thứ 6.

Ngoài việc sử dụng toán từ OR bit | và AND bit &, ta có thể sử dụng các toán tử bitwise khác như: NOT và XOR

Giới hạn của Flags enum là nó chỉ có tương đối ít các giá trị đơn do byte chỉ có 8 bit, int có 32 bit.