在玩轉動態編譯:一、初識中,我們已經學會了最簡單的使用動態編譯。今天直接由實戰入手,看看 真實情況下的動態編譯能為我們來帶什麼。
今天要演示的實例是一個在實際開發中經常遇到的情 況,對象轉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));
}
查看結果

小結
看到結論,可能有人要開始說了:貌似第二個動態編譯的方法性能還不如反射的好啊~~ 目前的情況來看呢,確實是這樣的.不過動態編譯當然不止如此, 性能上的問題是一定要解決的
查看本欄目