Nhìn bề ngoài, int, struct, class chỉ khác nhau ở syntax. Nhưng phía dưới CLR (Common Language Runtime), Value Type và Reference Type được cấp phát, copy và lưu trữ theo hai cách hoàn toàn khác nhau.

Cú lừa của dấu "="

Có bao giờ nhìn hai đoạn code này và nghĩ chúng hoạt động giống nhau chưa?

int a = 10;
int b = a;

b = 20;

và:

User user1 = new User();
User user2 = user1;

user2.Name = "Duy";

Nhìn qua đều là biến mới = biến cũ, nhưng kết quả runtime lại khác hoàn toàn.

Value Type - copy toàn bộ giá trị

Ví dụ:

int a = 10;
int b = a;

b = 20;

Kết quả:

a = 10
b = 20

Vì với Value Type, dấu "=" sẽ copy toàn bộ dữ liệu.

Nghĩa là:

a
↓
10

b
↓
10

Sau khi copy, a và b hoàn toàn độc lập nhau. Đây là cách hoạt động của: int, double, bool, char, struct

Reference Type - copy địa chỉ memory

Ví dụ:

User user1 = new User();

User user2 = user1;

Lúc này:

user1
  ↓
Heap Memory

user2
  ↓
Heap Memory

Cả hai biến đang cùng trỏ tới một object trong memory.

Nghĩa là:

user2.Name = "Duy";

thì:

user1.Name

cũng đổi theo.

Đây là lý do rất nhiều bug “ủa sao object tự đổi?” xuất hiện.

Stack và Heap - hiểu lầm phổ biến nhất

Rất nhiều người học kiểu:

Value Type -> Stack

Reference Type -> Heap

Không hoàn toàn đúng.

Ví dụ:

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

Age vẫn là Value Type.

Hoặc:

struct Point
{
    public int X;
}

Nếu struct nằm bên trong object khác thì nó cũng có thể nằm trên Heap.

Điều quan trọng nhất không phải nằm ở đâu, mà là nó được copy kiểu gì

Value Type → copy giá trị

Reference Type → copy reference

Đây mới là bản chất thật sự.

Vì sao struct thường nhanh hơn class?

Vì Value Type thường không cần allocate object riêng trên Heap, ít tạo áp lực cho Garbage Collector copy trực tiếp dữ liệu. Nhưng đây cũng là con dao hai lưỡi.

Ví dụ struct quá lớn:

50 fields
100 fields

Thì mỗi lần copy sẽ rất tốn cost. Đây là lý do Microsoft thường khuyên Struct nên nhỏ và immutable. Việc truyền parameter cũng bị ảnh hưởng

Ví dụ:

void Change(int number)
{
    number = 100;
}

Gọi:

int x = 10;

Change(x);

Kết quả:

x = 10

Vì Value Type bị copy khi truyền parameter. Nhưng với class:

void Change(User user)
{
    user.Name = "Duy";
}

Thì object thật sự bị sửa. Đây là chỗ rất nhiều người mới học C# bị nhầm.

string là trường hợp khá đặc biệt

string là Reference Type.

Nhưng:

string a = "hello";
string b = a;

b = "world";

Kết quả:

a = hello
b = world

Nhìn giống Value Type.

Lý do là vì string immutable. Mỗi lần thay đổi string, CLR sẽ tạo object mới thay vì sửa object cũ. Đây là thứ rất nhiều người hiểu nhầm khi mới học C#.

Kết luận

Nhìn bề ngoài: int, struct, class chỉ khác nhau ở syntax. Nhưng phía dưới CLR:

  • Value Type copy dữ liệu thật
  • Reference Type copy địa chỉ memory

Và đây là lý do rất nhiều behavior trong C# hoạt động khác nhau:

  • Assign object
  • Truyền parameter
  • Memory allocation
  • Performance
  • Garbage collection

Hiểu được chuyện này không giúp code CRUD nhanh hơn. Nó giúp biết chính xác:

  • Vì sao object bị đổi theo
  • Vì sao data không update
  • Vì sao memory tăng
  • Vì sao struct và class hoạt động khác nhau

Nó chỉ là CLR đang quản lý dữ liệu theo hai cơ chế hoàn toàn khác nhau phía dưới runtime.