CodeGym /Các khóa học /C# SELF /required property và field property

required property và field property

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

1. Giới thiệu

Lập trình C# mà không dùng property thì cũng giống như đi patin trong Nhà Trắng — làm được, nhưng cực kỳ không thoải mái. Không chỉ vì bọn mình đã quen với cú pháp ngắn gọn không cần getter/setter thừa, mà khi app bắt đầu làm việc với các model “nghiêm túc” (ví dụ class User mô tả người dùng trong hệ thống), thì việc đảm bảo mọi dữ liệu cần thiết thực sự có mặt là rất quan trọng.

Ví dụ, nếu có class như này:

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Rất dễ quên khởi tạo property:

User user = new User(); // Name sẽ là null, Age = 0

Kết quả là đâu đó sau mười màn hình và trăm vòng logic, bạn sẽ gặp lỗi nổi tiếng NullReferenceException và sẽ phải đi tìm thủ phạm rất lâu.

Nhớ lại về init-only property

Đúng, bạn có thể dùng khởi tạo chỉ khi tạo object:

public string Name { get; init; }

Nhưng ngay cả với cú pháp này, không ai bắt người dùng class phải truyền giá trị nếu họ không muốn — hầu hết constructor mặc định sẽ khởi tạo field bằng giá trị mặc định (null, 0 v.v.).

Vậy làm sao bắt lập trình viên (hoặc chính mình) không quên set giá trị cần thiết? Đó là lý do modifier required ra đời trong C# 11.

2. required property: khởi tạo nghiêm ngặt

Modifier required — là chỉ dẫn cho compiler kiểm tra xem property đó đã được set rõ ràng khi tạo object chưa. Nói đơn giản, nếu property này chưa được khởi tạo khi tạo object — compiler sẽ không cho qua, còn IDE sẽ vẽ sóng đỏ đáng sợ.


public class User
{
    public required string Name { get; set; }
    public int Age { get; set; }
}
Property bắt buộc với required

Thử tạo user:

// Lỗi biên dịch: property Name bắt buộc phải khởi tạo!
User user1 = new User();

Hoặc như này:


// Lỗi biên dịch: chưa set property required Name
User user2 = new User { Age = 18 };

Cách đúng là như này:

User user3 = new User { Name = "Hermiona", Age = 18 };

Required hoạt động như nào?

Modifier required nói với compiler: “Sau khi constructor object chạy xong (bất kỳ constructor nào!), property này phải được set rõ ràng”.

Nó hoạt động với cả property thường lẫn init-only:

public required string Name { get; init; }

Nếu bạn tự định nghĩa constructor và khởi tạo giá trị cho property required, thì cũng ok.

Sơ đồ kiểm tra điển hình

Kịch bản Compiler hài lòng?
Chưa set property required ❌
Set trong object initializer ✔️
Set trong constructor ✔️

Ví dụ với model “Chó” của chúng ta

public class Dog
{
    public required string Name { get; set; }
    public int Age { get; set; }
}

Dog dog = new Dog { Name = "Bobik", Age = 5 }; // Ok luôn!
Dog badDog = new Dog { Age = 2 }; // Lỗi! Chưa set Name

Required thực sự hữu ích ở đâu?

  • Truyền DTO giữa các layer: Nếu bạn có API, cần đảm bảo mọi field cần thiết luôn được truyền vào.
  • Model phức tạp với thuộc tính bắt buộc: Ví dụ, Product với SKU bắt buộc, Order với số đơn hàng bắt buộc.
  • Khi phỏng vấn hoặc code review: Nếu bạn show cú pháp này, chắc chắn code của bạn sẽ được đọc với sự tôn trọng (và chút ghen tị nhẹ).

3. Required hoạt động với constructor như nào?

Đôi khi bạn tự viết constructor. Nếu không khởi tạo property required trong constructor hoặc object initializer thì sao? Compiler sẽ báo lỗi.


public class Article
{
    public required string Title { get; set; }
    public required string Author { get; set; }

    public Article()
    {
        // Nếu không khởi tạo Title và Author — lỗi biên dịch!
        // Có thể làm như này:
        Title = "Khong co ten";
        Author = "Khong biet";
    }
}

Nếu constructor tự set giá trị cho property required — mọi thứ đều ổn. Nếu không, bạn phải khởi tạo field này qua object initializer (new Article { ... }).

Đặc điểm sử dụng

  • required chỉ dùng cho property, không dùng cho field.
  • required không được kế thừa — nếu property ở base là required, còn ở class con không ghi required thì compiler không báo lỗi (nhưng best practice là nên ghi rõ lại required ở class con).
  • required không dùng cho auto field hay bất cứ gì ngoài property.

4. Viết property kiểu mũi tên

Từ các phiên bản mới của C#, dev càng ngày càng thích code ngắn gọn, dễ đọc. Một trong những điểm mới nổi bật cho property là cú pháp mũi tên (hay expression-bodied properties).

Đôi khi bạn chỉ muốn định nghĩa property trả về giá trị mà không cần logic gì thêm. Trước đây phải viết getter đầy đủ với dấu ngoặc nhọn:

public int Age
{
    get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}

Giờ có thể viết ngắn hơn nhiều — dùng mũi tên (=>):


public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;

Kiểu này gọi là property với body là biểu thức (expression-bodied property). Rất hợp cho tính toán đơn giản và làm code gọn hơn.

Ví dụ dùng get và set

public class Book
{
    private string _title;

    public string Title
    {
        get => _title;
        set => _title = value.Trim();
    }
}

Ở đây get trả về giá trị field, còn set — gán giá trị sau khi loại bỏ khoảng trắng thừa ở hai đầu.

Ví dụ chỉ dùng get (chỉ đọc):

Nếu property chỉ đọc — có thể viết luôn không cần ngoặc nhọn, chỉ cần =>.

public class Person
{
    private string name = "Mark Twain";

    // Chỉ đọc: property tính toán
    public string Name => name.ToUpper();
}

Ở đây property Name chỉ đọc được — luôn trả về name viết hoa.

5. Từ khóa field cho property

Trước C# 14, nếu muốn truy cập field ẩn của auto-property ngay trong setter/getter (ví dụ để tránh recursion hoặc thêm logic riêng) — không làm được. Bạn phải khai báo field rõ ràng.

C# 14 cho phép truy cập field ẩn của auto-property bằng từ khóa field:


public class Person
{
    public string Name
    {
        get => field; // field — là field ẩn của property Name
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Ten khong duoc rong!");
            field = value; // dùng field thay vì _name
        }
    }
}

Trước đây phải làm như này:

private string _name;
public string Name
{
    get => _name;
    set
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("Ten khong duoc rong!");
        _name = value;
    }
}

Giờ bớt được một biến. Code càng gọn càng tốt.

Tại sao đôi khi cần truy cập field trong property?

  • Đôi khi cần kiểm soát rõ nơi và cách lưu giá trị (ví dụ muốn trả về bản sao object, cache kết quả hoặc lazy-loading).
  • Nếu muốn dùng attribute hoặc reflection — có thể cần field theo tên.
  • Trong một số trường hợp (de)serialization hoặc tối ưu hiệu năng, bạn muốn kiểm soát nhiều hơn việc lưu giá trị.

Các kiểu dùng:

Validate dữ liệu trong setter

public double Grade
{
    get => field;
    set
    {
        if (value < 0 || value > 5)
            throw new ArgumentOutOfRangeException("Diem phai tu 0 den 5");
        field = value;
    }
}

Thống kê thay đổi giá trị

public int StepCount
{
    get => field;
    set
    {
        if (value > field)
        {
            Console.WriteLine($"Yeah! Ban da di duoc {value - field} buoc nhieu hon!");
        }
        field = value;
    }
}

Lazy Load

public string Data
{
    get
    {
        if (field == null)
            field = LoadDataFromDatabase();
        return field;
    }
    set => field = value;
}

6. Lỗi phổ biến và lưu ý

Lỗi #1: quên khởi tạo property required.
Compiler sẽ không cho qua và báo lỗi ngay khi build, giúp tránh lỗi khi chạy chương trình.

Lỗi #2: property required không hoạt động nếu constructor không khởi tạo đầy đủ.
Nếu constructor không nhận giá trị cho mọi property bắt buộc, compiler sẽ nhắc bạn còn thiếu.

Lỗi #3: cố dùng required với const hoặc readonly.
Các modifier này không tương thích — required chỉ dùng cho property thường. Kết hợp chúng sẽ báo lỗi.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo class với các thuộc tính required
Tạo class với các thuộc tính required
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Logic người dùng trong getter và setter với field
Logic người dùng trong getter và setter với field
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION