Tôi đã giải thích sự nhầm lẫn này trong một blog tại https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Tôi sẽ cố gắng tóm tắt nó ở đây để bạn có thể có một ý tưởng rõ ràng.
Phương tiện tham khảo, "Cần":
Trước hết, bạn cần hiểu rằng, nếu đối tượng A giữ một tham chiếu đến đối tượng B, thì, nó sẽ có nghĩa là, đối tượng A cần đối tượng B để hoạt động, phải không? Vì vậy, người thu gom rác sẽ không thu thập đối tượng B miễn là đối tượng A còn sống trong bộ nhớ.
Tôi nghĩ phần này nên rõ ràng đối với một nhà phát triển.
+ = Có nghĩa là, tiêm tham chiếu của đối tượng bên phải vào đối tượng bên trái:
Nhưng, sự nhầm lẫn đến từ toán tử C # + =. Toán tử này không nói rõ cho nhà phát triển rằng, phía bên phải của toán tử này thực sự đang tiêm một tham chiếu đến đối tượng phía bên trái.
Và bằng cách đó, đối tượng A nghĩ rằng, nó cần đối tượng B, mặc dù, theo quan điểm của bạn, đối tượng A không nên quan tâm liệu đối tượng B có sống hay không. Vì đối tượng A nghĩ rằng đối tượng B là cần thiết, đối tượng A bảo vệ đối tượng B khỏi bộ thu gom rác miễn là đối tượng A còn sống. Nhưng, nếu bạn không muốn sự bảo vệ đó được trao cho đối tượng người đăng ký sự kiện, thì, bạn có thể nói, đã xảy ra rò rỉ bộ nhớ.
Bạn có thể tránh rò rỉ như vậy bằng cách tách bộ xử lý sự kiện.
Làm thế nào để đưa ra quyết định?
Nhưng, có rất nhiều sự kiện và trình xử lý sự kiện trong toàn bộ cơ sở mã của bạn. Có nghĩa là, bạn cần phải tiếp tục xử lý sự kiện ở khắp mọi nơi? Câu trả lời là Không. Nếu bạn phải làm như vậy, cơ sở mã của bạn sẽ thực sự xấu xí với verbose.
Bạn có thể theo một biểu đồ dòng đơn giản để xác định xem một trình xử lý sự kiện tách rời có cần thiết hay không.
Hầu hết thời gian, bạn có thể thấy đối tượng người đăng ký sự kiện cũng quan trọng như đối tượng nhà xuất bản sự kiện và cả hai được cho là đang sống cùng một lúc.
Ví dụ về một kịch bản mà bạn không cần phải lo lắng
Ví dụ, một sự kiện nhấn nút của một cửa sổ.
Ở đây, nhà xuất bản sự kiện là Nút và người đăng ký sự kiện là MainWindow. Áp dụng biểu đồ luồng đó, đặt câu hỏi, Cửa sổ chính (người đăng ký sự kiện) có bị chết trước Nút (nhà xuất bản sự kiện) không? Rõ ràng là không phải không? Điều đó thậm chí sẽ không có ý nghĩa. Sau đó, tại sao phải lo lắng về việc tách trình xử lý sự kiện nhấp?
Một ví dụ khi tách biệt xử lý sự kiện là PHẢI.
Tôi sẽ cung cấp một ví dụ trong đó đối tượng thuê bao được cho là đã chết trước đối tượng nhà xuất bản. Giả sử, MainWindow của bạn xuất bản một sự kiện có tên "SomethingHappened" và bạn hiển thị một cửa sổ con từ cửa sổ chính bằng một nút bấm. Cửa sổ con đăng ký vào sự kiện đó của cửa sổ chính.
Và, cửa sổ con đăng ký một sự kiện của Cửa sổ chính.
Từ mã này, chúng ta có thể hiểu rõ rằng có một nút trong Cửa sổ chính. Nhấp vào nút đó sẽ hiển thị Cửa sổ con. Cửa sổ con lắng nghe một sự kiện từ cửa sổ chính. Sau khi làm một cái gì đó, người dùng đóng cửa sổ con.
Bây giờ, theo biểu đồ luồng mà tôi đã cung cấp nếu bạn đặt câu hỏi "Cửa sổ con (người đăng ký sự kiện) có bị chết trước nhà xuất bản sự kiện (cửa sổ chính) không? Câu trả lời phải là CÓ. Tôi thường làm điều đó từ sự kiện Unloaded của Window.
Nguyên tắc chung: Nếu chế độ xem của bạn (ví dụ: WPF, WinForm, UWP, Xamarin Form, v.v.) đăng ký vào một sự kiện của ViewModel, hãy luôn nhớ tách trình xử lý sự kiện. Bởi vì ViewModel thường sống lâu hơn một khung nhìn. Vì vậy, nếu ViewModel không bị hủy, mọi chế độ xem đã đăng ký của ViewModel đó sẽ nằm trong bộ nhớ, điều này không tốt.
Bằng chứng về khái niệm bằng cách sử dụng một hồ sơ bộ nhớ.
Sẽ không có gì thú vị nếu chúng ta không thể xác nhận khái niệm bằng trình lược tả bộ nhớ. Tôi đã sử dụng trình tạo hồ sơ dotMemory JetBrain trong thử nghiệm này.
Đầu tiên, tôi đã chạy MainWindow, hiển thị như thế này:
Sau đó, tôi chụp ảnh kỷ niệm. Sau đó tôi bấm nút 3 lần . Ba cửa sổ con hiện lên. Tôi đã đóng tất cả các cửa sổ con đó và nhấp vào nút Force GC trong trình lược tả dotMemory để đảm bảo rằng Trình thu gom rác được gọi. Sau đó, tôi chụp một bức ảnh chụp bộ nhớ khác và so sánh nó. Hãy chứng kiến! nỗi sợ của chúng tôi là sự thật. Cửa sổ trẻ em không được người thu gom rác thu thập ngay cả sau khi chúng bị đóng. Không chỉ vậy, số lượng đối tượng bị rò rỉ cho đối tượng ChildWindow cũng được hiển thị " 3 " (Tôi đã nhấp vào nút 3 lần để hiển thị 3 cửa sổ con).
Ok, sau đó, tôi tách trình xử lý sự kiện như dưới đây.
Sau đó, tôi đã thực hiện các bước tương tự và kiểm tra trình lược tả bộ nhớ. Lần này, wow! không bị rò rỉ bộ nhớ.