Các đối tượng không bao giờ đi ra khỏi phạm vi trong C # như chúng làm trong C ++. Chúng được xử lý bởi Garbage Collector khi chúng không được sử dụng nữa. Đây là một cách tiếp cận phức tạp hơn C ++ trong đó phạm vi của một biến là hoàn toàn xác định. Trình thu gom rác CLR tích cực đi qua tất cả các đối tượng đã được tạo và xử lý nếu chúng đang được sử dụng.
Một đối tượng có thể "đi ra khỏi phạm vi" trong một hàm nhưng nếu giá trị của nó được trả về, thì GC sẽ xem xét liệu hàm gọi có giữ giá trị trả về hay không.
Đặt tham chiếu đối tượng null
là không cần thiết vì bộ sưu tập rác hoạt động bằng cách tìm ra các đối tượng đang được các đối tượng khác tham chiếu.
Trong thực tế, bạn không phải lo lắng về sự phá hủy, nó chỉ hoạt động và thật tuyệt :)
Dispose
phải được gọi trên tất cả các đối tượng thực hiện IDisposable
khi bạn kết thúc làm việc với chúng. Thông thường bạn sẽ sử dụng một using
khối với các đối tượng như vậy:
using (var ms = new MemoryStream()) {
//...
}
EDIT Trên phạm vi biến. Craig đã hỏi liệu phạm vi biến có ảnh hưởng gì đến tuổi thọ của đối tượng không. Để giải thích chính xác khía cạnh đó của CLR, tôi sẽ cần giải thích một vài khái niệm từ C ++ và C #.
Phạm vi biến thực tế
Trong cả hai ngôn ngữ, biến chỉ có thể được sử dụng trong cùng phạm vi như đã được xác định - lớp, hàm hoặc khối câu lệnh được bao quanh bởi dấu ngoặc. Tuy nhiên, sự khác biệt tinh tế là trong C #, các biến không thể được xác định lại trong một khối lồng nhau.
Trong C ++, điều này là hoàn toàn hợp pháp:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
Trong C #, tuy nhiên bạn gặp lỗi trình biên dịch aa:
int iVal = 8;
if(iVal == 8) {
int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}
Điều này có ý nghĩa nếu bạn nhìn vào MSIL được tạo - tất cả các biến được sử dụng bởi hàm được xác định khi bắt đầu hàm. Hãy xem chức năng này:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
Dưới đây là IL được tạo. Lưu ý rằng iVal2, được xác định bên trong khối if thực sự được xác định ở cấp chức năng. Thực tế, điều này có nghĩa là C # chỉ có phạm vi mức độ lớp và chức năng liên quan đến tuổi thọ biến.
.method public hidebysig static void Scope() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 iVal,
[1] int32 iVal2,
[2] bool CS$4$0000)
//Function IL - omitted
} // end of method Test2::Scope
Phạm vi C ++ và tuổi thọ đối tượng
Bất cứ khi nào một biến C ++, được phân bổ trên ngăn xếp, đi ra khỏi phạm vi nó sẽ bị phá hủy. Hãy nhớ rằng trong C ++, bạn có thể tạo các đối tượng trên ngăn xếp hoặc trên heap. Khi bạn tạo chúng trên ngăn xếp, một khi thực thi rời khỏi phạm vi, chúng sẽ bị bật ra khỏi ngăn xếp và bị phá hủy.
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
Khi các đối tượng C ++ được tạo trên heap, chúng phải bị hủy hoàn toàn, nếu không đó là rò rỉ bộ nhớ. Không có vấn đề như vậy với các biến stack.
Đối tượng C # trọn đời
Trong CLR, các đối tượng (tức là các kiểu tham chiếu) luôn được tạo trên heap được quản lý. Điều này được củng cố thêm bằng cú pháp tạo đối tượng. Xem xét đoạn mã này.
MyClass stackObj;
Trong C ++, điều này sẽ tạo một thể hiện MyClass
trên ngăn xếp và gọi hàm tạo mặc định của nó. Trong C #, nó sẽ tạo một tham chiếu đến lớp MyClass
không trỏ đến bất cứ thứ gì. Cách duy nhất để tạo một thể hiện của một lớp là sử dụng new
toán tử:
MyClass stackObj = new MyClass();
Theo một cách nào đó, các đối tượng C # rất giống các đối tượng được tạo bằng new
cú pháp trong C ++ - chúng được tạo trên heap nhưng không giống như các đối tượng C ++, chúng được quản lý bởi bộ thực thi, vì vậy bạn không phải lo lắng về việc phá hủy chúng.
Vì các đối tượng luôn ở trên heap, thực tế là các tham chiếu đối tượng (tức là con trỏ) đi ra khỏi phạm vi trở thành moot. Có nhiều yếu tố liên quan đến việc xác định xem một đối tượng sẽ được thu thập hơn là sự hiện diện đơn giản của các tham chiếu đến đối tượng.
Tham chiếu đối tượng C #
Jon Skeet đã so sánh các tham chiếu đối tượng trong Java với các đoạn chuỗi được gắn vào quả bóng, đó là đối tượng. Sự tương tự tương tự áp dụng cho các tham chiếu đối tượng C #. Họ chỉ đơn giản là chỉ đến một vị trí của heap chứa đối tượng. Do đó, đặt nó thành null không có tác dụng ngay lập tức đối với tuổi thọ của vật thể, khinh khí cầu tiếp tục tồn tại, cho đến khi GC "bật" nó.
Tiếp tục xuống sự tương tự bóng bay, có vẻ hợp lý rằng một khi quả bóng không có dây gắn vào nó, nó có thể bị phá hủy. Trong thực tế, đây chính xác là cách các đối tượng đếm tham chiếu hoạt động trong các ngôn ngữ không được quản lý. Ngoại trừ cách tiếp cận này không hoạt động cho các tài liệu tham khảo tròn rất tốt. Hãy tưởng tượng hai quả bóng được gắn với nhau bằng một chuỗi nhưng không quả bóng nào có dây với bất cứ thứ gì khác. Theo quy tắc đếm ref đơn giản, cả hai vẫn tiếp tục tồn tại, mặc dù toàn bộ nhóm bóng bay là "mồ côi".
Các đối tượng .NET rất giống bóng bay heli dưới một mái nhà. Khi mái nhà mở ra (GC chạy) - những quả bóng bay không sử dụng trôi đi, mặc dù có thể có những nhóm bóng bay được buộc lại với nhau.
.NET GC sử dụng kết hợp giữa thế hệ GC và đánh dấu và quét. Cách tiếp cận thế hệ liên quan đến việc ưu tiên thời gian chạy để kiểm tra các đối tượng được phân bổ gần đây nhất, vì chúng có nhiều khả năng không được sử dụng và đánh dấu và quét liên quan đến thời gian chạy đi qua toàn bộ biểu đồ đối tượng và xử lý nếu có các nhóm đối tượng không được sử dụng. Điều này giải quyết thỏa đáng với vấn đề phụ thuộc vòng tròn.
Ngoài ra, .NET GC chạy trên một luồng khác (còn gọi là luồng hoàn thiện) vì nó có khá nhiều việc phải làm và thực hiện điều đó trên luồng chính sẽ làm gián đoạn chương trình của bạn.