Since the first version .NET BCL gave developer a possibility to generate code in runtime via using types in System.Reflection.Emit (SRE) namespace. This feature was highly claimed in a various scopes, most well-known are:
- Dynamic generation of proxy classes (applied in many AOP frameworks)
- Creation of specialized implementations based on runtime data (for example mappers in ORMs)
- Compilation and execution of different script languages
However all this magnificence has one “tiny” limitation, result assembly cannot be unloaded from the app domain. It stuck in the domain till the end of times (or the domain) thus becoming the source of memory leaks.
NET 2.0 introduces concept of dynamic methods that can be generated in runtime and referred by delegate. In contrast to dynamic assemblies dynamic methods can be collected by GC. But this solution was far for perfect: dynamic methods as follows from the name can define only methods (just code) and not types.
.NET 4.0 makes one step forward to new wonderful world, so without further delays let me introduce: Collectible Assemblies!
All details about their limitations and lifetime features are well described in MSDN article. So let’s turn to practice.
Declare a simple interface
public interface IPrinter
{
void Print(string text);
}
Create a method that will generate the implementation with given AssemblyBuilderAccess
private static Type CreatePrinterType(AssemblyBuilderAccess assemblyBuilderAccess)
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("transient_assembly"),
assemblyBuilderAccess
);
var module = assembly.DefineDynamicModule("transient");
var type = module.DefineType("ConsolePrinter");
type.AddInterfaceImplementation(typeof(IPrinter));
type.DefineDefaultConstructor(MethodAttributes.Public);
var method = type.DefineMethod(
"Print",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final,
typeof (void),
new[] {typeof (string)}
);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new [] {typeof(string)}));
il.Emit(OpCodes.Ret);
return type.CreateType();
}
The only remaining thing we need for tests: little helper method to list all assemblies in domain
private static void PrintAssemblies()
{
Console.WriteLine("===");
Console.WriteLine(string.Join(" ", AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name)));
}
Everything is ready, let's roll (all code should be compiled in Release and executed without debugger). Use good-old AssemblyBuilderAccess.Run
static void Main(string[] args)
{
PrintAssemblies();
var t = CreatePrinterType(AssemblyBuilderAccess.Run);
PrintAssemblies();
var instance = (IPrinter)Activator.CreateInstance(t);
instance.Print("Hi from SRE!");
GC.Collect();
PrintAssemblies();
}
/*
===
mscorlib SRE System.Core System
===
mscorlib SRE System.Core System transient_assembly
Hi from SRE!
===
mscorlib SRE System.Core System transient_assembly
*/
Result is expected, transient assembly survived during garbage collection. A little twist… switch Run to RunAndCollect
static void Main(string[] args)
{
PrintAssemblies();
var t = CreatePrinterType(AssemblyBuilderAccess.RunAndCollect);
PrintAssemblies();
var instance = (IPrinter)Activator.CreateInstance(t);
instance.Print("Hi from SRE!");
GC.Collect();
PrintAssemblies();
}
/*
===
mscorlib SRE System.Core System
===
mscorlib SRE System.Core System transient_assembly
Hi from SRE!
===
mscorlib SRE System.Core System
*/
Brilliant, isn't it? As soon as all references removed, transient assembly becomes eligible for garbage collection.
Note: MSDN article has a mistake in it:Lifetime of Collectible Assemblies
The lifetime of a collectible dynamic assembly is controlled by the existence of references to the types it contains, and the objects that are created from those types. The common language runtime does not unload an assembly as long as one or more of the following exist (T is any type that is defined in the assembly): An instance of an array of T, or an instance of a generic collection that has T as one of its type arguments, even if that array or collection is empty.
The last sentence should be: An instance of an array of T, or an instance of any generic type (i.e. collection) that has T as one of its type arguments, even if that array or collection is empty.
class A<T> { }
static void Main(string[] args)
{
PrintAssemblies();
var t = CreatePrinterType(AssemblyBuilderAccess.RunAndCollect);
var at = typeof (A<>).MakeGenericType(t);
PrintAssemblies();
GC.Collect();
PrintAssemblies();
Console.WriteLine(at);
}
/*
===
mscorlib SRE System.Core System
===
mscorlib SRE System.Core System transient_assembly
===
mscorlib SRE System.Core System transient_assembly
SRE.Program+A`1[ConsolePrinter]
*/
Комментариев нет:
Отправить комментарий