玩转动态编译:二、实战


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

今天要演示的实例是一个在实际开发中经常遇到的情况,对象转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) + "}";
        }
    }
}
JsonConverter View Code

 

这个类完成大部分基础类型的转换工作,只有一个方法等待实现

 

  • 反射实现
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) + "}";

        }
    }
}
JsonConverter_Reflection View Code

 

  • 动态编译实现

动态编译的逻辑是这样的:因为在程序运行中,每个类型的相对应属性不可能发生更变,所以可以针对每个类型生成一个方法,

比如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('\'', '"');
        }
    }


}
JsonConverter_Dyncmp View Code

 

  • 测试调用
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; }
}
User View Code
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));
}

 

 

  • 查看结果

  • 小结
看到结论,可能有人要开始说了:貌似第二个动态编译的方法性能还不如反射的好啊~~
目前的情况来看呢,确实是这样的.不过动态编译当然不止如此,
性能上的问题是一定要解决的

欲知后事如何,且听下回分解

 
posted @ 2013-08-08 08:52  冰麟轻武  阅读(2374)  评论(13编辑  收藏  举报