Лямбда-выражение причина слабой ссылки не может быть GC? - PullRequest
2 голосов
/ 11 февраля 2009
namespace Test
{
    class Test
    {
        delegate void HandleMessage(string message);

        public void handleMessage(string message){}

        static void Main(string[] args)
        {
            HandleMessage listener1 = new Test().handleMessage;
            WeakReference w1 = new WeakReference(listener1);

            HandleMessage listener2 = (message) => { };
            WeakReference w2 = new WeakReference(listener2);

            Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
            Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

            listener1 = null;
            listener2 = null;
            GC.Collect();
            Console.WriteLine("after GC");

            Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
            Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

            Console.ReadLine();
        }
    }
}

Почему w2.Target не равен NULL после GC?

    w1.Target:      [Test.Test+HandleMessage]
    w2.Target:      [Test.Test+HandleMessage]
    after GC
    w1.Target:      []
    w2.Target:      [Test.Test+HandleMessage]

EDIT

Спасибо за все ответы, Брайан Расмуссен и Джон Скит, ваши ответы верны. Теперь я полностью понимаю, что происходит, поэтому я написал еще один пример, чтобы все было более наглядно.

Следующий пример показывает, что:

Если Test # create () не ссылается на какие-либо свойства или методы экземпляра, то компилятор создаст "приватное статическое HandleMessage CS $ <> 9__CachedAnonymousMethodDelegate1", как сказал Джон Скит. Это делает его более эффективным, когда вы использовать одно и то же лямбда-выражение несколько раз.

Если Test # create () ссылается на свойства или методы экземпляра, как в примере ниже, вызывая this.ToString (); тогда компилятор не может создать статический метод для замены логики метода intstance, поэтому после GC можно собрать экземпляр HandleMessage.

namespace Test
{
    class Test
    {
        public delegate void HandleMessage(string message);

        public void handleMessage(string message)
        {
        }

        public HandleMessage create()
        {
            return (message) => { 
                //this.ToString(); 
            };
        }       

        static void Main(string[] args)
        {
            HandleMessage listener1 = new Test().handleMessage;
            WeakReference w1 = new WeakReference(listener1);

            HandleMessage listener2 = new Test().create();//(message) => { };
            WeakReference w2 = new WeakReference(listener2);

            Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
            Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

            listener1 = null;
            listener2 = null;
            GC.Collect();
            Console.WriteLine("after GC");

            Console.WriteLine("w1.Target:\t[" + w1.Target + "]");
            Console.WriteLine("w2.Target:\t[" + w2.Target + "]");

            Console.ReadLine();
        }
    }
}

Ответы [ 3 ]

6 голосов
/ 11 февраля 2009

Это не имеет ничего общего с лямбдами. Такое же поведение можно наблюдать для анонимных делегатов. Так что если вы измените код на

HandleMessage listener2 = delegate(string message) => { };

вы получите тот же результат.

В первом случае у вас есть экземпляр метода для экземпляра Test. Поскольку у вас нет других ссылок на этот экземпляр, когда listener1 обнуляется, он может быть собран.

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

Взгляните на ИЛ, чтобы увидеть, как все устроено.

2 голосов
/ 11 февраля 2009

Лямбда-выражение кэшируется в статическом поле в классе - когда я его компилировал, оно было в CS$<>9__CachedAnonymousMethodDelegate1. Это делает его более эффективным, когда вы используете одно и то же лямбда-выражение несколько раз, но это означает, что оно не будет собирать мусор.

Посмотрите на сгенерированный IL, чтобы понять, что я имею в виду.

Если лямбда-выражение фиксирует какие-либо переменные, я не верю, что оно будет кэшировано (потому что этого не может быть!) Так что если вы измените свой код для использования:

string x = "hello";
HandleMessage listener2 = message => Console.WriteLine(x);

тогда вы увидите, что w2.Target станет нулевым после сборки мусора.

0 голосов
/ 11 февраля 2009

Общий шаблон для принудительного сбора памяти:

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

Более того, GC свободен не собирать вещи:)

...