Menu

Biến trong C#: var, const, readonly, phạm vi và cách đặt tên

Cách khai báo và gán giá trị cho biến trong C#, khi nào nên và không nên dùng var, phạm vi hoạt động ra sao, khác biệt giữa const, readonly và static readonly, quy ước đặt tên, và lỗi dùng biến chưa được gán giá trị.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Biến (variable) là một vùng lưu trữ có tên và có kiểu cố định. Trong C#, bạn khai báo biến bằng một kiểu và một tên, và có thể gán giá trị cho nó ngay trong cùng câu lệnh. Kiểu của biến không bao giờ thay đổi sau đó: một biến int chứa số nguyên trong suốt thời gian nó tồn tại.

Đầu ra:

Priya bought 5 items for 20.25
10

Gán một giá trị sai kiểu là lỗi biên dịch: quantity = "five"; báo lỗi CS0029 (cannot implicitly convert type 'string' to 'int'). Chuyển đổi giữa các kiểu phải viết tường minh; xem chuyển đổi kiểu.

var: để trình biên dịch tự suy ra kiểu

var khai báo một biến cục bộ có kiểu được suy ra từ giá trị khởi tạo. Đây không phải là kiểu động: kiểu được cố định lúc biên dịch, y như khi bạn tự viết nó ra.

Đầu ra:

String
Int32
Decimal
Double
List`1

var có ba quy tắc. Nó chỉ dùng được cho biến cục bộ (không dùng cho field hay tham số). Biến phải được khởi tạo ngay khi khai báo: var x; là lỗi CS0818, "Implicitly-typed variables must be initialized". Và giá trị khởi tạo phải có kiểu: var nothing = null; không biên dịch được, vì riêng null thì không có kiểu.

Khi nào dùng nó là chuyện phong cách của từng nhóm. Hướng dẫn thường gặp là dùng var khi kiểu đã rõ ràng từ vế phải, và ghi rõ kiểu khi không rõ:

var orders = new List<Order>();         // obvious: var is fine
var reader = new StreamReader(path);    // obvious
decimal discount = CalculateDiscount(); // not obvious: name the type
int count = items.Count();              // makes the intent clear

Một cái bẫy khó thấy: var total = 0; khiến total là int, nên về sau cộng 2.5 vào sẽ lỗi, và var price = 10; là int, không phải decimal. Khi kiểu của literal không phải kiểu bạn muốn, hãy ghi rõ kiểu hoặc thêm hậu tố (10m, 0.0).

Phạm vi

Một biến cục bộ tồn tại từ chỗ khai báo cho tới cuối khối { } chứa nó. Biến khai báo trong phần đầu của vòng lặp hoặc trong thân if không nhìn thấy được sau đó.

Đầu ra:

high
240

Khác với C và C++, C# không cho một khối bên trong khai báo biến cục bộ trùng tên với một biến ở khối bao ngoài (lỗi CS0136): một cái tên phải mang một nghĩa duy nhất trong cả phương thức. Hai khối ngang hàng thì có thể dùng lại cùng một tên, như hai vòng lặp for riêng biệt đều khai báo i.

Gán giá trị chắc chắn

Biến cục bộ không có giá trị mặc định. Trình biên dịch theo dõi mọi đường chạy trong phương thức và từ chối biên dịch nếu một biến có thể bị đọc trước khi được gán:

int discount;
if (isMember)
{
    discount = 10;
}
Console.WriteLine(discount);
// error CS0165: Use of unassigned local variable 'discount'

Hãy sửa bằng cách khởi tạo ngay khi khai báo (int discount = 0;) hoặc gán giá trị trong mọi nhánh (thêm một else). Field và phần tử mảng thì khác: chúng tự động được đặt về giá trị mặc định của kiểu (0, false, null).

const: hằng số lúc biên dịch

const là một giá trị được cố định lúc biên dịch. Nó phải được khởi tạo bằng một biểu thức hằng (một literal, một const khác, hoặc phép tính trên chúng), và chỉ có thể là số, char, bool, string, enum hoặc null.

Đầu ra:

Price in EUR
120.00 EUR
350

Một field const ngầm định là static, nên được truy cập qua tên kiểu (Pricing.VatRate), và viết static const là lỗi. Trình biên dịch sao chép giá trị vào mọi chỗ dùng nó, vì thế const không tốn gì lúc chạy, và cũng vì thế một const public trong thư viện nên là giá trị không bao giờ thay đổi (như số ngày trong một tuần).

readonly và static readonly

Một field readonly chỉ có thể được gán ở phần khai báo hoặc trong hàm khởi tạo của lớp. Sau khi đối tượng được tạo xong, nó không thể thay đổi. Khác với const, giá trị của nó được tính lúc chạy, có thể thuộc bất kỳ kiểu nào, và có thể khác nhau giữa các đối tượng.

Đầu ra:

ACC-17 opened 2024-05-02
Launch: 2020-01-15
basic, pro

Mấy dòng cuối cho thấy một hiểu lầm phổ biến: readonly bảo vệ field, chứ không bảo vệ đối tượng mà nó trỏ tới. Plans sẽ luôn tham chiếu tới cùng một mảng, nhưng các phần tử của mảng vẫn có thể thay đổi. Để có collection thật sự không thay đổi được, hãy dùng ReadOnlyCollection<T> hoặc ImmutableArray<T>.

Tóm tắt cả ba:

constreadonlystatic readonly
Biết giá trị khi nàoLúc biên dịchLúc chạyLúc chạy
Kiểu được phépSố, char, bool, string, enum, nullMọi kiểuMọi kiểu
Gán ở đâuChỉ ở phần khai báoKhai báo hoặc hàm khởi tạoKhai báo hoặc hàm khởi tạo tĩnh
Riêng từng đối tượng hay dùng chungDùng chung (ngầm định static)Riêng từng đối tượngDùng chung
Biến cục bộCó (const int x = 1;)KhôngKhông

Quy ước đặt tên

Tên trong C# phân biệt chữ hoa chữ thường (total và Total là hai biến khác nhau) và phải bắt đầu bằng một chữ cái hoặc dấu gạch dưới. Các quy ước của Microsoft, được gần như mọi code C# tuân theo:

  • Biến cục bộ và tham số: camelCase (orderCount, unitPrice).
  • Hằng số, thuộc tính, phương thức, lớp và field public: PascalCase (MaxItems, VatRate).
  • Field private: _camelCase với dấu gạch dưới ở đầu (_balance), một quy ước phổ biến trong code .NET.
  • Biến boolean đọc như một câu hỏi: isActive, hasDiscount, canRetry.
  • Tránh viết tắt và tiền tố kiểu Hungarian (strName, iCount).

Một từ khóa của C# không thể dùng làm tên, trừ khi bạn thêm tiền tố @: var @class = "5B"; là hợp lệ, đôi khi có ích cho code được sinh tự động hoặc tên field trong JSON.

Hoán đổi và gán nhiều biến

Từ C# 7, tuple cho phép gán nhiều biến trong một câu lệnh, nhờ đó có thể hoán đổi hai biến chỉ trong một dòng:

Đầu ra:

20 10
Lisbon: 545,000

Dạng thứ hai là deconstruction: nó khai báo city là một string và population là một int từ một tuple. Xem thêm ở trang tuple.

Câu hỏi thường gặp

var trong C# là gì?

var bảo trình biên dịch suy ra kiểu của một biến cục bộ từ giá trị khởi tạo của nó: var count = 5; khai báo một int. Biến vẫn có kiểu tĩnh và về sau không thể chứa một chuỗi. var chỉ dùng được cho biến cục bộ được khởi tạo ngay trong cùng câu lệnh.

const và readonly trong C# khác nhau thế nào?

const là hằng số lúc biên dịch: giá trị của nó phải biết được ngay khi biên dịch (số, chuỗi, bool, null), nó ngầm định là static, và giá trị được sao chép vào mọi chỗ dùng đến nó. Một field readonly được gán một lần lúc chạy, ở phần khai báo hoặc trong hàm khởi tạo, có thể thuộc bất kỳ kiểu nào, và có thể khác nhau giữa các đối tượng.

Khi nào nên dùng static readonly thay cho const?

Dùng static readonly cho một giá trị dùng chung không thể là hằng số lúc biên dịch, như một DateTime, một mảng hay một đối tượng, hoặc cho một giá trị public có thể thay đổi giữa các phiên bản của một thư viện. Vì giá trị const được sao chép vào các assembly gọi tới, đổi một const public đòi hỏi phải biên dịch lại mọi assembly dùng nó.

Lỗi "use of unassigned local variable" trong C# nghĩa là gì?

Lỗi CS0165: trình biên dịch tìm thấy một đường chạy mà ở đó biến cục bộ bị đọc trước khi được gán bất kỳ giá trị nào. Biến cục bộ trong C# không có giá trị mặc định. Hãy khởi tạo biến ngay khi khai báo, hoặc bảo đảm mọi nhánh của if/switch đều gán giá trị cho nó trước khi dùng.

Nên dùng var hay ghi rõ kiểu trong C#?

Cả hai đều được; mỗi nhóm chọn một phong cách. Một quy tắc phổ biến là dùng var khi kiểu đã rõ ràng từ vế phải (var list = new List<string>();) và ghi rõ kiểu khi không rõ (decimal total = GetTotal();). Mã nguồn của .NET runtime đi theo đại khái quy tắc đó.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu