C# Day 3: Members & Modifiers
Sau khi đã học về Type System ở ngày 1 và class, struct, record, interface, abstract, equality ở ngày 2, ngày 3 tập trung vào các thành phần nằm bên trong một type.
Mục tiêu của ngày 3 là hiểu:
- Field là gì?
- Property là gì?
- Backing field là gì?
- Auto-property hoạt động thế nào?
get,set,init,requiredkhác nhau ra sao?- Constructor dùng để làm gì?
const,readonly,static,static readonlykhác nhau thế nào?- Access modifier kiểm soát quyền truy cập ra sao?
- Base type và derived type là gì?
virtual,override,new,abstract,sealedkhác nhau thế nào?- Overload khác Override ra sao?
this,base,partialdùng khi nào?
1. Field là gì?
Field là nơi lưu state trực tiếp của object hoặc type.
public class User
{
private string _name = "";
private int _age;
}
Ở đây _name và _age là các field.
Mental model:
Field
→ storage
→ nơi giữ state
2. Property là gì?
Property là API để đọc hoặc ghi state.
public class User
{
private string _name = "";
public string Name
{
get => _name;
set => _name = value;
}
}
Ở đây:
_name
→ storage
Name
→ API để truy cập storage
Khi gọi:
user.Name = "Vu";
có thể hình dung:
Name.set("Vu")
↓
_name = "Vu"
Khi đọc:
var name = user.Name;
mental model:
Name.get()
↓
return _name
Property nhìn giống field khi sử dụng, nhưng có thể chứa logic.
3. Backing Field là gì?
Backing field là field được dùng để lưu giá trị phía sau một property.
private string _name = "";
public string Name
{
get => _name;
set => _name = value;
}
_name là backing field của Name.
Mental model:
Property
↓
get / set
↓
Backing field
↓
Actual data
4. Auto-property
Nếu property không cần custom logic, bạn không cần viết backing field thủ công.
public string Name { get; set; } = "";
Compiler tự tạo backing storage phía sau.
Mental model:
public string Name { get; set; }
↓ compiler
hidden backing field
+
getter
+
setter
Điểm quan trọng:
Auto-property giúp giữ encapsulation nhưng giảm boilerplate code.
5. Vì sao C# dùng Property thay vì Public Field?
Public field:
public string Name;
cho phép caller truy cập trực tiếp storage.
Property:
public string Name { get; set; }
cho phép sau này thêm validation mà caller không phải đổi API.
Ví dụ:
private string _name = "";
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name is required.");
_name = value;
}
}
Caller vẫn dùng:
user.Name = "Vu";
Mental model:
Property
→ abstraction around state
6. Không phải Property nào cũng có Backing Field
Ví dụ computed property:
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public string FullName
=> $"{FirstName} {LastName}";
FullName không cần backing field riêng.
Mỗi lần đọc user.FullName, nó tính lại từ FirstName + LastName.
Nhớ:
Auto-property
→ compiler thường tạo backing field
Computed property
→ có thể không cần backing field
7. = "" trong Property là gì?
public string Name { get; set; } = "";
Có hai phần khác nhau:
{ get; set; }
→ cách đọc / ghi property
= ""
→ initial value
= "" dùng để initialize giá trị ban đầu.
Nó không phải là set, cũng không phải init.
8. set
public string Name { get; set; } = "";
Caller có thể thay đổi property sau khi object được tạo.
var user = new User();
user.Name = "Vu";
user.Name = "David";
Mental model:
set
→ normal write
→ mutable property
9. init
public string Name { get; init; } = "";
init là init-only setter.
Cho phép:
var user = new User
{
Name = "Vu"
};
Nhưng sau khi object được initialize:
// user.Name = "David"; // compile error
Mental model:
= ""
→ bắt đầu bằng giá trị gì?
init
→ caller được phép gán khi nào?
10. get; init; = "" hoạt động thế nào?
public string Name { get; init; } = "";
Có thể đọc như sau:
Name
→ property
get
→ được đọc
init
→ được ghi trong initialization phase
= ""
→ default initial value
Ví dụ:
var a = new User();
thì Name là "".
Còn:
var b = new User
{
Name = "Vu"
};
thì object initializer thay giá trị mặc định bằng "Vu".
11. required
public class User
{
public required string Name { get; init; }
}
Caller phải initialize member này.
var user = new User
{
Name = "Vu"
};
Mental model:
required
→ must initialize
init
→ initialize-only write
Combination phổ biến:
public required string Name { get; init; }
12. Constructor
Constructor được gọi khi object được tạo.
public class User
{
public Guid Id { get; }
public string Name { get; }
public User(Guid id, string name)
{
Id = id;
Name = name;
}
}
Một constructor tốt thường giúp đảm bảo:
new object
→ valid initial state
13. Constructor Overload
public User()
{
}
public User(string name)
{
}
Cùng tên constructor nhưng khác parameter list.
Đây là overload.
14. Method Overload
public void Send(string message)
{
}
public void Send(string message, string email)
{
}
Cùng method name nhưng khác parameters.
Compiler chọn overload phù hợp tại compile time.
15. const
public const int MaxRetry = 3;
const là compile-time constant.
Đặc điểm:
- giá trị phải biết tại compile time
- không thể thay đổi
- implicitly static
Ví dụ:
public class Config
{
public const int MaxRetry = 3;
}
Dùng:
Config.MaxRetry
Không cần tạo instance.
Mental model:
const
→ compile-time constant
→ belongs to type
16. Vì sao const là implicitly static?
const không có giá trị khác nhau theo từng object.
Ví dụ:
public class Config
{
public const int MaxRetry = 3;
}
Dùng trực tiếp:
Config.MaxRetry
Do đó không viết:
public static const int MaxRetry = 3;
vì const đã mang semantics thuộc type.
17. readonly
public readonly Guid Id;
Khác const, readonly có thể được initialize bằng runtime value.
public User()
{
Id = Guid.NewGuid();
}
Sau initialization, field không được reassign.
Mental model:
const
→ compile-time
readonly
→ runtime init được
→ no reassignment afterward
18. readonly không có nghĩa Immutable
public readonly List<string> Names = new();
Không được reassign:
// Names = new List<string>();
Nhưng vẫn được:
Names.Add("Vu");
Names.Clear();
Vì readonly chỉ ngăn thay field reference.
Nó không làm object được reference trỏ tới trở thành immutable.
Mental model:
readonly reference
→ reference không đổi
referenced object
→ vẫn có thể mutable
19. Nested Object với readonly
public class Address
{
public string City { get; set; } = "";
}
public class User
{
public readonly Address Address = new();
}
Hợp lệ:
user.Address.City = "HCM";
Không hợp lệ:
// user.Address = new Address();
readonly không lan xuống toàn bộ object graph.
20. static
public class User
{
public static int TotalUsers;
}
Static member thuộc về type.
User.TotalUsers++;
Mental model:
instance member
→ mỗi object một bản
static member
→ cả type dùng chung một bản
21. static readonly
public static readonly Guid ApplicationId =
Guid.NewGuid();
Kết hợp:
static
→ thuộc type
readonly
→ runtime initialize được
→ không reassign sau đó
Ví dụ:
public static readonly DateTime StartedAt =
DateTime.UtcNow;
DateTime.UtcNow chỉ biết ở runtime nên không thể dùng const.
22. const vs readonly vs static readonly
| Concept | const |
readonly |
static readonly |
|---|---|---|---|
| Thuộc | type | instance | type |
| Runtime init | không | có | có |
| Reassign sau init | không | không | không |
| Shared | có | không | có |
23. Access Modifiers
public
Accessible từ mọi nơi phù hợp với type visibility.
private
Chỉ trong chính type.
protected
Trong type hiện tại và derived types.
internal
Accessible trong cùng assembly.
protected internal
protected OR internal
private protected
protected AND internal
24. Base Type và Derived Type
public class Animal
{
}
public class Dog : Animal
{
}
Ở đây:
Animal
→ base type
Dog
→ derived type
Mental model:
Dog is an Animal
Do đó:
Animal animal = new Dog();
là hợp lệ.
25. protected và Derived Type
public class Animal
{
protected string Name = "";
}
Derived type:
public class Dog : Animal
{
public void PrintName()
{
Console.WriteLine(Name);
}
}
Dog truy cập được Name vì Dog là derived type.
26. virtual
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal");
}
}
virtual nghĩa là:
Derived class được phép override behavior này.
Nó có default implementation.
27. override
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog");
}
}
Animal animal = new Dog();
animal.Speak();
Kết quả:
Dog
Vì:
Compile-time type = Animal
Runtime type = Dog
virtual/override
→ runtime dispatch
→ Dog.Speak()
28. new và Method Hiding
public class Animal
{
public void Speak()
{
Console.WriteLine("Animal");
}
}
public class Dog : Animal
{
public new void Speak()
{
Console.WriteLine("Dog");
}
}
Nếu:
Dog dog = new Dog();
dog.Speak();
kết quả là Dog.
Nhưng:
Animal animal = new Dog();
animal.Speak();
kết quả là Animal.
Vì new không override method của base class.
Nó chỉ hide member.
Mental model:
new
→ compile-time type quyết định member
29. override vs new
Với:
Animal animal = new Dog();
Nếu Speak() dùng override:
runtime type = Dog
→ Dog.Speak()
Nếu Speak() dùng new:
compile-time type = Animal
→ Animal.Speak()
Chốt:
override
→ runtime dispatch
new
→ member hiding
→ compile-time binding
30. abstract
public abstract class PaymentService
{
public abstract void Pay();
}
Abstract member không có implementation.
Concrete derived class phải override:
public class StripePaymentService : PaymentService
{
public override void Pay()
{
Console.WriteLine("Stripe");
}
}
31. virtual vs abstract
virtual:
public virtual void Speak()
{
Console.WriteLine("Default");
}
có default implementation
override optional
abstract:
public abstract void Speak();
không implementation
concrete derived class phải override
32. sealed
Sealed class:
public sealed class UserService
{
}
Không thể derive tiếp.
Sealed override:
public sealed override void Speak()
{
}
Derived class phía dưới không được override tiếp method này.
Mental model:
sealed
→ stop extension here
33. partial
public partial class UserService
{
}
Cho phép định nghĩa cùng một type trong nhiều file.
Compiler ghép chúng thành một type.
Thường gặp ở generated code, source generators và WinForms.
34. this
this tham chiếu tới instance hiện tại.
public class User
{
private string _name;
public User(string name)
{
this._name = name;
}
}
35. base
base dùng để truy cập base class hoặc gọi base constructor.
public class Dog : Animal
{
public Dog(string name)
: base(name)
{
}
}
36. private set
public string Name { get; private set; }
Bên ngoài đọc được nhưng không set trực tiếp được.
Bên trong class vẫn có thể sửa.
Khác với init:
private set
→ class có thể mutate sau này
init
→ chỉ cho assignment trong initialization phase
Bảng Tổng Kết
| Concept | Mental model |
|---|---|
| Field | storage |
| Property | controlled access |
| Backing field | storage phía sau property |
| Auto-property | compiler tạo backing field |
= value |
initial value |
set |
normal write |
init |
initialization-only write |
required |
must initialize |
| Constructor | valid initial state |
const |
compile-time constant |
readonly |
runtime init, no reassignment |
static |
belongs to type |
static readonly |
shared runtime value |
protected |
type + derived type |
virtual |
cho phép override |
override |
runtime dispatch |
new |
member hiding |
abstract |
no implementation |
sealed |
stop inheritance/override |
partial |
chia type nhiều file |
Câu Hỏi Ôn Tập + Đáp Án
1. Field khác Property thế nào?
Field là storage trực tiếp. Property là API để kiểm soát việc đọc và ghi state.
2. Backing field là gì?
Là field lưu giá trị phía sau một property.
3. Auto-property là gì?
Là property mà compiler tự tạo backing storage và accessor cơ bản.
4. Mọi property đều có backing field không?
Không. Computed property có thể tính giá trị mà không cần storage riêng.
5. = "" trong property làm gì?
Initialize giá trị ban đầu của backing storage.
6. set khác init thế nào?
set cho phép ghi bình thường sau khi object được tạo. init chỉ cho phép ghi trong object initialization.
7. required làm gì?
Bắt caller phải initialize member.
8. const là gì?
Compile-time constant và implicitly static.
9. readonly có phải compile-time constant không?
Không. readonly có thể được initialize bằng runtime value.
10. readonly List<T> có .Add() được không?
Có. readonly ngăn reassign field, không làm object bên trong immutable.
11. static nghĩa là gì?
Member thuộc về type và được dùng chung giữa các instance.
12. static readonly khác const thế nào?
const phải biết giá trị ở compile time. static readonly có thể được initialize ở runtime.
13. Derived type là gì?
Là type kế thừa từ base type. Ví dụ Dog : Animal, Dog là derived type.
14. protected cho ai truy cập?
Class hiện tại và derived classes.
15. virtual dùng để làm gì?
Cho phép derived class override implementation.
16. override chọn implementation theo compile-time hay runtime type?
Runtime type.
17. new method hiding chọn member theo type nào?
Compile-time type.
18. abstract khác virtual thế nào?
virtual có default implementation. abstract không có implementation và concrete derived class phải override.
19. sealed làm gì?
Ngăn inheritance hoặc ngăn override tiếp.
20. Overload khác Override thế nào?
Overload:
same name
different parameters
compile-time selection
Override:
inheritance
virtual/abstract
runtime dispatch
Key Takeaways
Field
→ storage
Property
→ controlled access
Backing field
→ storage behind property
Auto-property
→ compiler-generated backing storage
= value
→ initial value
set
→ normal mutable setter
init
→ initialization-only setter
required
→ must initialize
const
→ compile-time constant
readonly
→ runtime initialization, no reassignment
static
→ belongs to type
static readonly
→ shared runtime value
derived type
→ inherits from base type
virtual
→ allow override
override
→ runtime type decides
new
→ compile-time type decides
abstract
→ no implementation
sealed
→ stop extension
Kết Luận
Ngày 3 không chỉ là học keyword.
Mục tiêu quan trọng hơn là hiểu:
state được lưu ở đâu
↓
state được expose thế nào
↓
ai được mutate state
↓
member thuộc instance hay type
↓
inheritance cho phép extension thế nào
↓
compiler hay runtime quyết định behavior
Nếu hiểu được chuỗi này, các modifier của C# sẽ không còn là những keyword phải học thuộc riêng lẻ.