Wednesday, December 19, 2007

Vom Namen zur Instanz mit DynamicMethod

Ein Kunde legt beim Logging unterschiedliche LogMessages (unterschiedliche Klassen) in einer Datenbank ab. Dabei werden der Typ der Message (der LogMessage Klassenname), die Message selber sowie weitere Felder in der Tabelle gespeichert.
Nun sollten diese Einträge gelesen und die ursprünglichen Objekte wieder hergestellt werden.

Für die erste Lösung wurde der offensichtliche Weg mit Reflection gewählt:

string s = "Namespace.Name.FromDB";
Assembly a = Assembly.GetAssembly(typeof(ThisClass));
Type t = a.GetType(s);
BaseType b = (BaseType)Activator.CreateInstance(t);

Da dies nicht sehr performant ist, wurde der Code wie folgt verbessert:

string s = "Namespace.Name.FromDB";
Assembly a = Assembly.GetAssembly(typeof(ThisClass));
Type t = a.GetType(s);
ConstructorInfo i = t.GetConstructor(Type.EmptyTypes);
BaseType b = (BaseType)i.Invoke(null);

Wobei der Klassenname und die zugehörige ConstructorInfo in einem statischen Dictionaire abgelegt werden, damit diese nicht bei jedem Aufruf neu evaluiert werden müssen. So haben wir vom zweiten bis x-ten Aufruf den gewünschten Performancegewinn. Der Code dazu sieht folgendermassen aus.

ConstructorInfo c = null;
if (_cinfo.TryGetValue(s, out c))
{
c = t.GetConstructor(Type.EmptyTypes);
lock(_cinfo)
{
if (_cinfo.ContainsKey(s))
{
_cinfo.Add(s, c);
}
}
}

Noch eleganter und viel performanter geht's mit Dynamic Methods. Der von der dynamischen Methode zurückgegebene Delegate wird wie zuvor die ConstructorInfo in einem Dictionaire abgelegt und für die Folgeaufrufe wieder verwendet:

public delegate BaseType CtorDelegate();

DynamicMethod dm = new DynamicMethod
("MyCtor", typeof(BaseType), Type.EmptyTypes, typeof(BaseType).Module);
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, t.GetConstructor(Type.EmptyTypes));
ilgen.Emit(OpCodes.Ret);
CtorDelegate d = (CtorDelegate)dm.CreateDelegate(typeof(CtorDelegate));

Der Aufruf ist dann genau so kurz (d wird zuvor aus dem Dictionaire gelesen) und unheimlich schnell:

t message = (t)d();

Nun muss nur noch alles in einer Factory Klasse schön verpackt werden und fertig. Mehr zu diesem Thema inklusive Performance Messungen findet man wie immer bei Google ;-)