事件侦听器代表中的自引用(C#)

时间:2011-07-21 16:49:48

标签: c# delegates event-handling garbage-collection

这可能是一个语义问题,但也许不是,所以我问:以下两个片段是否存在明显差异?

public Parent()
{
    Child newChild = new Child();
    newChild.RequestSpecialEvent += (sender, e) =>
    {
        newChild.DoMagic();
    }
}

public Parent()
{
    Child newChild = new Child();
    newChild.RequestSpecialEvent += (sender, e) =>
    {
        ((Child)sender).DoMagic();
    }
}

明显的区别是选项1类型的自引用本身,而选项2执行对象的转换。性能方面,我预计演员会更贵。

但是,我理论上在选项1中,它在技术上是“父”,其中包含对“newChild”的引用(通过Parent中定义的委托),所以即使newChild消失了(newChild = null或者某些东西)类似的),newChild对象不能被垃圾收集(gc'ed),因为Parent已经定义了一个仍然附加到它的委托。当父母最终离开时,newChild只能被gc'ed。

但是,在选项2中,Parent从不为newChild创建这样的“硬引用”,因此当newChild = null发生时,它确实可以立即被gc化。

我更喜欢选项1的简洁性和可读性,但担心选项2会更好。思考?我的理论是正确还是偏离基础?是否有替代或更优选的方法(有合理的推理)来声明与父/子类相同的事件监听器关系?

对@StriplingWarrior的回复:

关于垃圾收集,我仍然有点怀疑。委托引用newChild,所以对我来说,似乎newChild在代表离开之前不能消失。如果newChild消失了,那么代表将会离开......但是在代表离开之前,newChild仍然无法消失!似乎是圆形的(差不多)。似乎必须发生这种情况:

//newChild = null;
//This alone won't truly free up the newChild object because the delegate still
//points to the newChild object.

//Instead, this has to happen
newChild.RequestSpecialEvent = null; //destroys circular reference to newChild
newChild = null; //truly lets newChild object be gc'd

或者可以说'newChild = null;'只有,newChild.RequestSpecialEvent停止指向委托,这允许委托离开,然后允许newChild消失?也许我只是在谈谈你的答案。 :)

2 个答案:

答案 0 :(得分:2)

你的想法似乎非常明显,除了我非常确定newChild仍然可以在选项1中进行垃圾收集,因为引用它的委托本身仅由{{{{1}处理程序引用。 1}}本身。

两个片段在功能上是等效的,选项1使用稍微更多的内存,因为委托中有额外的引用,但是在调用时稍快一些,因为它避免了强制转换。这两种方式的区别是微不足道的,我建议使用最干净的(#1,如果你问我)。

如果你想将同一个委托应用于多个控件,那么我唯一一次使用类似#2的东西。在这种情况下,这将最终使用更少的内存:

newChild

另外需要注意的一点是,由于选项#1引用了var handler = new RequestSpecialEventHandler((sender, e) => { ((Child)sender).DoMagic(); }); foreach(var child in children) { child.RequestSpecialEvent += handler; } 变量,如果该变量的值稍后在方法中发生变化,则在调用处理程序时将使用新值。例如,在此示例中:

newChild

...每当事件触发任何这些孩子时,“魔法”将仅在foreach(var child in children) { child.RequestSpecialEvent += (sender, e) => { // BEWARE: Don't do this! Modified closure! child.DoMagic(); }; } 集合中的最后一个孩子上执行N次

答案 1 :(得分:2)

循环引用不是.Net GC的问题,因为它不使用引用计数来识别活动对象。 GC将识别保证仍在使用的引用,称为Roots(例如静态引用,当前正在执行的方法的堆栈上的引用等)。这些根引用的对象保证是活着的。传递性地,这些对象所引用的对象保证是活着的。因此,GC可以跟踪从根部到已知存在的所有对象的路径。其余的都死了。

在您的循环引用示例中,没有指向newChild的根。因此,它有资格进行收集,即使事件处理程序引用了newChild,它引用了事件处理程序,它引用了newChild ......

示例中的Parent类不保留对newChild的任何引用 - 它只是在Parent的构造函数中创建为本地。因此,在分配事件处理程序后,它应该有资格直接收集。

这是关于GC的好文章:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx