Показаны сообщения с ярлыком VS 2010. Показать все сообщения
Показаны сообщения с ярлыком VS 2010. Показать все сообщения

воскресенье, 7 марта 2010 г.

.NET 4.0 : Collectible assemblies

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]
*/

среда, 17 февраля 2010 г.

Co/Contravariance for .NET 2.0+

CLR supports generic variance for generic interfaces and delegates since version 2.0.

ECMA 335:

The CLI supports covariance and contravariance of generic parameters, but only in the signatures of interfaces and delegate classes. Suppose we have a generic interface, which is covariant in its one generic parameter; e.g., IA`1<+T>. Then all instantiations satisfy IA`1<GenArgB> := IA`1<GenArgA>, so long as GenArgB := GenArgA using the notion from assignment compatibility. So, for example, an instance of type IA`1<string> can be assigned to a local of type type IA`1<object>.

Generic contravariance operates in the opposite sense: supposing that we have a contravariant interface IB`1<-T>, then IB`1<GenArgB> := IB`1<GenArgA>, so long as GenArgA := GenArgB.

in spite of this, co/contravariance features  were only partially exposed for the users of mainstream languages. C# 2.0 add support only for covariance and contravariance in delegates.  C# 4.0 makes one step further: both interfaces and delegates are supported and now it is possible to annotate generic parameter to be either covariant (out) or contravariant (in) and compiler will verify that all usages of this parameter are correct corresponding to the annotation.

For example:

public interface IMyComparer<in T>
{
// contravariant generic parameter
int Compare(T a, T b);
}

public interface IFactory<out T>
{
// covariant generic parameter
T Create();
}

public class MyComparer<T> : IMyComparer<T>
{
public int Compare(T a, T b)
{
return 0;
}
}

public class Factory<T> : IFactory<T>
{
public T Create()
{
return default(T);
}
}

// usage

IMyComparer<string> s = new MyComparer<object>();
IFactory<object> p = new Factory<string>();

Very nice feature! And it doesn’t require .NET 4.0 for execution, runtime already supports variance and C# compiler makes all remaining job.

Note:, all standard interfaces (IEnumerable<T>, IEnumerator<T>, IQueryable<T>, IGrouping<TK, TV>, IEqualityComparer<T>, IComparer<T>, IComparable<T> ) and delegates (Action<>, Func<T>, Predicate<T>, Comparison<T>, Converter<TIn, TOut>) shall not be accessible, since annotated versions are located in 4.0 assemblies.

Other compiler features that are accessible from VS 2010 for .NET 2.0+

  • optional parameters
  • named parameters
class Program
{
static void Main(string[] args)
{
Run(b : "value", a : 10);
}

public static void Run(int a, string b)
{
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}
 
GeekySpeaky: Submit Your Site!