(前缀:对于那些看到这个并且认为TL; DR,实际问题在最后的人)
自从在C#中发现lambda和代表以来,我已成为他们的一大消费者。但是,当涉及在闭包中维护的对象上释放内存时,我一直关注,特别是在处理嵌套闭包时。例如,考虑下面的一组课程我写的是我认为适当的"适当的" INotifyPropertyChanged
行为。
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;
}
这应该做什么(它可能 - 仍然需要测试,我认为我需要在这里和那里进行几次空检查):
PropertyChanged
事件(由dependantRef
标识)PropertyChanged
处理程序附加到foundationHost
,以便在基础发生变化时,通知目标foundationHost
并在发生这种情况时进行分离,确保目标不会收到与foundationHostRef
不再相关的foundationHost
的通知。因此,使用上述逻辑,并将foundationHostRef
s嵌套多层深层的问题(这是一项正在进行的工作)放在一边,它看起来像任何已被引用的对象foundationHostRef
将保持对目标的闭包引用,即使它与它不再相关,至少在它试图引发事件之前。
现在我对此的理解是,我创建的事件处理程序可以轻松地阻止目标占用的内存被释放。所有需要发生的事情都是某个对象在某个时刻占用target
,然后在其他地方重新分配,并且其生命周期比target
更长,具体取决于target
正在做什么从烦人的(target
是一个不占用大量内存的单身人物)到灾难性的({{1}}在程序生命周期内被创建和消除了数千次,并且有一些属性占用了很多内存,GC从不收集它。)
所以,我的问题:对于这种事情,有什么内在的保护措施,如果有的话?如果没有,我应该如何调整我的代表/ lambdas,以便他们不再是邪恶的?
答案 0 :(得分:1)
您可以通过两件事来减轻这种风险:
看起来像任何被FoundationHostRef引用的对象都会保持对目标的闭包引用,即使它与它不再相关
我完全不遵守上面给出的代码。从来没有从闭包中引用无限量的对象。对象引用的数量是不变的。
但是,可能存在的问题是,看似未使用或超出范围的局部变量仍然会影响强引用。这是因为C#编译器在关闭变量未使用时不会将其置空。对于普通的当地人来说,它不会这样做,但JIT足够聪明,可以做同样的事情。
在我知道的每种情况下,显式地清空你想要清除的变量(尽管它可能不受C#规范保证)。