玩转动态编译 - 高级篇:三,实例属性的读取与设置
- 实例属性的读取
先来回顾下静态属性读取的IL代码:
.method public hidebysig instance string AAA() cil managed { .maxstack 8 L_0000: call string blqw.IL.Demo.Program/MyClass::get_Name() L_0005: ret }
string AAA() { return MyClass.Name; }
再来看下读取实例属性的IL代码
.method private hidebysig instance string AAA(class blqw.IL.Demo.Program/MyClass my) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: callvirt instance string blqw.IL.Demo.Program/MyClass::get_Name() L_0006: ret }
string AAA(MyClass my) { return my.Name; }
区别很明显,多个一个指令ldarg.0 ,并且指令有所区别
// 将索引为 0 的参数加载到计算堆栈上。 public static readonly OpCode Ldarg_0;
操作实例方法和操作静态方法不同,静态方法不需要任何额外的参数,而实例方法必须要提供一个参数,这个参数指示操作的实例对象
转换成C#代码就是这样的
public static Func<MyClass, string> ILTest() { var type = typeof(MyClass); var prop = type.GetProperty("Name");//反射属性 var dm = new DynamicMethod("", typeof(string), new[] { typeof(MyClass) }, type); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); il.Emit(OpCodes.Ret); return (Func<MyClass, string>)dm.CreateDelegate(typeof(Func<MyClass, string>)); }
- 实例属性的设置
IL代码
.method private hidebysig instance void AAA(class blqw.IL.Demo.Program/MyClass my, string name) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldarg.1 L_0002: callvirt instance void blqw.IL.Demo.Program/MyClass::set_Name(string) L_0007: ret }
好吧,又多了一个参数,不过这个是显而易见的,既然你要设置值,总要把值当作参数传递进去吧
对应C#代码如下:
public static Action<MyClass, string> ILTest() { var type = typeof(MyClass); var prop = type.GetProperty("Name");//反射属性 var dm = new DynamicMethod("", null, new[] { typeof(MyClass), typeof(string) }, type); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Callvirt, prop.GetSetMethod()); il.Emit(OpCodes.Ret); return (Action<MyClass, string>)dm.CreateDelegate(typeof(Action<MyClass, string>)); }
重复来重复去都是这几样东西了..有些无聊了吧...
那么接下来就做一些实际情况下的应用了
- 实际应用
这次我要举起来的栗子就是DataTable转模型对象
不过在这之前,我要对现有的方法进行一些调整
public delegate void PropertySetter(object instance, object value); public static PropertySetter CreateSetter(PropertyInfo property) { var type = property.DeclaringType; var dm = new DynamicMethod("", null, new[] { typeof(object), typeof(object) }, type); //=== IL === var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); if (property.PropertyType.IsValueType)//判断属性类型是否是值类型 { il.Emit(OpCodes.Unbox,property.PropertyType);//如果是值类型就拆箱 } else { il.Emit(OpCodes.Castclass, property.PropertyType);//否则强转 } il.Emit(OpCodes.Callvirt, property.GetSetMethod()); il.Emit(OpCodes.Ret); //=== IL === return (PropertySetter)dm.CreateDelegate(typeof(PropertySetter)); }
修改的地方不是很多,应该不难理解,关于类型转换部分,请参考上一篇
现在我可以很方便的通过这个方法创建一个任意实例属性的Set方法委托
接下来我需要一个的新的类
public class ObjectProperty { public PropertyInfo Info { get; set; } public PropertySetter Setter { get; set; } }
这个类包含一个属性和这个属性的Set方法委托
在接下来我需要一个方法,把任意一个类中的所有公开的实例属性,转换成ObjectProperty集合
static readonly Dictionary<Type, ObjectProperty[]> Cache = new Dictionary<Type, ObjectProperty[]>(); public static ObjectProperty[] GetProperties(Type type) { ObjectProperty[] arr; if (Cache.TryGetValue(type, out arr))//优先从缓存中获取 { return arr; } PropertyInfo[] ps = type.GetProperties(); arr = new ObjectProperty[ps.Length]; for (int i = 0; i < ps.Length; i++) { ObjectProperty op = new ObjectProperty(); op.Info = ps[i]; op.Setter = CreateSetter(op.Info); //之前定义的方法 arr[i] = op; } Cache.Add(type, arr); //加入缓存 return arr; }
把他们整合起来
public class ObjectProperty { /// <summary> 属性信息 /// </summary> public PropertyInfo Info { get; set; } /// <summary> Set方法委托 /// </summary> public PropertySetter Setter { get; set; } //缓存 static readonly Dictionary<Type, ObjectProperty[]> Cache = new Dictionary<Type, ObjectProperty[]>(); /// <summary> 获取一个类中的所有公开实例属性和它们的Set方法委托 /// </summary> public static ObjectProperty[] GetProperties(Type type) { ObjectProperty[] arr; if (Cache.TryGetValue(type, out arr))//优先从缓存中获取 { return arr; } PropertyInfo[] ps = type.GetProperties(); arr = new ObjectProperty[ps.Length]; for (int i = 0; i < ps.Length; i++) { ObjectProperty op = new ObjectProperty(); op.Info = ps[i]; op.Setter = CreateSetter(op.Info); //之前定义的方法 arr[i] = op; } Cache.Add(type, arr); //加入缓存 return arr; } /// <summary> 创建指定属性的Set方法委托 /// </summary> public static PropertySetter CreateSetter(PropertyInfo property) { var type = property.DeclaringType; var dm = new DynamicMethod("", null, new[] { typeof(object), typeof(object) }, type); //=== IL === var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); if (property.PropertyType.IsValueType)//判断属性类型是否是值类型 { il.Emit(OpCodes.Unbox, property.PropertyType);//如果是值类型就拆箱 } else { il.Emit(OpCodes.Castclass, property.PropertyType);//否则强转 } il.Emit(OpCodes.Callvirt, property.GetSetMethod()); il.Emit(OpCodes.Ret); //=== IL === return (PropertySetter)dm.CreateDelegate(typeof(PropertySetter)); } }
现在就可以写出一个将DataTable转为实体类的方法了
public static List<T> ConvertToModels<T>(DataSet ds) where T : new() { var prop = ObjectProperty.GetProperties(typeof(T)); List<T> list = new List<T>(ds.Tables[0].Rows.Count); var cols = ds.Tables[0].Columns; foreach (DataRow row in ds.Tables[0].Rows) { T m = new T(); foreach (var p in prop) { if (cols.Contains(p.Info.Name)) { var val = row[p.Info.Name]; if ((val is DBNull) == false) { p.Setter(m, val); } } } list.Add(m); } return list; }
好了,我现在模拟出一个DataSet和一个实体类来测试下
public class User { public User() { } public int Id { get; set; } public string Name { get; set; } public bool Sex { get; set; } public Guid Uid { get; set; } public DateTime Time { get; set; } public string SexText { get { return Sex ? "男" : "女"; } set { Sex = (value == "男"); } } } //模拟方法 static public DataSet GetDataSet(string sql) { DataTable table = new DataTable("User"); table.Columns.Add("Id", typeof(int)); table.Columns.Add("Name", typeof(string)); table.Columns.Add("Sex", typeof(bool)); table.Columns.Add("Uid", typeof(Guid)); table.Columns.Add("Time", typeof(DateTime)); table.Columns.Add("多出来的属性", typeof(string)); for (int i = 0; i < 20; i++) { table.Rows.Add(i, "blqw" + i, true, Guid.NewGuid(), DateTime.Now, "多余的"); } DataSet ds = new DataSet(); ds.Tables.Add(table); return ds; }
- 反射和IL
就功能上来说IL可以做的,反射都可以做.基本上IL的操作指令很多参数都是需要用到反射对象的
那么我们为什么要选择麻烦的IL,而不直接用反射呢,答案就是性能
就拿上面的栗子来说,如果我们用反射来实现的话是这样的
static public List<T> ConvertToModels2<T>(DataSet ds) where T : new() { var prop = ObjectProperty.GetProperties(typeof(T)); List<T> list = new List<T>(ds.Tables[0].Rows.Count); var cols = ds.Tables[0].Columns; foreach (DataRow row in ds.Tables[0].Rows) { T m = new T(); foreach (var p in prop) { if (cols.Contains(p.Info.Name)) { var val = row[p.Info.Name]; if (Convert.IsDBNull(val) == false) { p.Info.SetValue(m, val, null);//这里直接用反射的SetValue } } } list.Add(m); } return list; }
这只是构造10000个只有5个属性的实体类而已
因为先运行的是动态编译IL的测试,所以缓存什么的都已经在这个时候建好了,下面反射只是调用缓存
如果这个测试还看不出太大区别的话,那就看下直接对比Set部分的性能
一般来说是6倍左右的性能差异
我写的文章,除了纯代码,其他的都是想表达一种思想,一种解决方案.希望各位看官不要局限于文章中的现成的代码,要多关注整个文章的主题思路,谢谢!
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