2
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();
        }
    }
}

GC後にw2.Targetがnullにならないのはなぜですか?

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

編集

すべての答えに感謝します、ブライアン・ラスムッセンとジョン・スキートあなたの答えは正しいです。今、私は何が起こっているのかを完全に理解しているので、すべてをより明確にするために別の例を書きました。

次の例は、次のことを示しています。

Test#create()がインスタンスのプロパティやメソッドを参照しない場合、Jon Skeetが言ったように、「private static HandleMessage CS $ <> 9__CachedAnonymousMethodDelegate1」がコンパイラによって作成されます。これにより、同じものを使用すると効率が向上します。ラムダ式を複数回。

Test#create()がインスタンスのプロパティまたはメソッドを参照する場合、以下の例のようにthis.ToString();を呼び出します。その場合、コンパイラはインスタンスメソッドのロジックを置き換える静的メソッドを作成できないため、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();
        }
    }
}
4

3 に答える 3

6

ラムダとは関係ありません。匿名デリゲートでも同じ動作が見られます。したがって、コードを次のように変更すると、

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

同じ結果が得られます。

最初のケースでは、Test のインスタンスにインスタンス メソッドがあります。が null の場合、このインスタンスへの他の参照がないためlistener1、収集される可能性があります。

2 番目のケースでは、無名メソッドを何らかの型に配置する必要があります (メソッドは単独では存在できないため)。この場合、コンパイラは匿名メソッドを静的メソッドとしてTestクラスに配置します。さらに、参照はTest型の静的メンバーに格納されます。したがってType、メソッドへの静的参照もあり、それがコレクションを存続させる理由です。

IL を見て、物事がどのように配線されているかを確認してください。

于 2009-02-11T09:39:33.277 に答える
2

ラムダ式はクラスの静的フィールドにキャッシュされます-コンパイルしたとき、それはにありましたCS$<>9__CachedAnonymousMethodDelegate1。これにより、同じラムダ式を複数回使用する場合の効率が向上しますが、ガベージコレクションが行われないことを意味します。

生成されたILを見て、私が何を意味するかを確認してください。

ラムダ式が変数をキャプチャする場合、キャッシュされるとは思わない(キャッシュできないため)。したがって、使用するようにコードを変更した場合:

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

w2.Targetその後、ガベージコレクション後にnullになることがわかります。

于 2009-02-11T09:26:14.993 に答える
-1

force-collect-memoryの一般的なパターンは次のとおりです。

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

さらに、GCは物を集めないように自由です:)

于 2009-02-11T09:21:55.550 に答える