玩转动态编译:二、实战
在玩转动态编译:一、初识中,我们已经学会了最简单的使用动态编译。今天直接由实战入手,看看真实情况下的动态编译能为我们来带什么。
今天要演示的实例是一个在实际开发中经常遇到的情况,对象转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)); }
- 查看结果
- 小结
看到结论,可能有人要开始说了:貌似第二个动态编译的方法性能还不如反射的好啊~~
目前的情况来看呢,确实是这样的.不过动态编译当然不止如此,
性能上的问题是一定要解决的
欲知后事如何,且听下回分解
我写的文章,除了纯代码,其他的都是想表达一种思想,一种解决方案.希望各位看官不要局限于文章中的现成的代码,要多关注整个文章的主题思路,谢谢!
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