序列化對大家來說應該都不陌生,特別是現在大量使用WEBAPI,JSON滿天飛,序列化操作應該經常出現在我們的程式碼上。
而我們最常用的序列化工具應該就是Newtonsoft.Json,當然你用其它工具類也是沒問題的,我們重點講的不是這個工具,我們的重點是高效的可自定義控制的序列化操作。
首先我們說一下大致的序列化原理:
一般情況下,我們把一個實體類,或是資料列表傳給工具類(這裡我拿Newtonsoft.Json做例子,其它的也是類似的)如:
class ClassTest{
public string aa{get;set;}
public string bb{get;set;}
}
var jsonStr=Newtonsoft.Json.JsonConvert.SerializeObject(new ClassTest());
得到的jsonStr="{\"aa\":null,\"bb\":null}";
這個時候我們就在想,序列化是怎麼完成的呢,為什麼我只要給他一個類,他就能幫我轉化成一個JSON字串呢?(應該有點開發年頭的老鳥心裡都有數,我還是多囉嗦幾句,給新人看一下)
其實預設情況下它是使用了反射,當我們得到一個類,想知道它有什麼成員,以及成員分別是什麼值的時候,我們就只能用反射來得到,通過後設資料我們能得到任何我們想要的資訊(不瞭解反射的朋友可以去查一下相關資料),反射用起來確實很方便,但是有個很大的問題,就是效能,反射是相當耗效能的,下面我會講到怎麼樣不用反射來實現序列化。
因為我們今天講的重點是自定義,所以我們先來談談如何自定義。
預設情況下序列化工具會反射我們所有的屬性,然後將它們轉化到字串中,如果我們只想讓部分屬性序列化出來要怎麼做呢?
可能有人會說,那我再建立一個類,裡面只寫部分屬性不就好了,雖然這也是一個辦法,不過我相信大家都知道這個方法有多LOW,因為我們的需求是不斷變化的,可能我們一個類要應付幾十種場景,不可能我們每個場景都要新建一類吧,而且後期需求還會變動,還會增加,所以這個方案不行(如果你只想應付一下老闆這個方法還是行的...)
可能又有人說,這些序列化工具類都提供了對應的特性,只要我寫在我的屬性身上我就可以控制哪些顯示,哪些不顯示,哪些有什麼預設值了比如:
[JsonProperty]
[JsonIgnore]
[DefaultValue(30)]
但是我想說的是你還是不能解決我們剛剛說的問題,因為一個類特性你只能寫一次,你這樣做雖然比剛剛好了一點,但是應付多個場景依然不行。
我們想要的解決方案是可以由我們程式自由控制哪些類本次需要在序列化字串中,哪些不需要,我們只需要一個類就可以解決所有場景的問題,自由控制。
於是乎我們找到了一個方案 使用 ISerializable ,用它我們可以完全自定義序列化過程中的具體行為,不再需要反射來幫我們,我們自己來實現序列化的細節,這是最高效,最自由的一種方式,但是它也有一個缺點
就是過於繁瑣。還是拿上面的類來做個例子:
class ClassTest: ISerializable{
#region json序列化 反序列化
/// <summary>序列化時呼叫</summary>
public virtual void GetObjectData(SerializationInfo s, StreamingContext c)
{
if(!string.IsNullOrEmpty(aa)) //這裡可以使用任何條件來控制是否序列化
{s.AddValue("aa", aa);}
s.AddValue("bb", bb);
}
/// <summary>反序列化時操作</summary>
protected ClassTest(SerializationInfo s, StreamingContext c)
{
SerializationInfoEnumerator sItem = s.GetEnumerator();
while (sItem.MoveNext())
{
switch (sItem.Name)
{
case "aa": aa = s.GetString("aa"); break;
case "bb": bb = s.GetString("bb"); break;
default: break;
}
}
}
#endregion
public string aa{get;set;}
public string bb{get;set;}
}
只需要繼承這個介面,實現兩個方法,就可以完全的自定義序列化過程了
可能又有人說你這裡只說了JSON,還有XML序列化怎麼辦呢?不要急,XML也可以自定義,只要實現 IXmlSerializable,同樣實現它裡面定義的方法就可以了,我這裡就不詳細說了。
我們這樣做了以後序列化的時候它就不會使用反射了,而是直接呼叫相應的介面方法,這樣效能顯著提高,同時你又可以根據自己的情況自定義序列的規則。
那我們剛剛說了它有一個缺點就是寫起來太繁瑣了,那我們怎麼辦呢,其實我是一直都有程式碼生成器,我寫好了模版只要設計好資料庫模型,我的實體類就自動生成出來了,根本不需要打任何程式碼,所以對我來說也不繁瑣.
還有一個問題大家應該注意到了就是我所有的序列化判斷是在類的方法裡面寫的,如果我想在外部呼叫的時候來控制哪些欄位序列化,哪些不序列化怎麼辦呢,你這個也解決不了啊?
對,上面的例子確實解決不了,但是我還是有解決方案,我利用泛型把實體類做了改造,這樣我就可以完全在外部來控制序列化了,這個具體的實現我會在後期的文章中在介紹我自己設計的開發框架時再詳細說(如果大家有興趣看的話)
說了半天好像完全沒提到.NET core,現在要說它了,上面好個方案是可以用到之前的.net framework的各版本的,以及mono上也是可以執行的,但是現在最新的.NET core 就有點問題了,我最近想把我的開發框架
移植到.NET core上就出現了問題,它不支援ISerializable,本來我還以為是我用錯了,後來檢視了原始碼才發現是真的還不支援
因為.NET core才正式釋出沒多久,很多東西還不不是很完善,我在查這個問題的時候也是找了半天原因,所以我在這裡也是給大家提個醒,這個東西暫時還沒弄好,不過後面應該會弄好的,時間問題。
那麼現階段我們要怎麼處理呢,其實很簡單,既然序列化是把我們的類屬生拼接成一個字串,為什麼我們不自己實現這個方法呢,只要用程式碼生成器幫我們生成這段程式碼,對我們來說也是很方便,高效的,應該說比以上方法都要高效。比如:
class ClassTest{
public string aa{get;set;}
public string bb{get;set;}
public string ToJson()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder("{");
if(!string.IsNullOrEmpty(aa)) //這裡可以使用任何條件來控制是否序列化
{sb.AppendFormat(",\"aa\":{0}", aa);}
sb.AppendFormat(",\"bb\":{0}", bb);
sb.Append("}");
return sb.ToString();
}
}
就這樣寫個方法就可以了,需要用到序列化的地方只需要用 new ClassTest().ToJson()就可以了,這樣也很方便吧,同樣的你想要外部呼叫時使用哪些序列化,後期我會再詳細講
其實小弟第一次發部落格寫文章,寫的東西也很簡單,沒什麼太大的技術含量,希望各位大神,大蝦指點我的時候輕點拍磚,我也只是把我知道的一直在用的,感覺比較好用的分享出來而以,並不是來秀技術的,我也沒什麼技術好秀。
以上內容,完全手打原創,轉載請註明出處!望各位大神指錯!