打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
玩转动态编译:二、实战
玩转动态编译:一、初识中,我们已经学会了最简单的使用动态编译。今天直接由实战入手,看看真实情况下的动态编译能为我们来带什么。

今天要演示的实例是一个在实际开发中经常遇到的情况,对象转Json。

我将会使用2种方式分别做一个转json字符串的实例,1:反射;2:动态编译

  • 分析问题

分析C#对象在json中的映射。总体来说json对象只有几种情况

  1. 键值对对象,由多组键对象+值对象构成,最外面是一对花括号包裹,键值对对象同时也可作为“值对象”使用
  2. 数组对象,由多个值对象构成,最外面是一对中括号包裹,数组对象同时也可作为“值对象”使用
  3. 键对象,由一个字符串构成,在键值对对象组成中担任“键”
  4. 一般值对象,由一个单独的值构成,可以是string,int,bool等,在键值对对象或者数组对象中担任“值”
  5. 特殊值对象,键值对对象或数组对象,本身也可以作为值对象使用

这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#交流 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
JSON系列化和反列化
探索c#之不可变数据类型
Java服务器端List对象转换为JSON对象并返回客户端实例
Asp.net生成tree数据格式的json类
8.4 JSON转换器 - 接口实现 - AJava
深入理解JavaScript系列(9):根本没有“JSON对象”这回事!
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服