Unity’s Biggest Trap: null That Isn’t Null
Minh Khoa
Tác giả
Hôm nay chúng ta sẽ cùng mổ xẻ một chủ đề tưởng chừng như cơ bản nhưng lại là "cái bẫy" tử thần đối với rất nhiều lập trình viên Unity, kể cả những người đã có kinh nghiệm: Sự khác biệt giữa C# Object (System.Object) và Unity Engine Object (UnityEngine.Object) trong việc xử lý Null và Destruction.
## 1. Bản chất của hai thế giới: Managed và Unmanaged
Để hiểu được lý do cốt lõi, chúng ta cần biết kiến trúc sâu xa của Unity. Khi code trong Unity, bạn đang đứng làm cầu nối giữa hai thế giới:
- Thế giới Managed (C#): Nơi chứa các C# object (
System.Object), được cấp phát trên bộ nhớ Heap. Bộ nhớ này được dọn dẹp tự động bởi hệ thống Garbage Collector (GC) của công cụ chạy C# (Mono/IL2CPP). - Thế giới Native/Unmanaged (C++): Nơi chứa cấu trúc dữ liệu thực sự của Unity (đại diện cho
GameObject,Transform,MonoBehaviour,Texture2D...). Chúng được viết bằng C++ để tối ưu hiệu năng và hệ thống cốt lõi của Unity lấy quyền tự trực tiếp quản lý bộ nhớ của chúng.
Khi bạn tạo một GameObject trong C#, Unity tạo ra một đối tượng C++ nặng nề dưới nền, đồng thời tạo ra một lớp vỏ bộ đệm (wrapper) bằng C# để bạn có thể tương tác với đối tượng C++ đó. 👉 Tức là: UnityEngine.Object thực chất chỉ là cấu trúc vỏ bọc C# mỏng chứa một con trỏ (pointer) liên kết với đối tượng C++ cốt lõi dưới nền.
2. Quá trình Hủy đối tượng (Destruction)
Sự khác biệt lớn nhất nằm ở cách 2 dạng object này bị tiêu hủy:
- C# Object tiêu chuẩn: Bạn không thể "chủ động" tiêu diệt nó bằng lệnh. Bạn chỉ có thể hủy bỏ tất cả reference (gán tham chiếu bằng
null). Hệ thống Garbage Collector (GC) của C# sẽ tự đến dọn dẹp vào một lúc nào đó trong tương lai với một quy trình non-deterministic (bạn không đoán chắc được thời điểm nó hủy). - UnityEngine.Object: Bạn quyết định mọi thứ bằng cách gọi
Destroy(gameObject). Hàm này sẽ tiêu diệt ngay lập tức (hoặc ở cuối frame) đối tượng gốc của Unity ở không gian C++. TUY NHIÊN, cái vỏ bọc C# mà bạn đang giữ tham chiếu không hề bị hủy ngay, nó vẫn nằm chình ình trên vùng nhớ C# (Heap) cho tới khi GC đến dọn.
3. Cú lừa "Fake Null" (Xử lý Null)
Chính vì điều kì lạ là "thể xác C++" đã biến mất nhưng "cái vỏ C#" vẫn sống dai, một bài toán nan giải xuất hiện: