Biến chỉ đọc (readonly) và hằng (const) – C# best practices

1

Khi học lập trình C# bạn hẳn đã nhận ra trong C# bạn có thể khai báo hai loại hằng (constant): một loại dùng từ khóa const, một loại dùng từ khóa readonly (mà nhiều tài liệu thường gọi là biến chỉ đọc). Mặc dù cùng để lưu những giá trị không thay đổi nhưng giữa chúng có những điểm khác biệt quan trọng bạn cần biết để quyết định sử dụng cái nào khi viết chương trình.

Trong bài viết này chúng ta sẽ trao đổi về sự khác nhau giữa hai loại hằng đó trong C#. Bạn sẽ được biết khi nào nên sử dụng loại hằng nào cũng ưu nhược điểm của từng loại hằng.

Compile-time và Runtime constant, hay const và readonly

Trong C# có hai loại hằng (constant): compile-time constant và runtime constant. Compile-time constant là loại hằng được xác định giá trị (và không đổi) ngay từ giai đoạn dịch mã nguồn. Runtime constant là loại hằng mà giá trị được xác định lần đầu khi chương trình chạy và sau đó không thay đổi giá trị nữa.

Khi viết code, compile-time constant được định nghĩa qua từ khóa const, còn runtime constant được định nghĩa bằng từ khóa readonly.

Ví dụ:

// Compile-time constant:
public const int Millennium = 2000;
// Runtime constant:
public static readonly int ThisYear = 2019;

Compile-time constant – const

Đây là loại hằng quen thuộc trong các ngôn ngữ lập trình họ C/C++. Trong C#, compile-time constant có thể được khai báo làm thành viên của class/struct, cũng như có thể khai báo trong thân của phương thức.

Loại hằng này phải được gán giá trị xác định ngay từ lúc viết code. Khi biên dịch, C# compiler sẽ copy giá trị này tới tất cả các vị trí sử dụng. Lấy ví dụ, nếu bạn khai báo hằng:

public const int Millennium = 2000;

Sau đó trong code bạn sử dụng hằng này:

if (myDateTime.Year == Millennium) { … }

Trên thực tế, khi biên dịch mã nguồn sang IL, C# compiler sẽ thực hiện copy giá trị 2000 sang các vị trí nó bắt gặp tên “Millennium”, giống hệt như khi bạn tự viết:

if (myDateTime.Year == 2000) { … }

Việc copy giá trị này thậm chí diễn ra ngay cả khi bạn tham chiếu đến compile-time constant từ một assembly (chương trình/thư viện) khác. Nghĩa là, nếu trong class A thuộc thư viện Lib1 bạn có một hằng số Pi = 3.14. Nếu trong class B thuộc thư viện Lib2 bạn tham chiếu tới Pi thì thực tế giá trị 3.14 được copy sang code của Lib2.

Với đặc điểm hoạt động như trên, compile-time constant chỉ có thể nhận được các giá trị thuộc kiểu cơ sở của C# (và .NET) như số nguyên, số thực, chuỗi, enum, null. Bạn không thể sử dụng các loại giá trị phức tạp (cần khởi tạo bằng từ khóa new) cho compile-time constant (ví dụ struct hay class).

Ví dụ sau đây là một khai báo lỗi:

// Does not compile, use readonly instead:
private const DateTime classCreation = new DateTime(2000, 1, 1, 0, 0, 0);

Runtime constant – readonly

Loại hằng này hoạt động rất khác so với compile-time constant. Giá trị của nó chỉ được tính toán khi chương trình chạy. Trong mã IL, loại hằng này được nhìn nhận giống như một biến (và chưa có giá trị). Vì lý do này, runtime constant cũng thường được gọi là biến chỉ đọc, thay vì hằng.

Đặc điểm làm cho biến readonly được xem là hằng nằm ở chỗ, một khi giá trị của nó được khởi tạo thì sẽ không thể thay đổi được nữa. Việc khởi tạo giá trị của biến readonly bắt buộc phải thực hiện ở nơi khai báo (sử dụng initializer) hoặc trong constructor (của class/struct).

