Bạn có cần loại bỏ các đối tượng và đặt chúng thành null?


310

Bạn có cần vứt bỏ các vật thể và đặt chúng thành null, hoặc bộ thu gom rác sẽ dọn sạch chúng khi chúng ra khỏi phạm vi không?


4
Dường như có một sự đồng thuận rằng bạn không cần đặt đối tượng thành null, nhưng bạn có cần thực hiện Dispose () không?
CJ7


9
Lời khuyên của tôi là luôn luôn xử lý nếu một đối tượng thực hiện IDis Dùng. Sử dụng một khối sử dụng mỗi lần. Đừng đưa ra các giả định, đừng để nó có cơ hội. Bạn không cần phải đặt mọi thứ thành null. Một đối tượng vừa đi ra khỏi phạm vi.
peter

11
@peter: Không sử dụng các khối "sử dụng" với proxy máy khách WCF: msdn.microsoft.com/en-us/l
Library / aa355056.aspx

9
TUY NHIÊN, BẠN CÓ THỂ muốn đặt một số tham chiếu thành null trong Dispose()phương thức của mình ! Đây là một biến thể tinh tế cho câu hỏi này, nhưng quan trọng bởi vì đối tượng bị loại bỏ không thể biết liệu nó có "đi ra khỏi phạm vi" hay không (gọi Dispose()là không đảm bảo). Xem thêm tại đây: stackoverflow.com/questions/6757048/ Mạnh
Kevin P. Rice

Câu trả lời:


239

Các đối tượng sẽ được dọn sạch khi chúng không còn được sử dụng và khi người thu gom rác thấy phù hợp. Đôi khi, bạn có thể cần đặt một đối tượng để nulllàm cho nó vượt ra khỏi phạm vi (chẳng hạn như trường tĩnh có giá trị bạn không còn cần nữa), nhưng nhìn chung thường không cần phải đặt thành null.

Về việc xử lý các đối tượng, tôi đồng ý với @Andre. Nếu đối tượng là IDisposablenó là một ý tưởng tốt để vứt bỏ nó khi bạn không còn cần đến nó, đặc biệt là nếu đối tượng sử dụng các nguồn lực không được quản lý. Không xử lý tài nguyên không được quản lý sẽ dẫn đến rò rỉ bộ nhớ .

Bạn có thể sử dụng usingcâu lệnh để tự động loại bỏ một đối tượng một khi chương trình của bạn rời khỏi phạm vi của usingcâu lệnh.

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

Đó là chức năng tương đương với:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}

4
Nếu obj là loại tham chiếu thì khối cuối cùng tương đương với:if (obj != null) ((IDisposable)obj).Dispose();
Randy hỗ trợ Monica

1
@Tuzo: Cảm ơn! Chỉnh sửa để phản ánh điều đó.
Zach Johnson

2
Một nhận xét liên quan IDisposable. Không loại bỏ một đối tượng thường sẽ không gây rò rỉ bộ nhớ trên bất kỳ lớp nào được thiết kế tốt. Khi làm việc với các tài nguyên không được quản lý trong C #, bạn nên có một bộ hoàn thiện vẫn sẽ giải phóng các tài nguyên không được quản lý. Điều này có nghĩa là thay vì phân bổ các tài nguyên khi cần thực hiện, nó sẽ được hoãn lại khi trình thu gom rác hoàn thiện đối tượng được quản lý. Nó vẫn có thể gây ra nhiều vấn đề khác mặc dù (chẳng hạn như khóa chưa được phát hành). Bạn nên vứt bỏ IDisposablemặc dù!
Aidiakapi 20/07/2015

@RandyLevy Bạn có tài liệu tham khảo cho điều đó không? Cảm ơn
Cơ bản

Nhưng câu hỏi của tôi là Dispose () cần thực hiện logic nào? Nó có phải làm gì không? Hoặc bên trong khi Dispose () được gọi là tín hiệu GC là tốt để đi? Tôi đã kiểm tra mã nguồn cho TextWriter chẳng hạn và Vứt bỏ không có triển khai.
Mihail Georgescu

137

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 nulllà 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 :)

Disposephải được gọi trên tất cả các đối tượng thực hiện IDisposablekhi 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 usingkhố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 MyClasstrê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 MyClasskhô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 newtoá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 newcú 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.


1
@Igor: Bằng cách đi ra khỏi phạm vi 'Tôi có nghĩa là tham chiếu đối tượng nằm ngoài ngữ cảnh và không thể được tham chiếu trong phạm vi hiện tại. Chắc chắn điều này vẫn xảy ra trong C #.
CJ7

