CodeGym /Các khóa học /C# SELF /Thuộc tính tự động và init-only setter

Thuộc tính tự động và init-only setter

C# SELF
Mức độ , Bài học
Có sẵn

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; }
    }
}
Thuộc tính thủ công với field private

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; }
}
Thuộc tính tự động

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; }
}
Nhiều thuộc tính tự động

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
public string Name;
Truy cập trực tiếp
Thuộc tính thủ công
public string Name { get; set; } + private string name;
Truy cập qua get/set
Thuộc tính tự động
public string Name { get; set; }
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; }
Setter private — chỉ thay đổi được trong class

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; }
}
Property với init-setter

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;
}
Thuộc tính tự động với giá trị mặc định

Hoặc với init:


public class Dog
{
    public string Name { get; init; } = "ChoCon";
    public int Age { get; init; } = 0;
}
init-only property với giá trị mặc định

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.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION