oder: Erzeugung dynamischer Typen als Ersatz für GetValue
/SetValue
mittels Reflection für häufige, performanzkritische Feld-/Propertyzugriffe.
Beschreibung:
Relektion ist toll 😄. Mit Reflektion kann man z.B. die Felder und Properties von Typen auslesen und belegen. Ein Beispiel ist z.B. ein O/R-Mapper, der die Werte einer Datenbankzeile ausliest und entsprechend des Spaltennamens Felder/Properties eines Typs mit den jeweiligen Werten belegt.
Dabei werden jedoch oft mehrere hundert oder tausend Objekte erzeugt. Und Reflektion ist langsam. Deshalb bietet es sich an, dynamisch einen Typen zu erzeugen, der die entsprechenden Felder/Properties nicht mehr via Reflection-API liest/setzt, sondern dies quasi mit einem direkten Verweis macht. Die Grundidee ist aus CodeProject: Fast Dynamic Property Access, die hier gezeigte Klasse ist aber noch ergänzt um Typkonvertierung für den Anwendungsfall Einfacher Tabellen Mapper . Zusätzlich werden Felder unterstützt. Da Microsoft so genial war und den Begriff "(Klassen-)Attribute" überladen hat, heißt meine Klasse "DynamicFieldAccessor" (Properties sind auch nur abstrahierte Felder...).
Features:*Lesen und Schreiben von Feldern und Properties *auch protected Felder/Properties können gelesen/geschrieben werden *Unterstützung von Enums und Nullable-Types *(normales) Lesen und Schreiben bis etwa 50x so schnell als Reflection *unterstützt Typkonvertierung beim Schreiben (optional) *beim Schreiben mit Typkonvertierung sogar schneller als direkter Zugriff
Beispiel:
public class TestClass<T>
{
public T Value { get; set; }
}
TestClass<int> testClass = new TestClass<int>();
// direkter Zugriff
int value = testClass.Value;
testClass.Value = value;
// Zugriff mit Reflection-API
PropertyInfo propertyInfo = typeof(TestClass<int>).GetProperty("Value");
value = (int)propertyInfo.GetValue(testClass);
propertyInfo.SetValue(testClass,value);
// Zugriff mit DynamicFieldAccessor
propertyInfo = typeof(TestClass<int>).GetProperty("Value");
IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.Create(propertyInfo,false);
value = fieldAccessor.Get(testClass);
fieldAccessor.Set(testClass,value);
DynamicFieldAccessor.Create(...)
erzeugt dabei je nach Property-/Feld dynamisch einen neuen Typ (hier eine Klasse), die den Property/Feldzugriff quasi direkt durchführt. Im Folgenden sieht man für verschiedene Anwendungsfälle den C#-Code, der dem erzeugten IL-Code/dynamischen Typen entspricht: *IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.Create(typeof(TestClass<
string
>).GetProperty ("Value"),
false
)
;
public class TestClass_SystemString_Value : IDynamicFieldAccessor
{
public virtual object Get(object target)
{
return ((TestClass<string>) target).Value;
}
public virtual void Set(object target, object value)
{
((TestClass<string>) target).Value = (string) value;
}
}
*IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.Create(typeof(TestClass<
int
>).GetProperty ("Value"),
true
)
;
public class TestClass_Int_Value_TypeChange : IDynamicFieldAccessor
{
public object Get(object target)
{
return ((TestClass<int>) target).Value;
}
public void Set(object target, object value)
{
if ((value is int) || typeof(int).IsAssignableFrom(value.GetType()))
((TestClass<int>) target).Category = (int) value;
else
((TestClass<int>) target).Category = Convert.ToInt32(value);
}
}
*IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.Create(typeof(TestClass<
TestEnum
>).GetProperty ("Value"),
true
)
;
public class TestClass_TestEnum_Value : IFieldAccessor
{
public virtual object Get(object target)
{
return ((TestClass<TestEnum>) target).Value;
}
public virtual void Set(object target, object value)
{
if (value is int)
((TestClass<TestEnum>) target).Value = (TestEnum) value;
else
((TestClass<TestEnum>) target).Value = (TestEnum) Enum.Parse(typeof(TestEnum), value.ToString());
}
}
*IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.Create(typeof(TestClass<
int?
>).GetProperty ("Value"),
true
)
;
public class TestClass_SystemNullableSystemInt_Value : IDynamicFieldAccessor
{
public virtual object Get(object target)
{
return ((TestClass<int?>) target).Value;
}
public virtual void Set(object target, object value)
{
if ((value is int?) || typeof(int?).IsAssignableFrom(value.GetType()))
((TestClass<int?>) target).Value = (int?) value;
else
((TestClass<int?>) target).Value = (int?) Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(int?)));
}
}
Performanzvergleich:
Teste System.Int32 mit 500000 Wiederholungen
Getter:
direkter Zugriff 19
DynamicFieldAccessor 28 #
FastDynamicPropertyAccessor 47
Reflection/PropertyInfo.GetValue 1569
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 70
DynamicFieldAccessor 31 #
FastDynamicPropertyAccessor 43
Reflection/PropertyInfo.SetValue 1971
Setter (mit Typkonvertierungprüfung,ohne Typänderung):
direkter Zugriff 55
DynamicFieldAccessor 34 #
FastDynamicPropertyAccessor 70
Reflection/PropertyInfo.SetValue 2006
Setter (mit Typkonvertierungprüfung,mit Typänderung):
direkter Zugriff 771
DynamicFieldAccessor 394 #
FastDynamicPropertyAccessor 763
Reflection/PropertyInfo.SetValue 2947
Teste System.String mit 500000 Wiederholungen
Getter:
direkter Zugriff 17
DynamicFieldAccessor 31 #
FastDynamicPropertyAccessor 44
Reflection/PropertyInfo.GetValue 1439
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 35
DynamicFieldAccessor 41 #
FastDynamicPropertyAccessor 47
Reflection/PropertyInfo.SetValue 1835
Setter (mit Typkonvertierungprüfung,ohne Typänderung):
direkter Zugriff 41
DynamicFieldAccessor 24 #
FastDynamicPropertyAccessor 62
Reflection/PropertyInfo.SetValue 1893
Setter (mit Typkonvertierungprüfung,ohne Typänderung):
direkter Zugriff 51
DynamicFieldAccessor 28 #
FastDynamicPropertyAccessor 64
Reflection/PropertyInfo.SetValue 1877
Teste System.Nullable`1[[System.Int32]] mit 500000 Wiederholungen
Getter:
direkter Zugriff 23
DynamicFieldAccessor 474 #
FastDynamicPropertyAccessor 587
Reflection/PropertyInfo.GetValue 2323
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 286
DynamicFieldAccessor 730 #
FastDynamicPropertyAccessor 781
Reflection/PropertyInfo.SetValue 3067
Setter (mit Typkonvertierungprüfung,mit Typänderung):
direkter Zugriff 463
DynamicFieldAccessor 258 #
FastDynamicPropertyAccessor 658
Reflection/PropertyInfo.SetValue 2880
Setter (mit Typkonvertierungprüfung,mit Typänderung):
direkter Zugriff 2907
DynamicFieldAccessor 2154 #
FastDynamicPropertyAccessor 2880
Reflection/PropertyInfo.SetValue 6071
Code:
Und nun endlich der Code:
/// <summary>
/// Ermöglicht den schnellen Zugriff (ohne Reflection) auf Felder/Properties
/// </summary>
public interface IDynamicFieldAccessor
{
/// <summary>
/// Ersetzt das Property/Feld, das durch diese Instanz repräsentiert wird, am übergeben Zielobjekt durch den übergeben Wert
/// </summary>
/// <param name="target">das Zielobjekt, dem das Feld/Property angehört</param>
/// <param name="value">der Wert, der gesetzt werden soll</param>
void Set(object target,object value);
/// <summary>
/// Liest das Property/Feld, das durch diese Instanz repräsentiert wird, vom übergeben Zielobjekt aus
/// </summary>
/// <param name="target">das Zielobjekt, dem das Feld/Property angehört</param>
/// <returns>den Wert des Properties/Feldes, das durch diese Instantz repräsentiert wird</returns>
object Get(object target);
}
/*using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;*/
/// <summary>
/// Stellt Methoden bereit, um Objekte zu erzeugen, die den schnellen Zugriff (ohne Reflection) auf Felder/Properties ermöglichen
/// </summary>
public abstract class DynamicFieldAccessor
{
// Cache zum Speichern der bereits erzeugten "dynamischen Feldzugreifer"
private static Dictionary<bool,Dictionary<MemberInfo,IDynamicFieldAccessor>> cache = new Dictionary<bool,Dictionary<MemberInfo,IDynamicFieldAccessor>>();
// MethodInfos, die oft benutzt werden
private static readonly MethodInfo typeGetTypeFromHandleMethodInfo = typeof(Type).GetMethod("GetTypeFromHandle");
private static readonly MethodInfo objectGetTypeMethodInfo = typeof(object).GetMethod("GetType");
private static readonly MethodInfo typeIsAssignableFromMethodInfo = typeof(Type).GetMethod("IsAssignableFrom");
private static readonly MethodInfo convertChangeTypeMethodInfo = typeof(Convert).GetMethod("ChangeType",new Type[]{typeof(object),typeof(Type)});
private static readonly MethodInfo objectToStringMethodInfo = typeof(object).GetMethod("ToString");
private static readonly MethodInfo enumParseMethodInfo = typeof(Enum).GetMethod("Parse",new Type[]{typeof(Type),typeof(string)});
private static readonly MethodInfo nullableGetUnderlyingTypeMethodInfo = typeof(Nullable).GetMethod("GetUnderlyingType");
/// <summary>
/// Konstruktor
/// </summary>
static DynamicFieldAccessor()
{
// Felder im Cache anlegen
DynamicFieldAccessor.cache.Add(true,new Dictionary<MemberInfo,IDynamicFieldAccessor>());
DynamicFieldAccessor.cache.Add(false,new Dictionary<MemberInfo,IDynamicFieldAccessor>());
}
/// <summary>
/// Erzeugt eine neue IDynamicFieldAccessor-Instanz
/// </summary>
/// <param name="memberInfo">das Feld/Property, auf das zugegriffen werden soll</param>
/// <param name="enableSetterTypeChange">true, falls eine (eventuelle) Typkonvertierung beim Setzen von Werten vorgenommen werden soll</param>
/// <returns>eine neue IDynamicFieldAccessor-Instanz zum Zugriff auf das übergebene Feld/Property</returns>
public static IDynamicFieldAccessor Create(MemberInfo memberInfo,bool enableSetterTypeChange)
{
if (!(memberInfo is PropertyInfo || memberInfo is FieldInfo))
throw new ArgumentException(memberInfo+" ist kein Feld oder Property.");
else
// Instanz aus dem Cache zurückgeben
if (DynamicFieldAccessor.cache[enableSetterTypeChange].ContainsKey(memberInfo))
return DynamicFieldAccessor.cache[enableSetterTypeChange][memberInfo];
// neue Instanz erzeugen und im Cache speichern
else
{
IDynamicFieldAccessor fieldAccessor = DynamicFieldAccessor.create(memberInfo,enableSetterTypeChange);
DynamicFieldAccessor.cache[enableSetterTypeChange].Add(memberInfo,fieldAccessor);
return fieldAccessor;
}
}
/// <summary>
/// Erzeugt eine neue IDynamicFieldAccessor-Instanz
/// </summary>
/// <param name="memberInfo">das Feld/Property, auf das zugegriffen werden soll</param>
/// <param name="enableSetterTypeChange">true, falls eine (eventuelle) Typkonvertierung beim Setzen von Werten vorgenommen werden soll</param>
/// <returns>eine neue IDynamicFieldAccessor-Instanz zum Zugriff auf das übergebene Feld/Property</returns>
private static IDynamicFieldAccessor create(MemberInfo memberInfo,bool enableSetterTypeChange)
{
// Pipeline AssemblyBuilder->ModuleBuilder->TypeBuilder
#if DEBUG
string name = "[DynamicFieldAccessor]"+memberInfo.ReflectedType.Name+" "+Regex.Replace(memberInfo.ToString().Replace(" ","_"),"[^a-z^A-Z^_]","");
if (enableSetterTypeChange)
name += "(TypeChange)";
AssemblyName assemblyName = new AssemblyName(name);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly (assemblyName,AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(name,name+".dll");
#else
string name = Guid.NewGuid().ToString();
AssemblyName assemblyName = new AssemblyName(name);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly (assemblyName,AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
#endif
// einen neuen TypeBuilder erzeugen. Falls das Feld/Property nicht public ist, muss vom reflektierten Typ geerbt werden, damit Zugriff erhalten werden kann
TypeBuilder typeBuilder = (DynamicFieldAccessor.mustInherit(memberInfo)) ? moduleBuilder.DefineType(name,TypeAttributes.Public,memberInfo.ReflectedType) : moduleBuilder.DefineType(name,TypeAttributes.Public);
// der neue Typ implementiert die Schnittstelle IDynamicFieldAccessor; die hat eine Methode "Get" und eine Methode "Set", welche implementiert werden müssen
typeBuilder.AddInterfaceImplementation(typeof(IDynamicFieldAccessor));
DynamicFieldAccessor.implementGetMethod(typeBuilder,memberInfo);
DynamicFieldAccessor.implementSetMethod (typeBuilder,memberInfo,enableSetterTypeChange);
// den Typen erzeugen
Type resultType = typeBuilder.CreateType();
#if DEBUG
// die Assembly auf die Platte speichern, damit man den produzierten IL-/C#-Code angucken kann
assemblyBuilder.Save(name+".dll");
#endif
// neue Instanz erzeugen -> dabei wird Konstruktor nicht aufgerufen (braucht nicht; hätte bei Spezialisierungen, um an protected Member zu kommen, auch Nebeneffekte)
IDynamicFieldAccessor result = (IDynamicFieldAccessor)FormatterServices.GetUninitializedObject(resultType);
return result;
}
/// <summary>
/// Prüft, ob ein neuer Typ vom Typ, der das Property/Feld enthält, ableiten muss, um an das Property/Feld zu kommen, da dies nicht public ist
/// </summary>
/// <param name="memberInfo">der Member, der untersucht werden soll</param>
/// <returns>true, falls abgeleitet werden muss</returns>
private static bool mustInherit(MemberInfo memberInfo)
{
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
FieldInfo fieldInfo = memberInfo as FieldInfo;
// Es muss abgeleitet werden, wenn Getter oder Setter des Properties oder das Feld nicht public ist.
// Getter/Setter/Feld müssen natürlich auch protected sein
// wenn es ein Property ist
if (propertyInfo!=null)
{
MethodInfo getMethod = propertyInfo.GetGetMethod(true);
if (getMethod!=null)
{
if ((getMethod.Attributes & MethodAttributes.Family)!=MethodAttributes.Family)
throw new ArgumentException("'"+memberInfo+"' muss public oder protected sein.");
if ((getMethod.Attributes & MethodAttributes.Public)!=MethodAttributes.Public)
return true;
}
MethodInfo setMethod = propertyInfo.GetSetMethod(true);
if (setMethod!=null)
{
if ((setMethod.Attributes & MethodAttributes.Family)!=MethodAttributes.Family)
throw new ArgumentException("'"+memberInfo+"' muss public oder protected sein.");
if ((setMethod.Attributes & MethodAttributes.Public)!=MethodAttributes.Public)
return true;
}
}
// wenn es ein Feld ist
else
{
if ((fieldInfo.Attributes & FieldAttributes.Family)!=FieldAttributes.Family)
throw new ArgumentException("'"+memberInfo+"' muss public oder protected sein.");
if ((fieldInfo.Attributes & FieldAttributes.Public)!=FieldAttributes.Public)
return true;
}
return false;
}
/// <summary>
/// Implementiert die "object Get(object target)"-Methode von IDynamicFieldAccessor
/// </summary>
/// <param name="typeBuilder">der zu verwendende TypeBuilder</param>
/// <param name="memberInfo">das Feld/Property, auf das ausgelesen werden soll</param>
private static void implementGetMethod(TypeBuilder typeBuilder,MemberInfo memberInfo)
{
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
FieldInfo fieldInfo = memberInfo as FieldInfo;
// neue Methode erstellen und die Methode aus der Schnittstellendeklaration damit überschreiben
MethodInfo methodInfo = typeof(IDynamicFieldAccessor).GetMethod("Get");
MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name,(MethodAttributes) (methodInfo.Attributes-MethodAttributes.Abstract),methodInfo.ReturnType,new Type[] { typeof(object) });
typeBuilder.DefineMethodOverride(methodBuilder,methodInfo);
// IL-Generator erzeugen
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
if (propertyInfo!=null && propertyInfo.GetGetMethod(true)==null)
{
// wenn es ein Property ist und kein Getter vorhanden ist, Exception werfen
ilGenerator.Emit(OpCodes.Ldstr,"Property '"+memberInfo+"' hat keinen Getter.");
ilGenerator.Emit(OpCodes.Newobj,typeof(NotSupportedException).GetConstructor(new Type[] { typeof(string) }));
ilGenerator.Emit(OpCodes.Throw);
}
// Property-/Feldzugriff implementieren
else
{
// lokale Variable
ilGenerator.DeclareLocal(typeof(object));
// übergebenes Objekt laden und zum casten
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Castclass,memberInfo.ReflectedType);
if (propertyInfo!=null)
// Property auslesen
ilGenerator.Emit(OpCodes.Callvirt,propertyInfo.GetGetMethod(true));
else
// Feld auslesen
ilGenerator.Emit(OpCodes.Ldfld,fieldInfo);
// den gelesenen Property-/Feldwert boxen
Type memberType = (propertyInfo!=null) ? propertyInfo.PropertyType : fieldInfo.FieldType;
if (memberType.IsValueType)
ilGenerator.Emit(OpCodes.Box,memberType);
// gelesenen Property-/Feldwert zurückgeben
ilGenerator.Emit(OpCodes.Ret);
}
}
/// <summary>
/// Implementiert die "void Set(object target,object value);"-Methode von IDynamicFieldAccessor
/// </summary>
/// <param name="typeBuilder">der zu verwendende TypeBuilder</param>
/// <param name="memberInfo">das Feld/Property, auf das gesetzt werden soll</param>
/// <param name="changeType">true, falls eine (eventuelle) Typkonvertierung beim Setzen von Werten vorgenommen werden soll</param>
private static void implementSetMethod(TypeBuilder typeBuilder,MemberInfo memberInfo,bool changeType)
{
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
FieldInfo fieldInfo = memberInfo as FieldInfo;
Type memberType = (propertyInfo!=null) ? propertyInfo.PropertyType : fieldInfo.FieldType; // Zieltyp
// neue Methode erstellen und die Methode aus der Schnittstellendeklaration damit überschreiben
MethodInfo methodInfo = typeof(IDynamicFieldAccessor).GetMethod("Set");
MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name,(MethodAttributes) (methodInfo.Attributes-MethodAttributes.Abstract),methodInfo.ReturnType,new Type[] { typeof(object),typeof(object) });
typeBuilder.DefineMethodOverride(methodBuilder,methodInfo);
// IL-Generator erzeugen
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
// Sprungziele definieren
Label labelSetConverted = ilGenerator.DefineLabel();
Label labelSetDirect = ilGenerator.DefineLabel();
if (propertyInfo!=null && propertyInfo.GetSetMethod(true)==null)
{
// wenn es ein Property ist und kein Setter vorhanden ist, Exception werfen
ilGenerator.Emit(OpCodes.Ldstr,"Property '"+memberInfo+"' hat keinen Setter.");
ilGenerator.Emit(OpCodes.Newobj,typeof(NotSupportedException).GetConstructor(new Type[]{typeof(string)}));
ilGenerator.Emit(OpCodes.Throw);
}
// Property-/Feldzugriff implementieren
else
{
// wenn gewünscht, die TypPRÜFUNG implementieren
if (changeType)
{
// lokale Variable
ilGenerator.DeclareLocal(typeof(bool));
// den zu setzende Wert laden; {value is MemberType}-Vergleich machen; falls erfolgreich, zum direkt ohne Konvertieren setzen gehen
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Isinst,(!memberType.IsEnum) ? memberType : typeof(int));
ilGenerator.Emit(OpCodes.Brtrue_S,labelSetDirect);
// is-Vergleich ist nicht erfolgreich: bei Enumtypen zum Konvertieren gehen...
if (memberType.IsEnum)
ilGenerator.Emit(OpCodes.Br_S,labelSetConverted);
// ... ansonsten noch Testen, ob der Wert nicht doch zuweisbar ist
else
{
// {if (typeof(MemberType).IsAssignableFrom(value.GetType()))}
ilGenerator.Emit(OpCodes.Ldtoken,memberType);
ilGenerator.Emit(OpCodes.Call,typeGetTypeFromHandleMethodInfo);
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Callvirt,objectGetTypeMethodInfo);
ilGenerator.Emit(OpCodes.Callvirt,typeIsAssignableFromMethodInfo);
// wenn der Test nicht erfolgreich war, zum Konvertieren gehen...
ilGenerator.Emit(OpCodes.Brfalse_S,labelSetConverted);
// ... ansonsten kommt jetzt folgend die Zuweisung ohne Konvertieren
}
ilGenerator.MarkLabel(labelSetDirect);
}
// Zielobjekt laden; dieses casten; Wert laden; unboxen
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Castclass,memberInfo.ReflectedType);
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Unbox_Any,memberType);
if (propertyInfo!=null)
// Property setzen
ilGenerator.Emit(OpCodes.Callvirt,propertyInfo.GetSetMethod(true));
else
// Feld setzen
ilGenerator.Emit(OpCodes.Stfld,fieldInfo);
// fertig
ilGenerator.Emit(OpCodes.Ret);
// falls gewünscht, hier der Teil zum Konvertieren
if (changeType)
{
ilGenerator.MarkLabel(labelSetConverted);
// Zielobjekt laden und casten
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Castclass,memberInfo.ReflectedType);
if (!memberType.IsEnum)
{
// value laden
ilGenerator.Emit(OpCodes.Ldarg_2);
MethodInfo convertToMethodInfo = DynamicFieldAccessor.getConvertToMethodInfo(memberType);
if (convertToMethodInfo!=null)
// {Convert.ToXY(value)}
ilGenerator.Emit(OpCodes.Call,convertToMethodInfo);
else
{
// {Convert.ChangeType(value,typeof(MemberType))}
// Zieltyp laden
ilGenerator.Emit(OpCodes.Ldtoken,memberType);
ilGenerator.Emit(OpCodes.Call,typeGetTypeFromHandleMethodInfo);
if (memberType.IsGenericType && memberType.GetGenericTypeDefinition()==typeof(Nullable<>))
// {Convert.ChangeType(value,Nullable.GetUnderlyingType(typeof(MemberType)))}
// GetUnderlyingType aufrufen
ilGenerator.Emit(OpCodes.Call,nullableGetUnderlyingTypeMethodInfo);
// .ToXY/.ChangeType aufrufen
ilGenerator.Emit(OpCodes.Call,convertChangeTypeMethodInfo);
ilGenerator.Emit(OpCodes.Unbox_Any,memberType);
}
}
// Enums
else
{
// Enum-Zieltyp laden
ilGenerator.Emit(OpCodes.Ldtoken,memberType);
ilGenerator.Emit(OpCodes.Call,typeGetTypeFromHandleMethodInfo);
// value laden und zu string machen
ilGenerator.Emit(OpCodes.Ldarg_2);
ilGenerator.Emit(OpCodes.Callvirt,objectToStringMethodInfo);
// {Enum.Parse(typeof(MemberType),value)}
ilGenerator.Emit(OpCodes.Call,enumParseMethodInfo);
ilGenerator.Emit(OpCodes.Unbox_Any,memberType);
}
if (propertyInfo!=null)
// Property setzen
ilGenerator.Emit(OpCodes.Callvirt,propertyInfo.GetSetMethod(true));
else
// Feld setzen
ilGenerator.Emit(OpCodes.Stfld,fieldInfo);
// fertig
ilGenerator.Emit(OpCodes.Ret);
}
}
}
/// <summary>
/// Gibt das MethodInfo der zum übergebenen Typ gehörenden Convert.ToXY-Method zurück (null, falls keine passende gefunden)
/// </summary>
/// <param name="targetType">Typ, in den konvertiert werden soll</param>
/// <returns>das MethodInfo der zum übergebenen Typ gehörenden Convert.ToXY-Method zurück (null, falls keine passende gefunden)</returns>
private static MethodInfo getConvertToMethodInfo(Type targetType)
{
// Signatur der gesuchen Methode ermitteln
string methodName;
if (targetType==typeof(Boolean))
methodName = "ToBoolean";
else if (targetType==typeof(Byte))
methodName = "ToByte";
else if (targetType==typeof(Char))
methodName = "ToChar";
else if (targetType==typeof(DateTime))
methodName = "ToDateTime";
else if (targetType==typeof(Decimal))
methodName = "ToDecimal";
else if (targetType==typeof(Double))
methodName = "ToDouble";
else if (targetType==typeof(Int16))
methodName = "ToInt16";
else if (targetType==typeof(Int32))
methodName = "ToInt32";
else if (targetType==typeof(Int64))
methodName = "ToInt64";
else if (targetType==typeof(SByte))
methodName = "ToSByte";
else if (targetType==typeof(Single))
methodName = "ToSingle";
else if (targetType==typeof(String))
methodName = "ToString";
else if (targetType==typeof(UInt16))
methodName = "ToUInt16";
else if (targetType==typeof(UInt32))
methodName = "ToUInt32";
else if (targetType==typeof(UInt64))
methodName = "ToUInt64";
else
return null;
// Methode mit der entsprechenden Signatur suchen und zurückgeben
return typeof(Convert).GetMethod(methodName,new Type[]{typeof(object)});
}
}
Schlagwörter: DynamicFieldAccessor, Felder, Properties, Reflection, IL-Generator
letzte Änderung am Code: 27.03.2008
Blöde Frage: Warum benutzt du nicht einfach System.Reflection.Emit.DynamicMethod? So hab ich's bei meinen Cheatmeisen gemacht (AntMe), die dann in der Lage waren sich selbst zu heilen und die Käfer für tot zu erklären 🙂 :evil:
Aber Spaß beiseite, mit DynamicMethod geht das ganze viel einfacher, da du m.E. nach einfach an den bestehenden Typen zur Laufzeit quasi eine Methode dranhaengst. Der Zugriff erfolgt dann per Delegate, aber den kann man ja kapseln.
Und ein bisschen (konstruktive) Kritik hab ich auch noch übrig 🙂 Du rufst Properties immer ueber callvirt auf, das ist aber nur bei virtuellen Properties notwendig.
Hallo,
Blöde Frage: Warum benutzt du nicht einfach System.Reflection.Emit.DynamicMethod? Aber Spaß beiseite, mit DynamicMethod geht das ganze viel einfacher, da du m.E. nach einfach an den bestehenden Typen zur Laufzeit quasi eine Methode dranhaengst. Der Zugriff erfolgt dann per Delegate, aber den kann man ja kapseln.
Hm, am besten mache ich eine Liste:*Kannte ich nicht (woher kennst du dich damit aus?) *Naja, "einfacher" werde ich es nicht mehr haben. Die Klasse ist fertig und wird eingesetzt 😁
*Das "einfacher" würde ich auch erstmal nur auf die Programmierung beziehen. Denn von der Struktur her kann ich nicht erkennen, dass es einfacher, als die Klasse/das Interface ist (wäre eher noch zusätzlicher Aufwand) *Es geht hier primär um Performanz. Ich würde einfach mal in den Raum stellen, dass so ein Delegate-Zugriff langsamer ist. Wenn ich mal ne freie Minute habe, werd ich mir DynamicMethod aber mal genauer angucken und testen. *Summa summarum sehe ich als einzigen Vorteil, dass ich die Methode einfach in ein bestehendes Modul hänge und man so auch leichter Zugriff auf nichtöffentliches Zeugs hat (Was meine Lösung aber auch kann).
Und ein bisschen (konstruktive) Kritik hab ich auch noch übrig 🙂 Du rufst Properties immer ueber callvirt auf, das ist aber nur bei virtuellen Properties notwendig. Hm, ich hab "Reverse-Programming" betrieben. Der C#-Compiler hat das so ausgegeben. Performanzmäßig bringt ein Call aber auch nichts.
Gruß
dN!3L
Sehr interessant, werde das ganze gleich mal in meinen einfachen Tabellen Mapper integrieren. Damit dürfte ich Performanzmäßig noch einiges herausholen können 🙂.
Die DynamicMethod Variante habe ich mir auch schon angesehen.
Ich befürchte auch, dass diese Vorgehensweise langsamer ist.
Ich hab die Performance von DynamicMethod ehrlich gesagt noch nie getestet, kann mir aber nicht wirklich vorstellen, dass der Delegate-Zugriff soo viel langsamer ist als der Zugriff über Interface. Momentan kann ich's auch nicht überprüfen (Lüfter vom Laptop kaputt X()
Das ist ne tolle Sache, ich habe mir direkt mal die DynamicMethod Variante zusammengebastelt.
Aber leider lohnt sich das ganze nur, wenn ich sehr oft ein und das selbe Property schreibe oder lese. Ansonsten ist der Aufwand zu groß, den Code zu erzeugen.
Wie auch immer, trotzdem ne gute Sache!
Gruß
Hallo,
Das ist ne tolle Sache, ich habe mir direkt mal die DynamicMethod Variante zusammengebastelt. Könntest du mal bitte den Code posten (bzw. mit zukommen lassen)? Dann kann ich mal ein paar Performanztests machen (und muss nicht so viel vorbereiten...).
Aber leider lohnt sich das ganze nur, wenn ich sehr oft ein und das selbe Property schreibe oder lese. Ja, genau dafür ist es doch gedacht. 😁
Gruß
dN!3L
Hier die DynamicMethod Lösung:
public class DynamicAccessor
{
delegate object Getter(object target);
delegate void Setter(object target, object value);
Getter _getter;
Setter _setter;
public DynamicAccessor(PropertyInfo propertyInfo)
{
_getter = EmitGetter(propertyInfo);
if (propertyInfo.CanWrite)
{
_setter = EmitSetter(propertyInfo);
}
}
Getter EmitGetter(PropertyInfo propertyInfo)
{
DynamicMethod dynMethod = new DynamicMethod("Get",
typeof(object),
new Type[]{typeof(object)},
propertyInfo.DeclaringType);
ILGenerator il = dynMethod.GetILGenerator();
MethodInfo getterMethod = propertyInfo.GetGetMethod();
il.DeclareLocal(typeof(object));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
il.EmitCall(OpCodes.Callvirt, getterMethod, null);
OpCode box = propertyInfo.PropertyType.IsValueType ?
OpCodes.Box :
OpCodes.Castclass;
il.Emit(box, propertyInfo.PropertyType);
il.Emit(OpCodes.Ret);
return (Getter)dynMethod.CreateDelegate(typeof(Getter));
}
Setter EmitSetter(PropertyInfo propertyInfo)
{
DynamicMethod dynMethod = new DynamicMethod("Set",
null,
new Type[]{typeof(object),
typeof(object)},
propertyInfo.DeclaringType);
ILGenerator il = dynMethod.GetILGenerator();
MethodInfo setter = propertyInfo.GetSetMethod();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
il.Emit(OpCodes.Ldarg_1);
OpCode box = propertyInfo.PropertyType.IsValueType ?
OpCodes.Unbox_Any :
OpCodes.Castclass;
il.Emit(box, propertyInfo.PropertyType);
il.EmitCall(OpCodes.Callvirt, setter, null);
il.Emit(OpCodes.Ret);
return (Setter)dynMethod.CreateDelegate(typeof(Setter));
}
public void Set(object target, object value)
{
if (_setter == null)
throw new NotSupportedException("Property is read-only");
_setter(target, value);
}
public object Get(object target)
{
return _getter(target);
}
}
Ich suche jetzt nach einer Möglichkeit, die Zieltypen im IL Code zu verändern, dann könnte man das ganze dynamisch für verschiedene Properties verwenden. DynamicILInfo könnte dabei helfen.
Hat jemand damit schon Erfahrungen gemacht?
Hier die DynamicMethod Lösung:
Und hier die Performanzprüfungen:
Die erste DynamicMethod-Variante ist direkt das Getter-Delegate aufzurufen, die zweite ist die, die du gepostet hast (also quasi noch ein Proxy).
Man sieht, die DynamicMethod-Variante liegt nur ganz knapp hinter der DynamicType-Variante. Hätte ich nicht gedacht...
Teste System.Int32 mit 1000000 Wiederholungen
Getter:
direkter Zugriff 29
DynamicFieldAccessor 43
DynamicMethod Getter 46
DynamicMethodPropertyAccessor 45
FastDynamicPropertyAccessor 46
Reflection/PropertyInfo.GetValue 2058
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 27
DynamicFieldAccessor 43
DynamicMethod Setter 52
DynamicMethodPropertyAccessor 55
FastDynamicPropertyAccessor 56
Reflection/PropertyInfo.SetValue 2654
Teste System.String mit 1000000 Wiederholungen
Getter:
direkter Zugriff 17
DynamicFieldAccessor 35
DynamicMethod Getter 36
DynamicMethodPropertyAccessor 37
FastDynamicPropertyAccessor 39
Reflection/PropertyInfo.GetValue 1906
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 33
DynamicFieldAccessor 40
DynamicMethod Setter 42
DynamicMethodPropertyAccessor 46
FastDynamicPropertyAccessor 48
Reflection/PropertyInfo.SetValue 2443
Teste System.Nullable`1[[System.Int32]] mit 1000000 Wiederholungen
Getter:
direkter Zugriff 32
DynamicFieldAccessor 644
DynamicMethod Getter 641
DynamicMethodPropertyAccessor 630
FastDynamicPropertyAccessor 659
Reflection/PropertyInfo.GetValue 3157
Setter (ohne Typkovertierungsprüfung):
direkter Zugriff 324
DynamicFieldAccessor 945
DynamicMethod Setter 951
DynamicMethodPropertyAccessor 961
FastDynamicPropertyAccessor 988
Reflection/PropertyInfo.SetValue 4022
Ich suche jetzt nach einer Möglichkeit, die Zieltypen im IL Code zu verändern, dann könnte man das ganze dynamisch für verschiedene Properties verwenden. 🤔 Was willst du für Zieltypen verändern, und warum?
EDIT: (Folgebeitrag für diese Frage ist Properties dynamisch schneller schreiben als mit PropertyInfo.SetValue )
Gruß
dN!3L
Hallo allerseits,
auch wenn dieser Beitrag schon älter ist, möchte ich noch eine Verbesserung beisteuern.
Der Zugriff auf Structs wie System.Windows.Media.Media3D.Point3D klappt mit der oben beschriebenen Dynamic Method Lösung nicht. Ich habe mir dafür folgenden Unit Test geschrieben:
[Test]
public void CheckStructSecondParameter()
{
var Pos = new System.Windows.Media.Media3D.Point3D(10.0,20.0,30.0);
var expected = 20.0;
var daSubProp = new DynamicAccessor(Pos.GetType().GetProperty("Y"));
var result = daSubProp.Get(Pos);
Assert.AreEqual(expected, result);
}
Dieser Test schlägt fehl! Eigenartig ist jedoch, dass die Variable 'result' den Wert 10.0 hat. Es wird also scheinbar die falsche Property ausgelesen. In diesem Fall passt es zur Property "X".
Wenn man nun statt der Point3D Struktur eine Point3D Klasse anlegt
public class myPoint3D
{
public myPoint3D(double x, double y, double z)
{
X = x; Y = y; Z = z;
}
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
und damit den gleichen Test durchführt
[Test]
public void CheckClassSecondParameter()
{
var Pos = new myPoint3D(10.0, 20.0, 30.0);
var expected = 20.0;
var daSubProp = new DynamicAccessor(Pos.GetType().GetProperty("Y"));
var result = daSubProp.Get(Pos);
Assert.AreEqual(expected, result);
}
funktioniert es. Diesmal ist der Test grün.
Bei Stackoverflow habe ich einen Hinweis gefunden: DynamicMethod returns incorrect value when property type is Int64
Ich habe daher die 'EmitGetter' Funktion folgendermaßen angepasst:
Getter EmitGetter(PropertyInfo propertyInfo)
{
DynamicMethod dynMethod = new DynamicMethod("Get",
typeof(object),
new Type[] { typeof(object) },
propertyInfo.DeclaringType);
ILGenerator il = dynMethod.GetILGenerator();
MethodInfo getterMethod = propertyInfo.GetGetMethod();
il.DeclareLocal(typeof(object));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
//// Dieser Codeblock wurde ersetzt. CK,22.1.2014
//
// il.EmitCall(OpCodes.Callvirt, getterMethod, null);
//
// OpCode box = propertyInfo.PropertyType.IsValueType ?
// OpCodes.Box :
// OpCodes.Castclass;
//
// il.Emit(box, propertyInfo.PropertyType);
if (propertyInfo.PropertyType.IsValueType)
{
if (propertyInfo.ReflectedType.IsValueType)
{
il.Emit(OpCodes.Unbox, propertyInfo.ReflectedType);
}
il.EmitCall(OpCodes.Callvirt, getterMethod, null);
il.Emit(OpCodes.Box, propertyInfo.PropertyType);
}
else
{
il.EmitCall(OpCodes.Callvirt, getterMethod, null);
il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
}
il.Emit(OpCodes.Ret);
return (Getter)dynMethod.CreateDelegate(typeof(Getter));
}
Mit dieser Änderung werden die beiden Tests erfolgreich abgeschlossen.
Da ich in meiner Software keinen schreibenden Zugriff benötige, habe ich mir die 'EmitSetter' Funktion nicht näher angesehen. Ich vermute aber, dass dort auch noch Anpassungen notwendig sind.
Viele Grüße,
Christof