@Craig Johnston, đừng nhầm lẫn phạm vi biến được sử dụng bởi trình biên dịch với tuổi thọ biến được xác định bởi thời gian chạy - chúng khác nhau. Một biến cục bộ có thể không "sống" mặc dù nó vẫn nằm trong phạm vi.
Randy hỗ trợ Monica

1
@Craig Johnston: Xem blog.msdn.com/b/ericgu/archive/2004/07/23/192842.aspx : "không có gì đảm bảo rằng một biến cục bộ sẽ tồn tại cho đến hết phạm vi nếu không được sử dụng. Thời gian chạy là tự do để phân tích mã mà nó có và xác định những gì không sử dụng thêm một biến nào ngoài một điểm nhất định, và do đó không giữ biến đó sống ngoài điểm đó (nghĩa là không coi nó là gốc cho các mục đích của GC). "
Randy hỗ trợ Monica

1
@Tuzo: Đúng. Đó là những gì GC.KeepAlive dành cho.
Steven Sudit

1
@Craig Johnston: Không và Có. Không bởi vì .NET runtime quản lý điều đó cho bạn và thực hiện công việc tốt. Có bởi vì công việc của lập trình viên không phải là viết mã mà (chỉ) biên dịch mà là viết mã chạy . Đôi khi nó giúp biết thời gian chạy đang làm gì dưới vỏ bọc (ví dụ: khắc phục sự cố). Người ta có thể lập luận rằng đó là loại kiến ​​thức giúp tách biệt các lập trình viên giỏi khỏi các lập trình viên tuyệt vời.
Randy hỗ trợ Monica

18

Như những người khác đã nói bạn chắc chắn muốn gọi Disposenếu lớp thực hiện IDisposable. Tôi có một vị trí khá cứng nhắc về điều này. Một số người cho rằng sức kêu gọi Disposetrên DataSet, ví dụ, là vô nghĩa vì họ tháo rời nó và thấy rằng nó đã không làm bất cứ điều gì có ý nghĩa. Nhưng, tôi nghĩ rằng có những ngụy biện đầy rẫy trong cuộc tranh luận đó.

Đọc điều này cho một cuộc tranh luận thú vị của các cá nhân tôn trọng về chủ đề này. Sau đó đọc lý do của tôi ở đây tại sao tôi nghĩ Jeffery Richter ở sai trại.

Bây giờ, về việc bạn có nên đặt tham chiếu hay không null. Câu trả lời là không. Hãy để tôi minh họa quan điểm của tôi với mã sau đây.

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

Vì vậy, khi nào bạn nghĩ rằng đối tượng được tham chiếu bởi ađủ điều kiện để thu thập? Nếu bạn nói sau khi gọi đến a = nullthì bạn đã nhầm. Nếu bạn nói sau khi Mainphương thức hoàn thành thì bạn cũng sai. Câu trả lời đúng là nó đủ điều kiện để thu thập đôi khi trong suốt cuộc gọi đến DoSomething. Đúng vậy Nó đủ điều kiện trước khi tham chiếu được đặt thành nullvà thậm chí trước khi cuộc gọi DoSomethinghoàn tất. Đó là bởi vì trình biên dịch JIT có thể nhận ra khi các tham chiếu đối tượng không còn bị hủy đăng ký ngay cả khi chúng vẫn được root.


3
Điều gì nếu alà một lĩnh vực thành viên tư nhân trong một lớp? Nếu akhông được đặt thành null, GC không có cách nào để biết liệu asẽ được sử dụng lại trong một số phương thức, phải không? Do đó asẽ không được thu thập cho đến khi toàn bộ lớp chứa được thu thập. Không?
Kevin P. Rice

4
@ Kevin: Đúng rồi. Nếu alà một thành viên của lớp và lớp chứa avẫn được root và sử dụng thì nó cũng sẽ bị treo. Đó là một kịch bản trong đó thiết lập nó nullcó thể có lợi.
Brian Gideon

1
Quan điểm của bạn liên quan đến một lý do tại sao lại Disposequan trọng - không thể gọi Dispose(hoặc bất kỳ phương pháp không thể xâm nhập nào khác) trên một đối tượng mà không có tham chiếu gốc đến nó; việc gọi Disposesau khi một người được thực hiện bằng cách sử dụng một đối tượng sẽ đảm bảo rằng một tham chiếu gốc sẽ tiếp tục tồn tại trong suốt thời gian của hành động cuối cùng được thực hiện trên nó. Việc từ bỏ tất cả các tham chiếu đến một đối tượng mà không gọi điện Disposecó thể có tác dụng làm cho tài nguyên của đối tượng đôi khi bị phát hành quá sớm .
16/1/2015

