是否收集了MakeGenericType /泛型类型的垃圾?

atl*_*ste 11 .net c# generics garbage-collection types

在.NET中众所周知,类型不是垃圾收集,这意味着如果你正在玩f.ex. Reflection.Emit,你必须要小心卸载AppDomains等等......至少我是如何理解事情是如何运作的.

这让我想知道泛型类型是否是垃圾收集的,更准确一点:用MakeGenericType例如基于用户输入创建的泛型.:-)

所以我构建了以下测试用例:

public interface IRecursiveClass
{
    int Calculate();
}

public class RecursiveClass1<T> : IRecursiveClass 
                                  where T : IRecursiveClass,new()
{
    public int Calculate()
    {
        return new T().Calculate() + 1;
    }
}
public class RecursiveClass2<T> : IRecursiveClass
                                  where T : IRecursiveClass,new()
{
    public int Calculate()
    {
        return new T().Calculate() + 2;
    }
}

public class TailClass : IRecursiveClass
{
    public int Calculate()
    {
        return 0;
    }
}

class RecursiveGenericsTest
{
    public static int CalculateFromUserInput(string str)
    {
        Type tail = typeof(TailClass);
        foreach (char c in str)
        {
            if (c == 0)
            {
                tail = typeof(RecursiveClass1<>).MakeGenericType(tail);
            }
            else
            {
                tail = typeof(RecursiveClass2<>).MakeGenericType(tail);
            }
        }
        IRecursiveClass cl = (IRecursiveClass)Activator.CreateInstance(tail);
        return cl.Calculate();
    }

    static long MemoryUsage
    {
        get
        {
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
            return GC.GetTotalMemory(true);
        }
    }

    static void Main(string[] args)
    {
        long start = MemoryUsage;

        int total = 0;
        for (int i = 0; i < 1000000; ++i)
        {
            StringBuilder sb = new StringBuilder();
            int j = i;
            for (int k = 0; k < 20; ++k) // fix the recursion depth
            {
                if ((j & 1) == 1)
                {
                    sb.Append('1');
                }
                else
                {
                    sb.Append('0');
                }
                j >>= 1;
            }

            total += CalculateFromUserInput(sb.ToString());

            if ((i % 10000) == 0)
            {
                Console.WriteLine("Current memory usage @ {0}: {1}", 
                                  i, MemoryUsage - start);
            }
        }

        Console.WriteLine("Done and the total is {0}", total);
        Console.WriteLine("Current memory usage: {0}", MemoryUsage - start);

        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,泛型类型被定义为"可能是递归的",其中"tail"类标记了递归的结束.为了确保GC.TotalMemoryUsage不作弊,我还开了任务经理.

到现在为止还挺好.我做的下一件事就是把这只野兽解雇,当我在等待"内存不足"时......我注意到 - 这与我的期望相反 - 随着时间的推移没有消耗更多的内存.事实上,它显示了内存消耗的轻微下降.

有人可以解释一下吗?GC是否实际收集了泛型类型?如果是这样的话......还有Reflection.Emit案例是垃圾收集?

Eri*_*ert 19

回答你的第一个问题:

不收集类型的通用构造.

但是,如果构造C<string>C<object>,CLR实际上只生成一次方法代码 ; 因为对字符串的引用和对象的引用保证是相同的大小,所以它可以安全地执行.这很聪明.如果你构造C<int>,C<double>但是,方法的代码生成两次,每次构造一次.(假设方法的代码当然是生成的;方法是按需进行的;这就是为什么它被称为jitting.)

要演示不收集泛型类型,请创建泛型类型

class C<T> { public static readonly T Big = new T[10000]; }
Run Code Online (Sandbox Code Playgroud)

C<object>C<string>共享为方法生成的任何代码,但每个代码都有自己的静态字段,这些字段将永远存在.构造的类型越多,用这些大数组填充的内存就越多.

现在你知道为什么这些类型无法收集; 我们无法知道某人是否会在将来的任何时间尝试访问其中一个阵列的成员.由于我们不知道最后一个数组访问的时间,它们必须永远存在,因此包含它的类型也必须永远存在.


回答你的第二个问题:有没有办法制作动态发射的集合?

是.文档在这里:

http://msdn.microsoft.com/en-us/library/dd554932.aspx

  • @supercat:魔术! (3认同)