程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 玩轉動態編譯(二) 實戰

玩轉動態編譯(二) 實戰

編輯:關於C#

在玩轉動態編譯:一、初識中,我們已經學會了最簡單的使用動態編譯。今天直接由實戰入手,看看 真實情況下的動態編譯能為我們來帶什麼。

今天要演示的實例是一個在實際開發中經常遇到的情 況,對象轉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));
}

查看結果

小結

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

查看本欄目

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved