1. Thuộc tính tự động
Trong C#, thuộc tính (property) là kiểu lai giữa field và method. Thực ra nó là cú pháp sugar giúp việc đóng gói (encapsulation) trở nên tiện lợi nhất. Bạn truy cập thuộc tính như field bình thường, nhưng bên trong có thể là bất kỳ logic nào: kiểm tra dữ liệu, thay đổi field khác, gọi method, thậm chí gửi e-mail cho bà ngoại của bạn (cái cuối thì không nên đâu nhé). Ở bài trước mình đã viết property thủ công. Nhưng làm vậy sẽ rất chán nếu bạn có 100500 object đơn giản chỉ cần "getter" và "setter" — không cần logic gì đặc biệt.
Xin giới thiệu — thuộc tính tự động. Nó sẽ giúp bạn khỏi phải viết code lặp lại và để C# tự lo lưu trữ giá trị cho bạn.
Ngày xưa: thuộc tính thủ công
Khi muốn giấu field nhưng vẫn cho truy cập qua property, thường sẽ viết như này:
public class Dog
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Kiểu code này đúng là "boilerplate" kinh điển. Mà thường thì mình chẳng cần thêm gì vào get/set cả! Chỉ cần cho truy cập là đủ.
Bây giờ thì sao
Trong C# mọi thứ đã đơn giản hơn: không cần tự khai báo field nữa — compiler sẽ lo. Chỉ cần viết:
public class Dog
{
public string Name { get; set; }
}
Thế là xong. Bạn có thể đọc và thay đổi giá trị Name thoải mái, còn bên dưới compiler sẽ tự tạo field private mà bên ngoài không thể truy cập trực tiếp.
Vậy thực ra chuyện gì xảy ra bên trong? Khi bạn viết public string Name { get; set; }, compiler sẽ tự động tạo một field ẩn kiểu như Name__BackingField. Mỗi lần truy cập Name nó sẽ chèn getter hoặc setter tương ứng. Field này không thấy được trong code, nhưng nó tồn tại — và nếu dùng reflection thì vẫn tìm ra được (nhưng đó là câu chuyện khác).
Điểm hay của cách này là gì? Thứ nhất, code ngắn hơn, không phải lặp lại. Thứ hai, code sạch hơn: ít rối mắt, dễ đọc, dễ bảo trì. Và cuối cùng, an toàn hơn — không ai có thể nghịch field trực tiếp, mọi truy cập đều qua property.
2. Cú pháp thuộc tính tự động
Cú pháp rất đơn giản:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
Giờ Dog của mình nhìn gọn hơn hẳn! Có thể tạo object và gán giá trị như bình thường:
Dog myDog = new Dog();
myDog.Name = "Barbos";
myDog.Age = 4;
Sơ đồ
| Cách | Cú pháp | Truy cập giá trị |
|---|---|---|
| Field | |
Truy cập trực tiếp |
| Thuộc tính thủ công | |
Truy cập qua get/set |
| Thuộc tính tự động | |
get/set + field ẩn |
3. Tuỳ chỉnh truy cập: chỉ đọc, chỉ ghi
Chỉ đọc: get-only
Nếu property chỉ cần đọc (ví dụ mã đăng ký thú cưng), có thể làm nó chỉ đọc được — chỉ ghi get, còn set thì không ghi hoặc để private. Giá trị chỉ gán được trong constructor hoặc khi khai báo:
public class Dog
{
public string RegistrationCode { get; }
public Dog()
{
RegistrationCode = "DOG-001"; // Gán giá trị trong constructor
}
}
Giờ bên ngoài chỉ đọc được RegistrationCode, không thể sửa. Cách này rất tốt để bảo vệ dữ liệu quan trọng khỏi bị thay đổi nhầm.
Thêm một cách: set private
public string Name { get; private set; }
Lúc này property chỉ thay đổi được bên trong class (ví dụ qua method hoặc constructor).
Chỉ ghi: ý tưởng không hay
Lý thuyết thì có thể làm property chỉ ghi (set-only), nhưng rất hiếm gặp và không nên dùng — tưởng tượng object như "hố đen": ném vào được nhưng không lấy ra được.
4. init-only setter
Chút lịch sử
Trước đây, để làm object bất biến (immutable), phải dùng property chỉ có get và gán giá trị qua constructor. Ổn, nhưng không phải lúc nào cũng tiện. Gần đây C# có cú pháp mới: init-only property.
Bản chất của init-setter?
- Chỉ cho gán giá trị property khi tạo object (tức là trong constructor hoặc initializer).
- Sau khi tạo object, property chỉ đọc được.
- Rất hợp cho class "gần như bất biến", DTO, config, model.
public class Dog
{
public string Name { get; init; }
public int Age { get; init; }
}
Giờ có thể làm như này:
Dog dog = new Dog { Name = "Bobik", Age = 2 };
dog.Name = "Rex"; // Lỗi! Sau khi tạo - chỉ đọc
Cũng có thể gán giá trị trong constructor:
public Dog(string name, int age)
{
Name = name;
Age = age;
}
Sơ đồ: khi nào gán được
| Gán ở đâu? | { get; set; } | { get; private set; } | { get; init; } |
|---|---|---|---|
| Bên ngoài class, sau khi tạo | ✅ | ❌ | ❌ |
| Trong constructor | ✅ | ✅ | ✅ |
| Trong initializer | ✅ | ❌ | ✅ |
| Bên trong class | ✅ | ✅ | ❌ (trừ constructor) |
5. Ứng dụng thực tế
Hãy viết lại class Dog dùng thuộc tính tự động và init-only:
public class Dog
{
// Property chỉ gán khi khởi tạo
public string Name { get; init; }
public int Age { get; init; }
// Thuộc tính tự động cho trạng thái hiện tại
public bool IsHungry { get; set; }
// Method, hành động của chó
public void Bark()
{
Console.WriteLine($"{Name} nói: Gâu!");
}
}
Giờ có thể tạo Dog như này:
Dog dog = new Dog { Name = "Sharik", Age = 3, IsHungry = true };
dog.Bark(); // in ra: Sharik nói: Gâu!
dog.IsHungry = false; // property này có thể thay đổi sau khi tạo
dog.Name = "Barbos"; // Lỗi! Property chỉ gán khi khởi tạo
6. Thuộc tính tự động với giá trị mặc định
C# làm mọi thứ cho bạn tiện nhất. Có thể gán giá trị mặc định cho property luôn:
public class Dog
{
public string Name { get; set; } = "KhongTen";
public int Age { get; set; } = 0;
}
Hoặc với init:
public class Dog
{
public string Name { get; init; } = "ChoCon";
public int Age { get; init; } = 0;
}
Nếu không gán Name khi tạo, nó sẽ là "ChoCon".
7. Thuộc tính tự động với mức truy cập khác nhau
Có thể làm property đọc và ghi với các modifier khác nhau:
public class Dog
{
public string Name { get; private set; }
public int Age { get; private set; }
public Dog(string name, int age)
{
Name = name;
Age = age;
}
}
Trường hợp này chỉ có thể thay đổi tên và tuổi bên trong class Dog, ví dụ trong method hoặc constructor.
So sánh thuộc tính thủ công và tự động
| Cách | Độ khó viết | Kiểm soát trong set/get | Thay đổi sau khi tạo | Dùng trong model |
|---|---|---|---|---|
| Thuộc tính thủ công | Dài dòng | Tuyệt đối | Bất kỳ | Khi cần logic |
| Thuộc tính tự động | Ngắn gọn | Không | Bất kỳ hoặc private | Gần như mọi nơi |
| init-only tự động | Ngắn gọn | Không | Chỉ khi tạo | DTO, config |
8. Lỗi thường gặp khi dùng property
Lỗi số 1: cố gán giá trị cho property get-only ngoài constructor.
Nếu property chỉ có get, compiler sẽ không cho gán giá trị ở chỗ khác. Chỉ gán được trong constructor hoặc khi khai báo. Quên là dính lỗi compile ngay.
Lỗi số 2: nhầm giữa field và property.
Khi thay field thường bằng property tự động, nhớ kiểm tra code còn truy cập field cũ không. Field và property là hai thứ khác nhau, nếu để sót sẽ bị lỗi compile hoặc chương trình chạy sai.
Lỗi số 3: set private mà không hiểu hậu quả.
Nếu property có set private, mà code khác vẫn cố gán giá trị — sẽ lỗi. Thường gặp khi copy code của người khác. Luôn kiểm tra set có dùng được ở chỗ bạn cần không.
GO TO FULL VERSION