前言:
經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的效能。
隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大夥分析或使用在專案中。
ORM 實現的三個通用階段:
第一階段:
在以往新手入門寫 ORM 實現的時候,往往會藉助程式碼生成器,來針對整個資料庫,生成一個一個的基礎增刪改查。
用程式碼生成器提前生成針對性的方法,執行效率高,但開發效率有可維護性低。
第二階段:
隨著對程式進一步的理解,可能會進化的使用反射來替代程式碼生成器,可以簡化掉大量的生成式程式碼。
但該方向正好相反,執行效率低,開發效率和可維護性高,透過對反射屬性加以快取,可以改善執行效率問題。
第三階段:
今天給出的專案示例是:
透過 Emit 實現 ORM 中常用的,透過 ADO.NET 的 DataReader 流讀取資料庫資料,並將其讀取到實體類 這一例子。
透過該方法,可以即有高的執行效率,同時又保持開發效率和可維護性。
下面看基礎示例:
示例程式碼:
以下示例程式碼,取自 CYQ.Data:
using System; using System.Collections.Generic; using System.Text; using System.Reflection.Emit; using System.Reflection; using CYQ.Data.Table; using CYQ.Data.Tool; using CYQ.Data.SQL; using System.Data.Common; namespace CYQ.Data.Emit { /// <summary> /// DbDataReader 轉實體 /// </summary> internal static partial class DbDataReaderToEntity { static Dictionary<Type, Func<DbDataReader, object>> typeFuncs = new Dictionary<Type, Func<DbDataReader, object>>(); private static readonly object lockObj = new object(); internal static Func<DbDataReader, object> Delegate(Type t) { if (typeFuncs.ContainsKey(t)) { return typeFuncs[t]; } lock (lockObj) { if (typeFuncs.ContainsKey(t)) { return typeFuncs[t]; } DynamicMethod method = CreateDynamicMethod(t); var func = method.CreateDelegate(typeof(Func<DbDataReader, object>)) as Func<DbDataReader, object>; typeFuncs.Add(t, func); return func; } } /// <summary> /// 構建一個ORM實體轉換器(第1次構建有一定開銷時間) /// </summary> /// <param name="entityType">轉換的目標型別</param> private static DynamicMethod CreateDynamicMethod(Type entityType) { #region 建立動態方法 var readerType = typeof(DbDataReader); Type convertToolType = typeof(ConvertTool); MethodInfo getValue = readerType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null); MethodInfo changeType = convertToolType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(object), typeof(Type) }, null); MethodInfo getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle"); DynamicMethod method = new DynamicMethod("DbDataReaderToEntity", typeof(object), new Type[] { readerType }, entityType); var constructor = entityType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null); ILGenerator gen = method.GetILGenerator();//開始編寫IL方法。 if (constructor == null) { gen.Emit(OpCodes.Ret); return method; } var instance = gen.DeclareLocal(entityType);//0 : Entity t0; gen.DeclareLocal(typeof(object));//1 string s1; gen.DeclareLocal(typeof(Type));//2 Type t2; gen.Emit(OpCodes.Newobj, constructor); gen.Emit(OpCodes.Stloc_0, instance);//t0= new T(); List<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType); if (properties != null && properties.Count > 0) { foreach (var property in properties) { SetValueByRow(gen, getValue, changeType, getTypeFromHandle, property, null); } } List<FieldInfo> fields = ReflectTool.GetFieldList(entityType); if (fields != null && fields.Count > 0) { foreach (var field in fields) { SetValueByRow(gen, getValue, changeType, getTypeFromHandle, null, field); } } gen.Emit(OpCodes.Ldloc_0, instance);//t0 載入,準備返回 gen.Emit(OpCodes.Ret); #endregion return method; } private static void SetValueByRow(ILGenerator gen, MethodInfo getValue, MethodInfo changeType, MethodInfo getTypeFromHandle, PropertyInfo pi, FieldInfo fi) { Type valueType = pi != null ? pi.PropertyType : fi.FieldType; string fieldName = pi != null ? pi.Name : fi.Name; Label labelContinue = gen.DefineLabel();//定義迴圈標籤;goto; gen.Emit(OpCodes.Ldarg_0);//載入 reader 物件 gen.Emit(OpCodes.Ldstr, fieldName);//設定欄位名。 gen.Emit(OpCodes.Callvirt, getValue);//reader.GetValue(...) gen.Emit(OpCodes.Stloc_1);//將索引 1 處的區域性變數載入到計算堆疊上。 gen.Emit(OpCodes.Ldloc_1);//將索引 1 處的區域性變數載入到計算堆疊上。 gen.Emit(OpCodes.Brfalse_S, labelContinue);//if(!a){continue;} //-------------新增:o=ConvertTool.ChangeType(o, t); if (valueType.Name != "Object") { gen.Emit(OpCodes.Ldtoken, valueType);//這個卡我卡的有點久。將後設資料標記轉換為其執行時表示形式,並將其推送到計算堆疊上。 //下面這句Call,解決在 .net 中無法獲取Type值,拋的異常:嘗試讀取或寫入受保護的記憶體。這通常指示其他記憶體已損壞。 gen.Emit(OpCodes.Call, getTypeFromHandle); gen.Emit(OpCodes.Stloc_2); gen.Emit(OpCodes.Ldloc_1);//o gen.Emit(OpCodes.Ldloc_2); gen.Emit(OpCodes.Call, changeType);//Call ChangeType(o,type);=> invoke(o,type) 呼叫由傳遞的方法說明符指示的方法。 gen.Emit(OpCodes.Stloc_1); // o=GetItemValue(ordinal); } //------------------------------------------- SetValue(gen, pi, fi); gen.MarkLabel(labelContinue);//繼續下一個迴圈 } private static void SetValue(ILGenerator gen, PropertyInfo pi, FieldInfo fi) { if (pi != null && pi.CanWrite) { gen.Emit(OpCodes.Ldloc_0);//實體物件obj gen.Emit(OpCodes.Ldloc_1);//屬性的值 objvalue EmitCastObj(gen, pi.PropertyType);//型別轉換 gen.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(), null); // Call the property setter } if (fi != null) { gen.Emit(OpCodes.Ldloc_0);//實體物件obj gen.Emit(OpCodes.Ldloc_1);//屬性的值 objvalue EmitCastObj(gen, fi.FieldType);//型別轉換 gen.Emit(OpCodes.Stfld, fi);//對實體賦值 System.Object.FieldSetter(String typeName, String fieldName, Object val) } } private static void EmitCastObj(ILGenerator il, Type targetType) { if (targetType.IsValueType) { il.Emit(OpCodes.Unbox_Any, targetType); } else { il.Emit(OpCodes.Castclass, targetType); } } } }
示例程式碼使用示例:
private static List<T> ReaderToListEntity<T>(DbDataReader reader) { List<T> list = new List<T>(); var func = DbDataReaderToEntity.Delegate(typeof(T)); while (reader.Read()) { object obj = func(reader); if (obj != null) { list.Add((T)obj); } } return list; }
示例程式碼使用示例重點講解:
1、Emit 實現中,接收 DbDataReader 做為引數,它是各種 DataReader 的基類:
可以適應不同的資料庫型別,如果新手使用只是針對某一資料庫型別,也可以修改為:SqlDataReader 或 MySqlDataReader 等。
2、Emit 實現中,僅實現讀取當前行資料的功能,而讀取多行,是在外層封裝(即使用示例的封裝方法)實現:
這樣的好處是可以簡化 Emit 的部分實現,同時又保留高效的效能。
3、Emit 實現中,涉及到三個外部方法:
A:List<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType);
該方法是 CYQ.Data 的內部的實現,以快取反射的屬性,可以用以下程式碼替代:
PropertyInfo[] pInfo = t.GetProperties();
B:List<FieldInfo> fields = ReflectTool.GetFieldList(entityType);
該方法是 CYQ.Data 的內部的實現,以快取反射的屬性,可以用以下程式碼替代:
FieldInfo[] pInfo = t.GetFields();
C:ConvertTool.ChangeType 方法:
方法原型如下,實現全品類型別的安全轉換:
public static object ChangeType(object value, Type t)
如果生成的實體類和資料庫型別保持一致,則可以不需要進行型別轉換,加型別轉換,是為了可以相容資料庫欄位型別和實體類屬性型別的不同。
該方法的高效實現,可以參考:ConverTool
總結:
Emit 雖然活躍在 ORM 和 動態代理的領域,但掌握它, 並在合適的場景使用它,則可以獲得更高效的解決方案。
當然,前提是你需要對程式 “效能” 有清晰的追求。