C# 正确处理闭包中维护的引用以避免内存泄漏 (太长了,读不下去了,想TL;DR,实际问题在结尾)< /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

自从在C#中发现lambda和委托之后,我就成了它们的大消费者。然而,当涉及到释放闭包中维护的对象上的内存时,我一直在担心,特别是在处理嵌套闭包时。例如,从下面的一组类中考虑下面的内容:我认为是“适当的”<代码> NoTiffyPrimyType < /Cord>行为> < /P>
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,使他们不再邪恶?

可以通过两件事来降低这种风险:

  • 如果您认为某段代码存在风险,请不要在那里使用lambdas。使用旧方法进行闭包:在类上创建实例方法
  • 安装。看到常见的C#习惯用法所做的所有分配是很有教育意义的。插件告诉你哪些变量被关闭了。R#也会对“隐式”捕获的变量发出警告(不确定这意味着什么,但听起来像是对您有用的警告)
  • 它看起来像任何物体