Могут ли делегаты вызвать утечку памяти? GC.TotalMemory(true), похоже, указывает

код

using System;
internal static class Test
{
    private static void Main()
    {
        try
        {
            Console.WriteLine("{0,10}: Start point", GC.GetTotalMemory(true));
            Action simpleDelegate = SimpleDelegate;
            Console.WriteLine("{0,10}: Simple delegate created", GC.GetTotalMemory(true));
            Action simpleCombinedDelegate = simpleDelegate + simpleDelegate + simpleDelegate;
            Console.WriteLine("{0,10}: Simple combined delegate created", GC.GetTotalMemory(true));
            byte[] bigManagedResource = new byte[100000000];
            Console.WriteLine("{0,10}: Big managed resource created", GC.GetTotalMemory(true));
            Action bigManagedResourceDelegate = bigManagedResource.BigManagedResourceDelegate;
            Console.WriteLine("{0,10}: Big managed resource delegate created", GC.GetTotalMemory(true));
            Action bigCombinedDelegate = simpleCombinedDelegate + bigManagedResourceDelegate;
            Console.WriteLine("{0,10}: Big combined delegate created", GC.GetTotalMemory(true));
            GC.KeepAlive(bigManagedResource);
            bigManagedResource = null;
            GC.KeepAlive(bigManagedResourceDelegate);
            bigManagedResourceDelegate = null;
            GC.KeepAlive(bigCombinedDelegate);
            bigCombinedDelegate = null;
            Console.WriteLine("{0,10}: Big managed resource, big managed resource delegate and big combined delegate removed, but memory not freed", GC.GetTotalMemory(true));
            GC.KeepAlive(simpleCombinedDelegate);
            simpleCombinedDelegate = null;
            Console.WriteLine("{0,10}: Simple combined delegate removed, memory freed, at last", GC.GetTotalMemory(true));
            GC.KeepAlive(simpleDelegate);
            simpleDelegate = null;
            Console.WriteLine("{0,10}: Simple delegate removed", GC.GetTotalMemory(true));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
        Console.ReadKey(true);
    }
    private static void SimpleDelegate() { }
    private static void BigManagedResourceDelegate(this byte[] array) { }
}

Выход

GC.TotalMemory(true)
    105776: Start point
    191264: Simple delegate created
    191328: Simple combined delegate created
 100191344: Big managed resource created
 100191780: Big managed resource delegate created
 100191812: Big combined delegate created
 100191780: Big managed resource, big managed resource delegate and big combined delegate removed, but memory not freed
    191668: Simple combined delegate removed, memory freed, at last
    191636: Simple delegate removed

Ответы

Ответ 1

Интересный случай. Вот решение:

enter image description here

Объединение делегатов чисто наблюдательно. Похоже, что делегаты неизменны для внешних. Но внутри, существующие делегаты изменяются. При определенных условиях они совместно используют те же _invocationList по соображениям производительности (оптимизируя для сценария, что несколько делегатов подключены к одному и тому же событию). К сожалению, _invocationList для simpleCombinedDelegate ссылается на bigMgdResDelegate, который заставляет память оставаться в живых.

Ответ 2

Мне может не хватать точки здесь, но сбор мусора по дизайну недетерминирован. Ergo, это зависит от платформы .NET, чтобы решить, когда она восстанавливает память.

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