(前綴:對於那些誰看到這一點,認爲TL; DR,實際的問題是,在年底)在關閉以避免內存保持引用正確處理泄漏
由於在C#中發現lambda表達式和委託,我已經成爲他們的大消費者。然而,當涉及到釋放關閉中維護的對象的內存時,尤其是在處理嵌套閉包時,我一直存在擔憂。例如,考慮從我正在寫的我認爲是「合適的」行爲的一組課程的下面。
public static PropertyChangedEventHandler GetHandler<TDependant, TDependantHost, TFoundation, TFoundationHost>
(
this TDependantHost target,
PropertyChangedEventHandler invokeTarget,
Expression<Func<TDependantHost, TDependant>> dependantRef,
Expression<Func<TFoundationHost, TFoundation>> foundationRef,
Expression<Func<TDependantHost, TFoundationHost>> foundationHostRef
)
where TDependantHost : ISupportsDependencyManager
where TFoundationHost : class, INotifyPropertyChanged
{
string foundationName = GetPropertyInfo(foundationRef).Name;
string dependantName = GetPropertyInfo(dependantRef).Name;
string foundationHostName = GetPropertyInfo(foundationHostRef).Name;
Func<TDependantHost, TFoundationHost> foundationHostRefCompiled = foundationHostRef.Compile();
PropertyChangedEventHandler oOut = null;
// Complex situation. This is more complex because whilst TDependantHost bears a relationship to TFoundationHost
// the actual dependency is on a property in TFoundationHost.
// oOut is the property changed handler that will be attached to target, so it needs to
// - Raise changed events whenever foundationHostRef would evaluate to a different object
// - Whenever that change occurs, attach a new PropertyChangedEventHandler to the new foundationHost
// - ... which also handles removal of itself from target so as to guarantee
oOut = (s, e) =>
{
var sender = s as INotifyPropertyChanged;
if (sender == null)
return;
if (e.PropertyName == foundationHostName)
{
// The Foundation Host has changed. So we need to attach a new inner PropertyChangedEventHandler to it.
PropertyChangedEventHandler innerHandler = null;
innerHandler =
(s2, e2) =>
{
// Caller safety...
var innerSender = s2 as TFoundationHost;
if (innerSender == null)
return;
// Check and see if this eventhandler still points to the right object
// If it does, we'll keep going - otherwise, got to remove the event handler and return
if (foundationHostRefCompiled(target) != innerSender)
{
innerSender.PropertyChanged -= innerHandler;
return;
}
// Now we know that the inner handler is executing for an entity that still bears the correct
// relationship to target. So we just check the same way as usual - did foundation just change?
// If so, so did dependant
if (e2.PropertyName == foundationName)
invokeTarget.SafeInvoke(target, dependantName);
};
// since the foundation has shifted, the dependency will also have changed
// Raise a handler for it.
invokeTarget.SafeInvoke(sender, dependantName);
}
};
return oOut;
}
什麼,這是應該做的(它可能 - 仍需雖然測試,我想我需要一對夫婦在這裏和那裏null檢查)是:
- 當引用類型目標的變化特性,提出一個
PropertyChanged
事件的相關屬性(由dependantRef
標識) - 此外,附加
PropertyChanged
處理程序foundationHost
,因此,如果基礎發生變化,目標得到通知 - 通過在執行事件處理程序之前解析
foundationHostRef
,確保目標不會收到與此不相關的foundationHost
的通知,並在發生此情況時將其解除。
因此,使用上述的邏輯,和撇開,它們嵌套foundationHost
S上的問題多於一層深(這是在正在進行的工作),它看起來像,迄今爲止,通過foundationHostRef
提及的任何對象將保持對目標的封閉引用,即使它不再與其相關聯,至少在它試圖提出事件之前。
現在我對此的理解是,我創建的事件處理程序可以輕鬆地停止目標占用的內存被釋放。所有需要發生的事情都是某個對象在某個時間點佔用foundationHostRef
,然後在其他地方重新分配,並且使用壽命比target
更長,具體取決於target
正在做什麼,可能來自煩人的任何地方(target
是一個單身人士, (佔用大量內存)到災難性的(target
在程序生命週期中被創建並且被禁止了數千次,並且具有佔用大量內存的一些屬性,並且GC從不收集它)。
所以,我的問題:什麼建立在保護是否存在,如果有的話,反對這種事情?如果沒有,我應該如何調整我的代表/ lambda,以便它們不再是邪惡的?
ReSharper可能會幫助,但我的試用許可證已過期,並且暫時沒有發現我使用它足以證明購買完整許可證的理由。儘管知道未使用的局部變量仍然可以產生強烈的參考,但有用。我正在考慮將參數轉換爲'WeakReference'以便在lambdas中使用,但是這聽起來不夠遠。不過,如果我在生成'WeakReference'之後將參數歸爲空,那可能會起作用,我在想...... – tobriand 2015-04-03 11:56:25
請注意,WeakReference引入了語義變化。如果不存在其他參考,WeakReference最終將在隨機時間點變爲空。這通常是一種更安全,更安全的方法來管理引用,而不是使用弱引用。 – usr 2015-04-03 11:58:43
非常真實 - 我剛剛有一種感覺,重構一個弱引用並檢查一致性要比依靠強大的引用更容易,我不想在Target發佈後實施其他引用。這聽起來像是唯一的方法是在(擴展名)方法結束之前將target和invokeTarget設爲null。因此,我可能因此大部分時間都會承受語義變化,因爲我首先檢查空位和實時狀態。 – tobriand 2015-04-03 12:42:18