委托不会被垃圾回收

5
以下是一个演示问题的控制台应用程序:
class Program
{
    static void Main()
    {
        InitRefs();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(_refObj.IsAlive);
        Console.WriteLine(_refAction.IsAlive);
        Console.WriteLine(_refEvent.IsAlive);
        Console.ReadKey();
    }

    private static void InitRefs()
    {
        _refObj = new WeakReference(new object());
        _refAction = new WeakReference((Action) (() => { }));
        _refEvent = new WeakReference(new EventHandler((sender, eventArgs) => { }));
    }

    private static WeakReference _refObj;
    private static WeakReference _refAction;
    private static WeakReference _refEvent;
}

输出为“False True True”。 我使用了SOS.dll来尝试找出阻止委托被垃圾回收的原因,以下是Action的结果:
!gcroot 02472584
HandleTable:
    006613ec (pinned handle)
    -> 03473390 System.Object[]
    -> 02472584 System.Action

有人能解释一下发生了什么吗?

@Baldrick,看起来是这样。 - Pavel Tupitsyn
1个回答

6

你的代理不会捕获任何内容,因此编译器基本上会对它们进行缓存。您可以通过以下简短程序来看到其效果:

using System;

class Program
{
    static void Main()
    {
        Action action1 = GetAction();
        Action action2 = GetAction();
        Console.WriteLine(ReferenceEquals(action1, action2)); // True
    }

    private static Action GetAction()
    {
        return () => {};
    }
}

在这个类中有自动生成的静态字段,其会被延迟填充。基本上这是一种优化方法,避免创建许多委托对象,它们都指向同一个静态方法,没有上下文来区分它们。
是的,这意味着委托本身将不会被垃圾回收 - 但它们非常轻量级(它们不会阻止任何其他变量被垃圾回收,因为它们不会捕获任何变量)。
例如,在 InitRefs 方法更改为以下情况下,委托无法被缓存(因此可以进行垃圾回收):
private static void InitRefs(int x)
{
    _refObj = new WeakReference(new object());
    _refAction = new WeakReference((Action) (() => x.ToString() ));
    _refEvent = new WeakReference(new EventHandler((sender, eventArgs) => 
                                                         x.ToString()));
}

然后将打印 False 三次,因为委托捕捉了 x 参数,所以无法被缓存。


这很有趣,但我尝试捕获成员变量,但什么也没有改变:_refAction = new WeakReference((Action)(() => { if (_refEvent.IsAlive) throw new Exception(); })); - Pavel Tupitsyn
你能举一个委托被垃圾回收的例子吗? - Pavel Tupitsyn
@Kefir:这并没有捕获任何东西 - _refEvent 是一个静态变量,因此它没有任何上下文。尝试向 InitRefs 添加一个参数并捕获 - Jon Skeet
谢谢,明白了。使用捕获的本地变量后,它被收集了。 - Pavel Tupitsyn
@Kefir:是的。我已经在答案中添加了一个示例。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接