- 實例屬性的讀取
先來回顧下靜態屬性讀取的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倍左右的性能差異