Ví dụ và giải thích này dường như không dứt khoát đối với đề xuất cứng để không bao giờ đặt tham chiếu thành null. Ý tôi là, ngoại trừ bình luận của Kevin, một tham chiếu được đặt thành null sau khi nó bị loại bỏ có vẻ khá lành tính , vậy tác hại của nó là gì? Tui bỏ lỡ điều gì vậy?
dathompson

13

Bạn không bao giờ cần đặt đối tượng thành null trong C #. Trình biên dịch và thời gian chạy sẽ đảm nhiệm việc tìm ra khi chúng không còn trong phạm vi.

Có, bạn nên loại bỏ các đối tượng triển khai IDis Dùng.


2
Nếu bạn có một tài liệu tham khảo tồn tại lâu dài (hoặc thậm chí tĩnh) cho một đối tượng lớn, bạn wantsẽ loại bỏ nó ngay khi bạn hoàn thành nó để có thể lấy lại miễn phí.
Steven Sudit

12
Nếu bạn đã từng "thực hiện với nó" thì nó không nên tĩnh. Nếu nó không tĩnh, nhưng "tồn tại lâu" thì nó vẫn sẽ vượt ra khỏi phạm vi ngay sau khi bạn hoàn thành nó. Sự cần thiết phải đặt tham chiếu thành null chỉ ra một vấn đề với cấu trúc của mã.
EMP

Bạn có thể có một mục tĩnh mà bạn đã hoàn thành. Xem xét: Một tài nguyên tĩnh được đọc từ đĩa theo định dạng thân thiện với người dùng và sau đó phân tích thành định dạng phù hợp để sử dụng chương trình. Bạn có thể kết thúc với một bản sao riêng của dữ liệu thô không phục vụ mục đích nào nữa. (Ví dụ trong thế giới thực: Phân tích cú pháp là một thói quen hai lần và do đó không thể xử lý dữ liệu đơn giản như đã đọc.)
Loren Pechtel

1
Sau đó, nó không nên lưu trữ bất kỳ dữ liệu thô nào trong trường tĩnh nếu nó chỉ được sử dụng tạm thời. Chắc chắn, bạn có thể làm điều đó, đó chỉ là thực hành không tốt vì chính xác lý do này: sau đó bạn phải quản lý vòng đời của nó bằng tay.
EMP

2
Bạn tránh nó bằng cách lưu trữ dữ liệu thô trong một biến cục bộ trong phương thức xử lý nó. Phương thức trả về dữ liệu đã xử lý mà bạn giữ, nhưng cục bộ cho dữ liệu thô sẽ vượt quá phạm vi khi phương thức thoát và được tự động hóa.
EMP

11

Tôi đồng ý với câu trả lời phổ biến ở đây là có, bạn nên loại bỏ và nói chung là bạn không nên đặt biến thành null ... nhưng tôi muốn chỉ ra rằng xử lý KHÔNG chủ yếu là về quản lý bộ nhớ. Vâng, nó có thể giúp (và đôi khi làm) với việc quản lý bộ nhớ, nhưng mục đích chính của nó là cung cấp cho bạn việc phát hành tài nguyên khan hiếm.

Ví dụ: nếu bạn mở một cổng phần cứng (ví dụ nối tiếp), ổ cắm TCP / IP, tệp (trong chế độ truy cập độc quyền) hoặc thậm chí kết nối cơ sở dữ liệu, bạn hiện đã ngăn không cho bất kỳ mã nào khác sử dụng các mục đó cho đến khi chúng được phát hành. Vứt bỏ thường phát hành các mục này (cùng với GDI và các thẻ điều khiển "os" khác, v.v ... có sẵn 1000, nhưng vẫn còn hạn chế về tổng thể). Nếu bạn không gọi dipose trên đối tượng chủ sở hữu và giải phóng rõ ràng các tài nguyên này, thì hãy thử mở lại cùng một tài nguyên trong tương lai (hoặc một chương trình khác) rằng nỗ lực mở sẽ thất bại vì đối tượng không bị chặn, không bị chặn của bạn vẫn mở mục . Tất nhiên, khi GC thu thập vật phẩm (nếu mẫu Vứt bỏ được triển khai chính xác), tài nguyên sẽ được giải phóng ... nhưng bạn không biết khi nào sẽ có, vì vậy bạn không ' Tôi biết khi nào an toàn để mở lại tài nguyên đó. Đây là vấn đề chính Vứt bỏ công việc xung quanh. Tất nhiên, việc giải phóng các tay cầm này cũng thường giải phóng bộ nhớ và không bao giờ giải phóng chúng có thể không bao giờ giải phóng bộ nhớ đó ... do đó, tất cả các cuộc thảo luận về rò rỉ bộ nhớ hoặc làm chậm bộ nhớ.