Để thực hiện điều này, C# cấm tất cả các lệnh làm thay đổi giá trị của biến readonly sau khi nó được khởi tạo giá trị.

Nếu bạn sử dụng bất kỳ lệnh nào làm thay đổi giá trị của biến readonly (nằm ngoài constructor), C# compiler sẽ báo lỗi và dừng biên dịch.

Với cơ chế như trên, giá trị của runtime constant thực tế được xác định ở giai đoạn chạy chương trình (runtime) chứ không phải ở giai đoạn biên dịch.

Do giá trị được tính toán ở giai đoạn runtime, biến readonly có thể nhận bất kỳ giá trị nào giống như một biến thành viên thông thường. Cũng do cơ chế khởi tạo như trên, biến readonly không thể khai báo trong thân phương thức. Nó chỉ có thể khai báo làm thành viên của class/struct.

Kinh nghiệm sử dụng hằng

Tính tương thích

Việc copy giá trị của compile-time constant dẫn đến một hậu quả rất lớn khi bảo trì và cập nhật ứng dụng. Một khi bạn thay đổi compile-time constant trong phiên bản mới của assesmbly (ví dụ, làm cho Pi = 3.1415 cho chính xác hơn), tất cả những assembly liên quan đều phải biên dịch lại. Nếu không, khi chạy chương trình, tất cả các assembly tham chiếu tới Pi sẽ vẫn giữ nguyên giá trị 3.14, thay vì giá trị mới 3.1415.

Vấn đề này được gọi là runtime compatibility. Đây cũng là vấn đề có ảnh hưởng lớn nhất đến việc sử dụng const.

Runtime constant không có tình trạng trên vì giá trị của nó không được gán cứng.

Sử dụng hằng có giá trị phức tạp

Vấn đề thứ hai bạn phải đối diện khi sử dụng compile-time constant: không thể sử dụng các giá trị phức tạp làm hằng số. Điều này xuất phát trực tiếp từ bản chất của const mà bạn đã biết ở trên.

Biến readonly không chịu giới hạn này. Bạn có thể gán giá trị thuộc kiểu bất kỳ cho nó.

Khi đó, ví dụ, nếu cần sử dụng một hằng số là ngày tháng (struct DateTime), bạn bắt buộc phải sử dụng biến readonly.

Hiệu suất

Với cơ chế copy giá trị khi biên dịch, const có hiệu suất tốt hơn so với readonly do mã IL sinh ra tối ưu hơn (dĩ nhiên rồi, có mỗi copy cái giá trị thì làm chỉ chả tối ưu!). Tuy nhiên, sự khác biệt về hiệu suất thực ra rất nhỏ đến mức không đáng kể.

Khi quyết định dùng const với mục tiêu hiệu suất, bạn phải phải đánh giá xem có xứng đáng đánh đổi với sự linh hoạt của readonly hay không.

Để đánh giá sự khác biệt về hiệu suất, bạn có thể sử dụng công cụ BenchmarkDotNet (tải từ link https://github.com/PerfDotNet/BenchmarkDotNet).

Kết luận

Mặc dù có thể tạo ra nhiều vấn đề, bạn bắt buộc phải dùng const khi sử dụng tham số cho attribute, nhãn cho cấu trúc điều khiển switch-case, định nghĩa enum. Đây là quy định của C#.

Bạn cũng có thể dùng const nếu xác định rõ rằng giá trị đó sẽ không bao giờ thay đổi từ release này qua release khác.

Trong tất cả các tình huống khác, nếu bạn cần lưu trữ “hằng”, bạn nên sử dụng readonly thay cho const.

+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!

Theo dõi
Thông báo của
guest

1 Thảo luận
Cũ nhất
Mới nhất
Phản hồi nội tuyến
Xem tất cả bình luận
Nguyen Quang

nice