C# 正确处理闭包中维护的引用以避免内存泄漏 (太长了,读不下去了,想TL;DR,实际问题在结尾)< /P>
自从在C#中发现lambda和委托之后,我就成了它们的大消费者。然而,当涉及到释放闭包中维护的对象上的内存时,我一直在担心,特别是在处理嵌套闭包时。例如,从下面的一组类中考虑下面的内容:我认为是“适当的”<代码> NoTiffyPrimyType < /Cord>行为> < /P>C# 正确处理闭包中维护的引用以避免内存泄漏 (太长了,读不下去了,想TL;DR,实际问题在结尾)< /P>,c#,memory-leaks,lambda,delegates,C#,Memory Leaks,Lambda,Delegates,自从在C#中发现lambda和委托之后,我就成了它们的大消费者。然而,当涉及到释放闭包中维护的对象上的内存时,我一直在担心,特别是在处理嵌套闭包时。例如,从下面的一组类中考虑下面的内容:我认为是“适当的” NoTiffyPrimyType < /Cord>行为> < /P> public static PropertyChangedEventHandler GetHandler<TDependant, TDependantHost, TFoundation, TFoundationHost
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;
}
公共静态属性ChangedEventHandler GetHandler
(
这个TDependantHost目标,
PropertyChangedEventHandler调用获取,
表达式REF,
表达式基础REF,
表达式基础hostref
)
其中TDependantHost:ISupportsDependencyManager
其中TFoundationHost:class,INotifyPropertyChanged
{
字符串foundationName=GetPropertyInfo(foundationRef).Name;
字符串dependentName=GetPropertyInfo(dependentRef).Name;
字符串foundationHostName=GetPropertyInfo(foundationHostRef).Name;
Func foundationHostRefCompiled=foundationHostRef.Compile();
PropertyChangedEventHandler oOut=null;
//复杂的情况。这更复杂,因为TDependantHost与TFoundationHost有关系
//实际依赖项位于TFoundationHost中的属性上。
//oOut是将附加到目标的属性更改处理程序,因此需要
//-只要foundationHostRef将计算为其他对象,就会引发已更改的事件
//-每当发生更改时,将新的PropertyChangedEventHandler附加到new foundationHost
//-…它还处理从目标中移除自身,以保证
oOut=(s,e)=>
{
var sender=s作为INotifyPropertyChanged;
if(发送方==null)
返回;
如果(e.PropertyName==foundationHostName)
{
//基础主机已更改。因此,我们需要将新的内部属性更改为DevEnthHunter。
PropertyChangedEventHandler innerHandler=null;
内部处理器=
(s2,e2)=>
{
//呼叫安全。。。
var innerssender=s2作为TFoundationHost;
如果(innerSender==null)
返回;
//检查并查看此eventhandler是否仍然指向正确的对象
//如果有,我们将继续,否则,必须删除事件处理程序并返回
if(foundationHostRefCompiled(目标)!=innerSender)
{
innerSender.PropertyChanged-=innerHandler;
返回;
}
//现在我们知道,内部处理程序正在为仍然具有正确
和我们的目标有关系。所以我们还是像平常一样检查,基金会刚刚改变了吗?
//如果是,受抚养人也是如此
if(e2.PropertyName==foundationName)
SafeInvoke(目标,名称);
};
//因为基础已经移位,依赖性也将发生变化。
//为它提出一个处理程序。
invokeTarget.SafeInvoke(发送方,名称);
}
};
返回输出;
}
这应该做什么(它可能还需要测试,我想我需要在这里和那里进行一些空检查)是:
- 当目标上的引用类型属性发生更改时,引发从属属性的
事件(由PropertyChanged
标识)dependenref
- 另外,将<代码>属性更改处理程序附加到<代码>基础主机,以便如果基础更改,目标被通知
- 通过在执行事件处理程序之前解析
,并在发生这种情况时分离,确保目标不会收到与其不再相关的foundationHostRef
的通知foundationHost
foundationHost
s问题(这是一项正在进行的工作),任何被foundationHostRef
引用过的对象看起来都会保留对目标的闭包引用,即使它不再与目标相关,至少在它试图引发事件之前
现在我对这一点的理解是,我创建的事件处理程序可以很容易地阻止目标占用的内存被释放。需要做的就是让某个对象在某个时刻占据foundationHostRef
,然后被重新分配到其他位置,并且比target
的生命周期更长,这取决于target
正在做的事情,而这可能会让人讨厌(target
是一个不占用太多内存的单例)到灾难性的(target
在程序生命周期内被创建和置零数千次,并且具有一些占用大量内存的属性,而GC从不收集这些属性)
那么,我的问题是:针对这类事情有什么内置的保护?如果没有,我应该如何调整我的代理/lambda,使他们不再邪恶?可以通过两件事来降低这种风险: