Light-weight Code Gen, LCG for short, is accomplished via the DynamicMethod class. I found the perfect example to follow here. However I needed slightly different code to handle calling getters on a struct. It took me a while to figure out how to do it. My advice if you're trying to do this is to write normal c# code in a method and compile it. Then open up your assembly with IL DASM, which is part of your Visual Studio install. Look at the code and then try and duplicate it with Light-weight Code Gen. This is exactly what I did, however I misread some of the code and ended up with slightly different code which caused exceptions.
Which brings me to my next point. LCG can be hard to debug since there's no source file. I misread one of the op-codes and ended up with some memory exceptions. I didn't have much luck setting up debugging, but I did find this very helpful Debugger Visualizer that allowed me to view the generated code and compare it to my example method that the compiler generated and figure out what was wrong. Here is the code to call a property getter on a struct (Note: you can't call property setters on structs since they are value objects).
/// <summary>
/// Creates a dynamic getter for the property
/// </summary>
/// <param name="propertyInfo">
/// <returns></returns>
private static GenericGetter CreateStructGetMethod(PropertyInfo propertyInfo)
{
/*
* If there's no getter return null
*/
MethodInfo getMethod = propertyInfo.GetGetMethod();
if (getMethod == null)
return null;
/*
* Create the dynamic method
*/
Type[] arguments = new Type[1];
arguments[0] = typeof(object);
string mName = propertyInfo.GetGetMethod().Name;
DynamicMethod getter = new DynamicMethod(
String.Concat("_Get", propertyInfo.Name, "_"),
typeof(object), arguments, propertyInfo.DeclaringType);
ILGenerator generator = getter.GetILGenerator();
generator.DeclareLocal(typeof(object)); // return value
generator.DeclareLocal(propertyInfo.DeclaringType); // struct instance
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Unbox_Any, propertyInfo.DeclaringType);
generator.Emit(OpCodes.Stloc_1); // get the unboxed value from the stack
//generator.Emit(OpCodes.Ldloc_1);
generator.Emit(OpCodes.Ldloca_S, 1);
generator.EmitCall(OpCodes.Call, getMethod, null);
if (!propertyInfo.PropertyType.IsClass)
generator.Emit(OpCodes.Box, propertyInfo.PropertyType);
generator.Emit(OpCodes.Ret);
/*
* Create the delegate and return it
*/
return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
}
So what was the performance increase?
The Old:
Serialization Performance Test
Serializer Type: JsonExSerializer
File Size : 31989 bytes
Iterations : 2500
Object Count : 100
Total Time : 27373ms
Avg Time : 10.949ms per iteration
Deserialization Performance Test
Serializer Type: JsonExSerializer
File Size : 31989 bytes
Iterations : 2500
Object Count : 100
Total Time : 51909ms
Avg Time : 20.764ms per iteration
The New:
Serialization Performance Test
Serializer Type: JsonExSerializer
File Size : 31989 bytes
Iterations : 2500
Object Count : 100
Total Time : 17534ms
Avg Time : 7.014ms per iteration
Deserialization Performance Test
Serializer Type: JsonExSerializer
File Size : 31989 bytes
Iterations : 2500
Object Count : 100
Total Time : 34285ms
Avg Time : 13.714ms per iteration
About 30% faster on serialization, and 35% faster on deserialization.