It does the same thing as the linq version but requires separate assign blocks for each combination of field and property.
using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; namespace typeoverridedemostrater { static class SuperConverter<T1, T2> where T2 : new() { private static Dictionary<Tuple<System.Type, System.Type>, MethodInvoker> converters = new Dictionary<Tuple<System.Type, System.Type>, MethodInvoker>(); delegate T2 MethodInvoker(T1 inpt); static public T2 Convert(T1 i) { var key = new Tuple<System.Type, System.Type>(typeof(T2), typeof(T1)); if (!converters.ContainsKey(key)) { DynamicMethod dm = new DynamicMethod("xmethod", typeof(T2), new Type[] { typeof(T1) }, typeof(T2)); ILGenerator il = dm.GetILGenerator(); il.DeclareLocal(typeof(T2)); il.Emit(OpCodes.Newobj, typeof(T2).GetConstructor(System.Type.EmptyTypes)); il.Emit(OpCodes.Stloc_0); (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a)); il.Emit(OpCodes.Stfld, typeof(T2).GetField(a)); }); (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType }) join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod()); il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod()); }); (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a)); il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod()); }); (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType }) join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod()); il.Emit(OpCodes.Stfld, typeof(T2).GetField(a)); }); il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ret); var dg = (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker)); converters.Add(key, dg); } return (T2)converters[key](i); } } }
Compared to the Linq version at 100 million repetitions (at lower numbers the results are less predictable):
Linq: 94.579 seconds vs MSIL: 93.423 seconds
Some of that time is consumed dealing with the delegates, so you can transform that into a full dynamic assembly where you can have base classes and real instance references like this:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Threading; using System.Reflection; namespace typeoverridedemostrater { static class SuperConverterII<T1, T2> where T2 : new() { private static Dictionary<Tuple<System.Type, System.Type>, convertbase<T1, T2>> converters = new Dictionary<Tuple<System.Type, System.Type>, convertbase<T1, T2>>(); static public T2 Convert(T1 i) { var key = new Tuple<System.Type, System.Type>(typeof(T2), typeof(T1)); if (!converters.ContainsKey(key)) { var xassemblybuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("xassembly"), System.Reflection.Emit.AssemblyBuilderAccess.Run); var xtype = xassemblybuilder.DefineDynamicModule("xmodule").DefineType("xtype", TypeAttributes.Public, typeof(convertbase<T1, T2>)); var xfunction = xtype.DefineMethod("convert", MethodAttributes.Public | MethodAttributes.Virtual, typeof(T2), new System.Type[] { typeof(T1) }); xtype.DefineMethodOverride(xfunction, typeof(convertbase<T1, T2>).GetMethod("convert")); var il = xfunction.GetILGenerator(); il.DeclareLocal(typeof(T2)); il.Emit(OpCodes.Newobj, typeof(T2).GetConstructor(System.Type.EmptyTypes)); il.Emit(OpCodes.Stloc_0); (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a)); il.Emit(OpCodes.Stfld, typeof(T2).GetField(a)); }); (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType }) join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod()); il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod()); }); (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a)); il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod()); }); (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType }) join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType }) on z equals y select z.name).ToList().ForEach(a => { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod()); il.Emit(OpCodes.Stfld, typeof(T2).GetField(a)); }); il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ret); xtype.CreateType(); converters.Add(key, (convertbase<T1, T2>)xassemblybuilder.CreateInstance("xtype")); } return converters[key].convert(i); } } public abstract class convertbase<T1, T2> { public abstract T2 convert(T1 i); } }
Compared to the original MSIL version at 100 million repetitions:
Delegate MSIL: 93.423 seconds vs Base Class MSIL: 91.814 seconds
At REALLY high volumes that is significant, and I am certain that someone better at MSIL than I could probably make it a bit faster still, but for my purposes the Linq version is more than sufficient as I find the MSIL versions a little hard to work in for the level of benefit in return.
Nice work, Bill. And if I'm shaving a 1/2 second off of doing 100 million anything, I'd consider changing jobs.
ReplyDeleteIMHO It depends on if you are doing it because it would be nice or if you are doing it to try and save your butt...
ReplyDeleteWhen I started this third approach I actually expected that the MSIL versions should outperform the Linq versions quite a bit more than they did. I may have done something wrong. I am not really that good with the MSIL stuff yet.