Tôi đã thấy các ví dụ thực tế về điều này gây ra vấn đề. Chẳng hạn, tôi đã thấy các ứng dụng web ASP.Net cuối cùng không kết nối được với cơ sở dữ liệu (mặc dù trong thời gian ngắn hoặc cho đến khi quá trình máy chủ web được khởi động lại) vì 'nhóm kết nối của máy chủ sql đã đầy' ... tức là , rất nhiều kết nối đã được tạo và không được phát hành rõ ràng trong một khoảng thời gian ngắn đến mức không có kết nối mới nào có thể được tạo và nhiều kết nối trong nhóm, mặc dù không hoạt động, vẫn được tham chiếu bởi các đối tượng không bị chặn và không bị chặn và vì vậy có thể ' t được tái sử dụng. Xử lý chính xác các kết nối cơ sở dữ liệu khi cần thiết đảm bảo sự cố này không xảy ra (ít nhất là không trừ khi bạn có quyền truy cập đồng thời rất cao).


11

Nếu đối tượng thực hiện IDisposable, thì có, bạn nên loại bỏ nó. Đối tượng có thể được treo vào tài nguyên gốc (xử lý tệp, đối tượng hệ điều hành) có thể không được giải phóng ngay lập tức. Điều này có thể dẫn đến tình trạng đói tài nguyên, các sự cố khóa tệp và các lỗi tinh vi khác có thể tránh được.

Xem thêm Thực hiện phương pháp vứt bỏ trên MSDN.


Nhưng sẽ không phải là người sưu tầm garabage gọi Dispose ()? Nếu vậy, tại sao bạn cần phải gọi nó?
CJ7

Trừ khi bạn gọi nó một cách rõ ràng, không có gì đảm bảo Disposesẽ được gọi. Ngoài ra, nếu đối tượng của bạn đang giữ một tài nguyên khan hiếm hoặc đang khóa một số tài nguyên (ví dụ: một tệp), thì bạn sẽ muốn giải phóng nó càng sớm càng tốt. Chờ đợi để làm điều đó là tối ưu.
Chris Schmich

12
GC sẽ không bao giờ gọi Dispose (). GC có thể gọi một bộ hoàn thiện mà theo quy ước sẽ làm sạch tài nguyên.
adrianm

@adrianm: Không mightgọi, nhưng willgọi.
leppie

2
@leppie: bộ hoàn thiện không mang tính quyết định và có thể không được gọi (ví dụ: khi tên miền appd không được tải). Nếu bạn cần quyết toán quyết định, bạn phải thực hiện những gì tôi nghĩ được gọi là xử lý quan trọng. CLR có cách xử lý đặc biệt đối với các đối tượng này để đảm bảo rằng chúng được hoàn thiện (ví dụ: nó xử lý trước mã quyết toán để xử lý bộ nhớ thấp)
adrianm

9

Nếu họ thực hiện giao diện IDis Dùng thì bạn nên loại bỏ chúng. Người thu gom rác sẽ lo phần còn lại.

EDIT: tốt nhất là sử dụng usinglệnh khi làm việc với các mục dùng một lần:

