Circular reference (Tham chiếu vòng) xảy ra khi hai hoặc nhiều đối tượng tham chiếu lẫn nhau, tạo thành chu trình (A → B → A). Trong ngôn ngữ có quản lý bộ nhớ thủ công (như C++), điều này gây rò rỉ bộ nhớ. Tuy nhiên, trong C#, Garbage Collector (GC) xử lý tự động.
Ví dụ tham chiếu vòng
public class A { public B RefB; }
public class B { public A RefA; }
// Tạo vòng tròn
A a = new A();
B b = new B();
a.RefB = b;
b.RefA = a;
// a và b giờ tham chiếu lẫn nhau
Cách GC xử lý
GC của .NET dùng thuật toán tracing (duyệt đồ thị) từ các GC roots (biến static, biến local trên stack, handle CPU...). Bất kỳ đối tượng nào không thể truy cập được từ GC roots sẽ được coi là rác và thu hồi, dù chúng tham chiếu lẫn nhau.
Quy trình:
- Đánh dấu (Mark): Duyệt từ roots, đánh dấu tất cả đối tượng còn "sống" (reachable).
- Dọn dẹp (Sweep/Compact): Giải phóng bộ nhớ các đối tượng không được đánh dấu.
⇒ Tham chiếu vòng không gây rò rỉ bộ nhớ trong C# miễn là toàn bộ chu trình không còn bị root giữ.
Khi nào cần can thiệp?
- Đối tượng nắm tài nguyên unmanaged (file handle, socket, COM object...) → implement
IDisposablevà dùngusing. - Event handler: Subscribe event mà không unsubscribe → publisher giữ reference đến subscriber → subscriber không bị GC. Dùng
WeakReferencehoặc unsubscribe trongDispose. - Cache/static collection giữ object quá lâu → memory leak logic (không phải leak do GC).
Giải pháp thiết kế tránh vòng tham chiếu logic
Dù GC xử lý được, thiết kế tốt nên tránh vòng tham chiếu không cần thiết:
// Thay vì A tham chiếu B, B tham chiếu A
// Dùng Interface để ngắt phụ thuộc chặt chẽ
public interface IB { void DoSomething(); }
public class A { public IB Service; } // A chỉ phụ thuộc abstraction
public class B : IB { public A Owner; } // B implement interface
Tóm lại: C# GC thông minh, tham chiếu vòng không phải vấn đề. Hãy tập trung vào Dispose pattern cho unmanaged resources và unsubscribe event.