GC делегатов, что мне не хватает? (мой делегат не собран) - PullRequest
8 голосов
/ 30 ноября 2011

У меня есть класс, который держится за делегата, чтобы потом что-то лениво оценить.

Как только я оценил это, вызвав делегата, я очищаю ссылку на делегата, надеясь, чточто это будет иметь право на сбор.В конце концов, он мог бы держаться за мир локальных переменных, если бы он был создан как анонимный метод.

Я пытался создать модульный тест, чтобы проверить это, но, похоже, он не сработалЯ планировал, вместо этого кажется, что мои предположения о 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;
        }
    }
}

Я предполагал, что:

  1. Независимо от сборки DEBUG, отладчик подключен, поскольку я создал делегат в отдельном методеот которого я возвращаюсь, не должно быть ничего, что удерживало бы делегата, кроме объектов WeakReference и Lazy<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

Но вместо этого получается:

true
true
10
true

Может кто-нибудь пролить свет на то, что мне здесь не хватает?

1 Ответ

6 голосов
/ 30 ноября 2011

«Проблема» в том, что компилятор замечает, что он может повторно использовать один экземпляр делегата навсегда .Он не захватывает никакой контекст, даже неявную ссылку this.Итак, это:

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

Превращается во что-то вроде:

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;
    ...
}

Посмотрите код в ildasm (или Reflector без включенной оптимизации), чтобы точно узнать, что происходит.

...