代表们的GC,我错过了什么?(我的代表没有收集)

ang*_*son 8 .net c# delegates garbage-collection

我有一个持有代表的课程,以便稍后懒洋洋地评估一些事情.

一旦我评估了它,通过调用委托,我清除了对委托的引用,希望它有资格收集.毕竟,如果将它构造为匿名方法,它可能会保留一个局部变量的世界.

我尝试构建一个单元测试来验证这一点,但它似乎没有按照我计划的方式运行,而是看起来我的假设WeakReference(我在这里用于测试目的),或者其他一些假设,都没有.拿水.

看看这段代码,你可以在LINQPad中运行它

void Main()
{
    WeakReference wr;
    Lazy<int> l;
    CreateTestData(out wr, out l);

    wr.IsAlive.Dump();                  // should be alive here

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    wr.IsAlive.Dump();                  // and alive here as well
    l.Value.Dump();                     // but now we clear the reference

    GC.Collect();                       // so one of these should collect it
    GC.WaitForPendingFinalizers();
    GC.Collect();

    wr.IsAlive.Dump();                  // and then it should be gone here
    GC.KeepAlive(l);
}

void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
    Func<int> f = () => 10;
    wr = new WeakReference(f);
    l = new Lazy<int>(f);
}

public class Lazy<T>
{
    private Func<T> _GetValue;
    private T _Value;

    public Lazy(Func<T> getValue)
    {
        _GetValue = getValue;
    }

    public T Value
    {
        get
        {
            if (_GetValue != null)
            {
                _Value = _GetValue();
                _GetValue = null;
            }
            return _Value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我假设:

  1. 无论DEBUG构建,调试器附加,因为我在一个单独的方法中创建了委托,我从中返回,除了WeakReferenceLazy<T>对象之外,应该没有任何东西保留委托
  2. 如果我要求Lazy<T>对象放弃对委托的引用,这将减少对仅WeakReference持有的对象的引用
  3. 然后强制进行完整的垃圾收集,假设剩下的唯一引用就是那个 WeakReference
  4. 然后会收集委托,我WeakReference会表明该对象不再存在

因此预计代码的输出(带注释):

true  // not gc'ed after construction
true  // not gc'ed after full GC, still beind held by Lazy<T>
10    // value from calling delegate
false // but is now gc'ed, Lazy<T> no longer has a reference to it
Run Code Online (Sandbox Code Playgroud)

但相反输出是:

true
true
10
true
Run Code Online (Sandbox Code Playgroud)

任何人都可以了解我在这里缺少的东西吗?

Jon*_*eet 6

"问题"是编译器注意到它可以永远重用单个委托实例.它不捕获任何上下文,甚至不捕获隐式this引用.所以这:

void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
    Func<int> f = () => 10;
    ...
}
Run Code Online (Sandbox Code Playgroud)

变成了这样的东西:

static Func<int> hiddenDelegate;

static int HiddenMethod()
{
    return 10;
}

void CreateTestData(out WeakReference wr, out Lazy<int> l)
{
    if (hiddenDelegate == null)
    {
        hiddenDelegate = HiddenMethod;
    }

    Func<int> f = hiddenDelegate;
    ...
}
Run Code Online (Sandbox Code Playgroud)

查看ildasm(或没有打开优化的Reflector)中的代码,看看究竟发生了什么.