在玩轉動態編譯:一、初識中,我們已經學會了最簡單的使用動態編譯。今天直接由實戰入手,看看 真實情況下的動態編譯能為我們來帶什麼。
今天要演示的實例是一個在實際開發中經常遇到的情 況,對象轉Json。
我將會使用2種方式分別做一個轉json字符串的實例,1:反射;2:動態編譯
分析問題
分析C#對象在json中的映射。總體來說json對象只有幾種情況
鍵值對 對象,由多組鍵對象+值對象構成,最外面是一對花括號包裹,鍵值對對象同時也可作為“值對象”使用
數組對象,由多個值對象構成,最外面是一對中括號包裹,數組對象同時也可作為“值對象”使 用
鍵對象,由一個字符串構成,在鍵值對對象組成中擔任“鍵”
一般值對象,由一個單 獨的值構成,可以是string,int,bool等,在鍵值對對象或者數組對象中擔任“值”
特殊值對 象,鍵值對對象或數組對象,本身也可以作為值對象使用
這4中對象分別對應了C#中的:
鍵值對對象 -> 任何有公開屬性的對象,或者實現了IDictionary的對象,或者同時擁有Key和Value枚 舉的對象
數組對象 -> 實現了IEnumerator或者IEnumerable接口的對象
鍵對象 -> string對象
一般值對象 -> System命名空間下的簡單值類型,包括int,bool,float,DateTime 等,外加一個string
編寫基類
為了滿足所有類型的轉換需求,首先要建立一個抽象基類 JsonConverter
using System; using System.Collections; using System.Collections.Generic; namespace blqw { /// <summary> /// 用於將C#轉換為Json字符串的抽象基類,基類提供基本類型的轉換,也可以重寫 /// </summary> public abstract class JsonConverter { public abstract string ToJson(object obj); public const string Flag = """; //基本類型轉換Json字符串 //bool值轉為true,false, //數值類型直接輸出,日期類型轉為指定格式字符串,前後加上雙引號 //字符串內部()替換為(),(")替換("),前後加上雙引號 //Guid轉為沒有-的字符串,前後加上雙引號 //方法命名按照From + 參數類名,為了一會反射和動態編譯的時候查找方法更方便 public virtual string FromBoolean(Boolean val) { return val ? "true" : "false"; } public virtual string FromByte(Byte val) { return val.ToString(); } public virtual string FromChar(Char val) { return val.ToString(); } public virtual string FromDateTime(DateTime val) { return Flag + val.ToString("yyyy-MM-dd HH:mm:ss") + Flag; } public virtual string FromDecimal(Decimal val) { return val.ToString(); } public virtual string FromDouble(Double val) { return val.ToString(); } public virtual string FromInt16(Int16 val) { return val.ToString(); } public virtual string FromInt32(Int32 val) { return val.ToString(); } public virtual string FromInt64(Int64 val) { return val.ToString(); } public virtual string FromSByte(SByte val) { return val.ToString(); } public virtual string FromSingle(Single val) { return val.ToString(); } public virtual string FromString(String val) { return Flag + val.Replace(@"",@"").Replace(""",@"""")+ Flag; } public virtual string FromUInt16(UInt16 val) { return val.ToString(); } public virtual string FromUInt32(UInt32 val) { return val.ToString(); } public virtual string FromUInt64(UInt64 val) { return val.ToString(); } public virtual string FromGuid(Guid val) { return Flag + val.ToString("N") + Flag; } //枚舉 public virtual string FromEnum(Enum val) { return Flag + val.ToString() + Flag; } //轉換數組對象 public virtual string FromArray(IEnumerator ee) { List<string> list = new List<string>(); while (ee.MoveNext()) { list.Add(ToJson(ee.Current)); } return "[" + string.Join(",", list) + "]"; } //轉換鍵值對對象 public virtual string FromKeyValue(IEnumerable keys, IEnumerable values) { List<string> list = new List<string>(); var ke = keys.GetEnumerator(); var ve = values.GetEnumerator(); bool a, b; while ((a = ke.MoveNext()) & (b = ve.MoveNext())) { if (ke.Current == null || (ke.Current + "").Length == 0) { throw new ArgumentNullException("Json鍵不能為null或空"); } list.Add(Flag + ke.Current + Flag + ":" + ToJson(ve.Current)); } if (a != b) { throw new ArgumentException("鍵值對的鍵和值個數不一致"); } return "{" + string.Join(",", list) + "}"; } } }
這個類完成大部分基礎類型的轉換工作,只有一個方法等待實現
反射實現
using System; using System.Collections; namespace blqw { /// <summary> 實現JsonConverter,利用反射構造Json字符串 /// </summary> public class JsonConverter_Reflection : JsonConverter { //靜態化,方便反復調用 readonly static Type _ThisType = typeof(JsonConverter_Reflection); //這個方法裡面的主要工作的就是obj的類型,來調用基類的不同方法,返回json字符串 public override string ToJson(object obj) { if (obj == null) { return "null"; } var type = obj.GetType(); type = Nullable.GetUnderlyingType(type) ?? type;//如果是可空值類型則獲取其內部基礎類型 if (type.Namespace == "System")//判斷如果是在System命名空間下的類型 { var met = _ThisType.GetMethod("From" + type.Name);//使用 From+類型名稱 作為方法名查找方法 if (met != null)//如果存在這樣的方法,直接反射調用方法 { return (string)met.Invoke(this, new object[] { obj }); } } if (obj is Enum)//枚舉 { return FromEnum((Enum)obj); } if (obj is IDictionary)//對象實現IDictionary { var dic = (IDictionary)obj; return FromKeyValue(dic.Keys, dic.Values); } if (obj is IEnumerator)//對象實現IEnumerator { return FromArray((IEnumerator)obj); } if (obj is IEnumerable)//對象實現IEnumerable { return FromArray(((IEnumerable)obj).GetEnumerator()); } //上面都不行,反射對象屬性 var ps = type.GetProperties(); if (ps.Length == 0)//如果對象屬性為空,直接返回空json { return "{}"; } string[] str = new string[ps.Length]; int i = 0; foreach (var p in ps)//反射對象屬性,和屬性值,構造Json字符串,處理屬性值的時候遞歸調用本身方法進行處理 { str[i++] = Flag + p.Name + Flag + ":" + ToJson(p.GetValue(obj)); } return "{" + string.Join(",", str) + "}"; } } }
動態編譯實現
動態編譯的邏輯是這樣的:因為在程序運行中,每個類型的相對應屬性 不可能發生更變,所以可以針對每個類型生成一個方法,
比如User對象
class User { public string Name { get; set; } public int Age { get; set; } public bool Sex { get; set; } }
我們可以為User對象生成一個方法,例如這個
public static string ToJson(User user) { return "{ \"Name\":\"" + user.Name + "\",\"Age\":" + user.Age + ",\"Sex\",\"" + (user.Sex ? "男" : "女") + "\"}"; }
這個方法如果自己寫實在是太蛋疼了,但是我們可以在程序中構造,由於動態編譯來完成,然後把方法 委托緩存起來,下次就可以直接使用了
整個方法是這樣的
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace blqw { /// <summary> 實現JsonConverter,利用動態編譯的方式輸出Json字符串 /// </summary> public class JsonConverter_Dyncmp : JsonConverter { //靜態化,方便反復調用 readonly static Type _ThisType = typeof(JsonConverter_Dyncmp); public override string ToJson(object obj) {//跟剛才那個方法邏輯基本是一致的只有最後實現的部分不一樣 if (obj == null) { return "null"; } var type = obj.GetType(); type = Nullable.GetUnderlyingType(type) ?? type;//如果是可空值類型則獲取其內部基礎類型 if (type.Namespace == "System")//判斷如果是在System命名空間下的類型 { var met = _ThisType.GetMethod("From" + type.Name);//使用 From+類型名稱 作為方法名查找方法 if (met != null)//如果存在這樣的方法,直接反射調用方法 { return (string)met.Invoke(this, new object[] { obj }); } } if (obj is Enum)//枚舉 { return FromEnum((Enum)obj); } if (obj is IDictionary)//對象實現IDictionary { var dic = (IDictionary)obj; return FromKeyValue(dic.Keys, dic.Values); } if (obj is IEnumerator)//對象實現IEnumerator { return FromArray((IEnumerator)obj); } if (obj is IEnumerable)//對象實現IEnumerable { return FromArray(((IEnumerable)obj).GetEnumerator()); } //上面都不行,動態編譯方法 { MethodInfo met; //在緩存中查詢是否已經編譯過了 if (MethodCache.TryGetValue(type, out met) == false) {//如果沒有,則編譯,並加入緩存 var code = CreateCode(type);//獲得代碼 var ass = DynamicCompile_1.CompileAssembly(code, typeof(Json), typeof(StringBuilder), typeof(IDictionary), typeof(Enum), typeof(IEnumerable), typeof(IEnumerator));//編譯 met = ass.GetTypes()[0].GetMethods()[0];//反射編譯後的方法 MethodCache.Add(type, met);//加入緩存 } return (string)met.Invoke(null, new object[] { obj });//執行方法,等到json字符串 } } //動態編譯方法緩存 private static Dictionary<Type, MethodInfo> MethodCache = new Dictionary<Type, MethodInfo>(); //得到一個類型的可視名稱,比如泛型類,List`1這種名字是不可以用的 private static string TypeDisplayName(Type type) { if (type == null) { return "null"; } if (type.IsGenericType) { var arr = type.GetGenericArguments(); string gname = type.GetGenericTypeDefinition().FullName; gname = gname.Remove(gname.IndexOf('`')); if (arr.Length == 1) { return gname + "<" + TypeDisplayName(arr[0]) + ">"; } StringBuilder sb = new StringBuilder(gname); sb.Append("<"); foreach (var a in arr) { sb.Append(TypeDisplayName(a)); sb.Append(","); } sb[sb.Length - 1] = '>'; return sb.ToString(); } else { return type.FullName.Replace('+', '.'); } } //根據類型,創建生成Json字符串的動態代碼 private string CreateCode(Type type) { //大體的邏輯就是 根據屬性的類型 var className = "_" + Guid.NewGuid().ToString("N"); StringBuilder sb = new StringBuilder(); sb.AppendLine("public class " + className); sb.AppendLine("{"); sb.AppendFormat("public static string a({0} obj)", TypeDisplayName(type)); sb.AppendLine("{"); sb.Append("return new StringBuilder()"); var ee = type.GetProperties().GetEnumerator(); string[] baseMethods = base.GetType().GetMethods().Select(it => it.Name).ToArray(); PropertyInfo p; string method; Type ptype; string pre = "{"; while (ee.MoveNext()) { p = (PropertyInfo)ee.Current; ptype = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType; sb.Append(".Append('").Append(pre).Append("'").Append(p.Name).Append("':')"); pre = ","; method = "From" + ptype.Name; if (ptype.Namespace == "System" && baseMethods.Contains(method)) { sb.Append(".Append(Json.Converter2.").Append(method).Append("((").Append(ptype.FullName).Append(")obj.").Append(p.Name).Append("))"); } else if (ptype.IsEnum)//屬性是枚舉 { sb.Append(".Append(Json.Converter2.FromEnum((Enum)obj.").Append(p.Name).Append("))"); } else if (ptype.GetInterface("IDictionary") == typeof(IDictionary))//屬性實現IDictionary { sb.Append(".Append(Json.Converter2.FromKeyValue(((IDictionary)obj.").Append(p.Name).Append(").Keys,((IDictionary)obj.").Append(p.Name).Append(").Values))"); } else if (ptype.GetInterface("IEnumerator") == typeof(IEnumerator))//對象實現IEnumerator { sb.Append(".Append(Json.Converter2.FromArray((IEnumerator)obj.").Append(p.Name).Append("))"); } else if (ptype.GetInterface("IEnumerable") == typeof(IEnumerable))//對象實現IEnumerable { sb.Append(".Append(Json.Converter2.FromArray(((IEnumerable)obj.").Append(p.Name).Append(").GetEnumerator()))"); } else { sb.Append(".Append(Json.ToJson_2(obj.").Append(p.Name).Append("))"); } } sb.AppendLine(".Append('}').ToString();").AppendLine("}").AppendLine("}"); return sb.ToString().Replace(''', '"'); } } }
測試調用
namespace blqw { public static class Json { public static JsonConverter Converter1 = new JsonConverter_Reflection(); public static JsonConverter Converter2 = new JsonConverter_Dyncmp(); public static string ToJson_1(object obj) { return Converter1.ToJson(obj); } public static string ToJson_2(object obj) { return Converter2.ToJson(obj); } } }
ToJson_1就是反射方式ToJson_2是動態編譯的方式再附上測試代碼一個非常復雜的對象
using System; using System.Collections.Generic; /// <summary> 用戶對象 /// </summary> public class User { /// <summary> 唯一ID /// </summary> public Guid UID { get; set; } /// <summary> 用戶名稱 /// </summary> public string Name { get; set; } /// <summary> 生日 /// </summary> public DateTime? Birthday { get; set; } /// <summary> 性別 /// </summary> public UserSex Sex { get; set; } /// <summary> 是否刪除標記 /// </summary> public bool IsDeleted { get; set; } /// <summary> 最近登錄記錄 /// </summary> public List<DateTime> LoginHistory { get; set; } /// <summary> 聯系信息 /// </summary> public UserInfo Info { get; set; } } /// <summary> 用戶性別 /// </summary> public enum UserSex { /// <summary> 男 /// </summary> Male, /// <summary> 女 /// </summary> Female } /// <summary> 用戶信息 /// </summary> public class UserInfo { /// <summary> 地址 /// </summary> public string Address { get; set; } /// <summary> 聯系方式 /// </summary> public Dictionary<string, string> Phone { get; set; } /// <summary> 郵政編碼 /// </summary> public int ZipCode { get; set; } }
static User GetUser() {//這裡我盡量構造一個看上去很復雜的對象,並且這個對象幾乎涵蓋了所有常用的類型 User user = new User(); user.UID = Guid.NewGuid(); user.Birthday = new DateTime(1986, 10, 29, 18, 00, 00); user.IsDeleted = false; user.Name = "blqw"; user.Sex = UserSex.Male; user.LoginHistory = new List<DateTime>(); user.LoginHistory.Add(DateTime.Today.Add(new TimeSpan(8, 00, 00))); user.LoginHistory.Add(DateTime.Today.Add(new TimeSpan(10, 10, 10))); user.LoginHistory.Add(DateTime.Today.Add(new TimeSpan(12, 33, 56))); user.LoginHistory.Add(DateTime.Today.Add(new TimeSpan(17, 25, 18))); user.LoginHistory.Add(DateTime.Today.Add(new TimeSpan(23, 06, 59))); user.Info = new UserInfo(); user.Info.Address = "廣東省廣州市"; user.Info.ZipCode = 510000; user.Info.Phone = new Dictionary<string, string>(); user.Info.Phone.Add("手機", "18688888888"); user.Info.Phone.Add("電話", "82580000"); user.Info.Phone.Add("短號", "10086"); user.Info.Phone.Add("QQ", "21979018"); return user; }
測試用代碼:
static void Main(string[] args) { var user = GetUser(); Stopwatch sw = new Stopwatch(); sw.Restart(); for (int i = 0; i < 10000; i++) { Json.ToJson_1(user); } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds + "ms"); sw.Restart(); for (int i = 0; i < 10000; i++) { Json.ToJson_2(user); } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds + "ms"); Console.WriteLine(); Console.WriteLine(Json.ToJson_1(user)); Console.WriteLine(); Console.WriteLine(Json.ToJson_2(user)); }
查看結果
小結
看到結論,可能有人要開始說了:貌似第二個動態編譯的方法性能還不如反射的好啊~~ 目前的情況來看呢,確實是這樣的.不過動態編譯當然不止如此, 性能上的問題是一定要解決的
查看本欄目