c# - recolector - ¿Se recolecta basura de tipos genéricos/tipos genéricos?
recolector de basura inteligente (1)
Para responder a tu primera pregunta:
Las construcciones genéricas de tipos no se recopilan.
Sin embargo, si construye C<string>
y C<object>
, el CLR realmente genera el código para los métodos solo una vez; ya que se garantiza que la referencia a la cadena y la referencia al objeto sea del mismo tamaño, puede hacerlo de forma segura. Es bastante inteligente. Sin embargo, si construye C<int>
y C<double>
, el código de los métodos se genera dos veces, una para cada construcción. (Suponiendo que el código para los métodos se genera en absoluto, por supuesto, los métodos se jit a pedido, es por eso que se llama jitting).
Para demostrar que no se recopilan los tipos genéricos, cree un tipo genérico
class C<T> { public static readonly T Big = new T[10000]; }
C<object>
y C<string>
comparten cualquier código generado para los métodos, pero cada uno obtiene sus propios campos estáticos, y esos campos vivirán para siempre. Cuantos más tipos construyas, más memoria se llenará con esas grandes matrices.
Y ahora sabes por qué esos tipos no se pueden recolectar; no tenemos forma de saber si alguien intentará acceder a un miembro de uno de esos conjuntos en cualquier momento en el futuro. Como no sabemos cuándo va a ser el último acceso a la matriz, tienen que vivir para siempre y, por lo tanto, el tipo que la contiene debe vivir para siempre.
Para responder a su segunda pregunta: ¿Hay alguna forma de realizar montajes emitidos dinámicamente que se recopilen?
Sí. La documentación está aquí:
Es bien sabido en .NET que los tipos no son basura, lo que significa que si juegas con f.ex. Reflection.Emit, tienes que tener cuidado de descargar AppDomains y demás ... Al menos así es como solía entender cómo funcionan las cosas.
Eso me hizo preguntarme si los tipos genéricos son basura recolectada, para ser más precisos: genéricos creados con MakeGenericType
, digamos ... por ejemplo basados en la entrada del usuario. :-)
Así que construí el siguiente caso de prueba:
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();
}
}
Como puede ver, los tipos genéricos se definen como ''posiblemente recursivo'', con una clase ''cola'' que marca el final de la recursión. Y para garantizar que GC.TotalMemoryUsage
no esté haciendo trampa, también abrí el Administrador de tareas.
Hasta aquí todo bien. Lo siguiente que hice fue disparar a esta bestia y mientras esperaba un "Sin memoria" ... noté que, a diferencia de mis expectativas, no consumía más memoria con el tiempo. De hecho, muestra una ligera caída en el consumo de memoria a tiempo.
¿Puede alguien por favor explicar esto? ¿Los tipos genéricos son realmente recopilados por el GC? Y si es así ... ¿hay también Reflection.Emit casos que son basura recolectada?