using(var con = new SqlConnection("..")){ ...

5

Khi một đối tượng thực hiện, IDisposablebạn nên gọi Dispose(hoặc Close, trong một số trường hợp, điều đó sẽ gọi Vứt bỏ cho bạn).

Bạn thường không phải thiết lập các đối tượng null, bởi vì GC sẽ biết rằng một đối tượng sẽ không được sử dụng nữa.

Có một ngoại lệ khi tôi đặt đối tượng null. Khi tôi truy xuất rất nhiều đối tượng (từ cơ sở dữ liệu) mà tôi cần phải làm việc và lưu trữ chúng trong một bộ sưu tập (hoặc mảng). Khi "công việc" hoàn thành, tôi đặt đối tượng thành null, vì GC không biết tôi đã hoàn thành công việc với nó.

Thí dụ:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called

4

Thông thường, không cần thiết phải đặt trường thành null. Tuy nhiên, tôi luôn khuyên bạn nên xử lý các tài nguyên không được quản lý.

Từ kinh nghiệm tôi cũng khuyên bạn nên làm như sau:

  • Hủy đăng ký từ các sự kiện nếu bạn không còn cần chúng.
  • Đặt bất kỳ trường nào giữ một đại biểu hoặc biểu thức thành null nếu không còn cần thiết.

Tôi đã gặp một số vấn đề rất khó tìm thấy đó là kết quả trực tiếp của việc không làm theo lời khuyên ở trên.

Một nơi tốt để làm điều này là trong Dispose (), nhưng sớm hơn thường tốt hơn.

Nói chung, nếu một tham chiếu tồn tại cho một đối tượng, trình thu gom rác (GC) có thể mất một vài thế hệ nữa để tìm ra rằng một đối tượng không còn được sử dụng. Tất cả trong khi đối tượng vẫn còn trong bộ nhớ.

Đó có thể không phải là vấn đề cho đến khi bạn thấy rằng ứng dụng của bạn đang sử dụng nhiều bộ nhớ hơn bạn mong đợi. Khi điều đó xảy ra, kết nối một trình biên dạng bộ nhớ để xem những đối tượng nào không được dọn sạch. Đặt các trường tham chiếu các đối tượng khác thành null và xóa các bộ sưu tập khi xử lý thực sự có thể giúp GC tìm ra những đối tượng mà nó có thể xóa khỏi bộ nhớ. GC sẽ lấy lại bộ nhớ đã sử dụng nhanh hơn làm cho ứng dụng của bạn ít bộ nhớ hơn và nhanh hơn.


1
Bạn có ý nghĩa gì về 'sự kiện và đại biểu' - những gì nên được 'làm sạch' với những điều này?
CJ7

@Craig - Tôi đã chỉnh sửa câu trả lời của mình. Hy vọng điều này làm rõ nó một chút.
Marnix van Valen

3

Luôn luôn gọi vứt bỏ. Nó không đáng mạo hiểm. Các ứng dụng doanh nghiệp được quản lý lớn nên được đối xử tôn trọng. Không có giả định nào có thể được thực hiện nếu không nó sẽ quay lại cắn bạn.

Đừng nghe leppie.

Rất nhiều đối tượng không thực sự triển khai IDis Dùng một lần, vì vậy bạn không phải lo lắng về chúng. Nếu họ thực sự đi ra khỏi phạm vi, họ sẽ được giải phóng tự động. Ngoài ra tôi chưa bao giờ gặp phải tình huống mà tôi phải đặt một cái gì đó thành null.

Một điều có thể xảy ra là rất nhiều đối tượng có thể được mở. Điều này có thể làm tăng đáng kể việc sử dụng bộ nhớ của ứng dụng của bạn. Đôi khi thật khó để biết liệu đây có thực sự là rò rỉ bộ nhớ hay không, hay ứng dụng của bạn chỉ đang làm nhiều thứ.

Các công cụ hồ sơ bộ nhớ có thể giúp với những thứ như vậy, nhưng nó có thể khó khăn.

Ngoài ra, luôn luôn hủy đăng ký từ các sự kiện không cần thiết. Cũng hãy cẩn thận với ràng buộc và kiểm soát WPF. Không phải là một tình huống thông thường, nhưng tôi đã gặp một tình huống mà tôi có một điều khiển WPF bị ràng buộc với một đối tượng cơ bản. Đối tượng cơ bản là lớn và chiếm một lượng lớn bộ nhớ. Kiểm soát WPF đã được thay thế bằng một phiên bản mới và vì lý do cũ vẫn bị treo xung quanh vì một số lý do. Điều này gây ra rò rỉ bộ nhớ lớn.

Trong hindsite mã được viết kém, nhưng vấn đề là bạn muốn đảm bảo rằng những thứ không được sử dụng sẽ vượt ra khỏi phạm vi. Người ta phải mất một thời gian dài để tìm thấy với một trình lược tả bộ nhớ vì thật khó để biết thứ gì trong bộ nhớ là hợp lệ và những gì không nên có.


2

Tôi cũng phải trả lời. JIT tạo các bảng cùng với mã từ phân tích tĩnh của việc sử dụng biến. Các mục trong bảng là "Rễ cây" trong khung ngăn xếp hiện tại. Khi con trỏ lệnh tiến lên, các mục trong bảng đó trở nên không hợp lệ và sẵn sàng để thu gom rác. Do đó: Nếu đó là một biến có phạm vi, bạn không cần đặt nó thành null - GC sẽ thu thập đối tượng. Nếu nó là thành viên hoặc biến tĩnh, bạn phải đặt nó thành null

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.