C# MakeGenericType/泛型类型是否被垃圾收集?

C# MakeGenericType/泛型类型是否被垃圾收集?,c#,.net,generics,types,garbage-collection,C#,.net,Generics,Types,Garbage Collection,在.NET中,类型是不被垃圾收集的,这意味着如果你在玩f.ex。发射,你必须小心卸载AppDomains等等。。。至少我以前是这样理解事情的 这让我想知道泛型类型是否是垃圾收集的,更准确地说:泛型是用MakeGenericType创建的,比方说。。。例如,基于用户输入。:-) 因此,我构建了以下测试用例: public interface IRecursiveClass { int Calculate(); } public class RecursiveClass1<T>

在.NET中,类型是不被垃圾收集的,这意味着如果你在玩f.ex。发射,你必须小心卸载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();
    }
}
公共接口IRecuriveClass
{
int计算();
}
公共类RecursiveClass1:IRecursiveClass
其中T:IRecursiveClass,new()
{
公共整数计算()
{
返回新的T().Calculate()+1;
}
}
公共类递归类2:IRecursiveClass
其中T:IRecursiveClass,new()
{
公共整数计算()
{
返回新的T().Calculate()+2;
}
}
公共类TailClass:IRecursiveClass
{
公共整数计算()
{
返回0;
}
}
类递归GenericsTest
{
公共静态int CalculateFromUserInput(字符串str)
{
类型tail=类型of(TailClass);
foreach(str中的字符c)
{
如果(c==0)
{
tail=typeof(RecursiveClass1)。MakeGenericType(tail);
}
其他的
{
tail=typeof(递归类2)。MakeGenericType(tail);
}
}
IRecursiveClass cl=(IRecursiveClass)Activator.CreateInstance(tail);
返回cl.Calculate();
}
静态长记忆
{
得到
{
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
返回GC.GetTotalMemory(true);
}
}
静态void Main(字符串[]参数)
{
长启动=记忆;
int-total=0;
对于(int i=0;i<1000000;++i)
{
StringBuilder sb=新的StringBuilder();
int j=i;
for(int k=0;k<20;++k)//修复递归深度
{
如果((j&1)==1)
{
某人附加('1');
}
其他的
{
某人附加('0');
}
j>>=1;
}
total+=CalculateFromUserInput(sb.ToString());
如果((i%10000)==0)
{
WriteLine(“当前内存使用@{0}:{1}”,
i、 备忘录(启动);
}
}
WriteLine(“完成,总数为{0}”,总计);
WriteLine(“当前内存使用情况:{0}”,MemoryUsage-start);
Console.ReadLine();
}
}
如您所见,泛型类型定义为“可能是递归的”,带有一个标记递归结束的“tail”类。为了确保
GC.TotalMemoryUsage
没有作弊,我还打开了任务管理器

到目前为止还不错。我做的下一件事是点燃这只野兽,在我等待“记忆不足”的时候。。。我注意到,与我的预期相反,并没有随着时间消耗更多的内存。事实上,它显示内存消耗在时间上略有下降


有人能解释一下吗?GC是否实际收集了泛型类型?如果是的话。。。是否也存在反射。发出垃圾收集的案例?

回答第一个问题:

不收集类型的泛型构造

但是,如果构造
C
C
,CLR实际上只为这些方法生成一次代码;因为对字符串的引用和对对象的引用保证大小相同,所以它可以安全地这样做。这很聪明。但是,如果构造
C
C
,则方法的代码将生成两次,每次构造一次。(当然,假设方法的代码是生成的;方法是根据需要进行jitting的;这就是为什么它被称为jitting的原因。)

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

class C<T> { public static readonly T Big = new T[10000]; }
class C{public static readonly T Big=new T[10000];}
C
C
共享为方法生成的任何代码,但每个方法都有自己的静态字段,这些字段将永远存在。构造的类型越多,这些大数组占用的内存就越多

现在你知道为什么不能收集这些类型了;我们无法知道将来是否有人试图访问其中一个阵列的成员。因为我们不知道最后一次数组访问将在何时进行,所以它们必须永远存在,因此包含它的类型也必须永远存在


回答第二个问题:是否有一种方法可以生成收集的动态发出的程序集

对。文件如下:


与代码共享或代码不共享无关,每次MakeGenericType尝试都将为metada创建新的内部CLR类,这将消耗内存。类型对象是直接在CLR代码中创建的(不是在托管代码中),每个类型对象只存在一个实例,因此您可以比较它们以获得引用相等性。CLR本身有一个对它的引用,所以它们不能被GC’ed,但在我的测试中,我确认GC可以移动它们

编辑:CLR保留的引用可能是弱引用,所以在挖掘RuntimeTypeHandle.cs源代码后,我看到了

internal bool IsCollectible()
{
    return RuntimeTypeHandle.IsCollectible(GetTypeHandleInternal());
}

这很可能是错误的,考虑到Eric Lippert

,您可以通过为您创建的泛型类型创建一个
WeakReference
,然后在运行GC传递后检查
目标
是否为null来进行试验。也许这将有助于@ErikLippert哇,这是一个很好的答案Eric,谢谢!我发现了关于static的问题,这就是为什么我没有在类中添加static。我还是