It is well known in .NET that types are not garbage collected, which means that if you're playing around with f.ex. Reflection.Emit, you have to be careful to unload AppDomains and so on... At least that's how I used to understand how things work.
That made me wonder if generic types are garbage collected, to be more precise: generics created with MakeGenericType
, let's say... for example based on user input. :-)
So I constructed the following test case:
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();
}
}
As you can see, the generic types are defined 'possibly recursive', with a 'tail' class that marks the end of the recursion. And to ensure that GC.TotalMemoryUsage
isn't cheating, I also opened Task Manager.
So far so good. Next thing I did was fire this beast up and while I was waiting for an 'Out of memory' ... I noticed that it was - contrary to my expectations - not consuming more memory over time. In fact, it shows a slight drop in memory consumption in time.
Can someone please explain this? Are generic types actually collected by the GC? And if so... are there also Reflection.Emit cases that are garbage collected?
To answer your first question:
Generic constructions of types are not collected.
However, if you construct C<string>
and C<object>
, the CLR actually generates the code for the methods only once; since reference to string and reference to object are guaranteed to be the same size, it can do so safely. It's pretty clever. If you construct C<int>
and C<double>
though, the code for the methods gets generated twice, once for each construction. (Assuming that the code for the methods is generated at all of course; methods are jitted on demand; that's why its called jitting.)
To demonstrate that generic types are not collected, instead create a generic type
class C<T> { public static readonly T Big = new T[10000]; }
C<object>
and C<string>
share any code generated for the methods, but each one gets its own static fields, and those fields will live forever. The more types you construct, the more memory will be filled up with those big arrays.
And now you know why those types cannot be collected; we have no way of knowing if someone is going to try to access a member of one of those arrays at any time in the future. Since we don't know when the last array access is going to be, they have to live forever, and therefore the type that contains it has to live forever too.
To answer your second question: Is there a way to make dynamically-emitted assemblies that are collected?
Yes. The documentation is here:
http://msdn.microsoft.com/en-us/library/dd554932.aspx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With