我有一個想法,有一個可以進行跨平臺的高性能數據協議規範,可以讓數據在兩個不一樣的程序之間進行讀取,最好可以支持直接將object序列化,那就完美了。git
說到序列化,.NET自帶的XML序列化就很好用了,無奈有不少類型不支持,典型的好比Dictionary<>
,並且這個東西雖然強大,可是xml的標籤機制致使多餘的內容比較多,空間佔用會比較大。github
支持任意object序列化,.NET還提供了BinaryFormatter
。c#
// code from https://stackoverflow.com/questions/7442164/c-sharp-and-net-how-to-serialize-a-structure-into-a-byte-array-using-binary MyObject obj = new MyObject(); byte[] bytes; IFormatter formatter = new BinaryFormatter(); using (MemoryStream stream = new MemoryStream()) { formatter.Serialize(stream, obj); bytes = stream.ToArray(); }
這種方式支持任意的object進行序列化,不過有一個問題,它和Type模型嚴格綁定,只支持同一個程序集版本的消息交互,也不支持其餘語言編寫的程序。
我以前用過這種方式,用於單個程序內的數據快速保存與讀取。這種狀況下,只是單純在爲了保存object的狀態,操做很是便捷,我認爲很是合適。數組
這個東西就是grpc中的數據格式,能夠跨平臺,支持多種語言,數據是二進制的,壓縮率也很高。好吧,就是它了。安全
若是要在.NET中使用Protobuf協議,常常用的兩個類庫,一個是Google.Protobuf
,另一個是protobuf.net
。詳細的區別我就不贅述了,有一篇文章有多個對比。因爲我比較喜歡直接使用C#的類型系統,因此我仍是遵從文章建議,直接使用protobuf.net了。工具
對於通訊雙方都是.NET程序的狀況下,使用protobuf不須要直接編寫proto文件,能夠直接共享數據類的引用。若是是須要與非.NET程序進行通訊的話,也能夠經過工具生成,直接從proto中讀取信息並生成類。回顧一下目標,一條條處理。性能
protobuf經過定義實體類來進行序列化,因此也是支持任意對象的。這裏我就再也不詳細說明了,能夠在官網查看詳細使用方法。ui
一直有一個痛點,可否從序列化後的內容中還原通常對象,就是對象類型在編譯的時候未知的那種。經過保存類型的string名稱,在須要反序列化的時候,經過類型名稱加載類型,將內容反序列化爲指定類型。這個多多少少要用到反射了吧。編碼
static void Main(string[] args) { var ps = new List<string> { "1346dfg" , "31461sfghj", "24576sth"} ; var name = ps.GetType().FullName; using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create)) { Serializer.Serialize(ms, ps); } using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open)) { //data已經轉換爲List<string>對象,不過返回的類型仍是object,能夠強制轉換。 dynamic data = Serializer.Deserialize(Type.GetType(name), ms); Console.WriteLine(data[1]); } }
這裏使用到了一個Type類型的FullName屬性,對於內置類型對象,假設ps的類型是String的話,那個FullName
爲System.String,返回的內容很簡單。但在這個例子中,FullName
爲System.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]],感受一會兒複雜了不少,並且要命的是,這裏明確指明瞭CoreLib的引用,還有版本聲明。若是須要在.NET Core3.1中反序列化,確定是沒法實現。spa
嘗試解決一下,這個System.String不光在.NET 5中有,在其餘.NET平臺應該均可以支持,因此得想辦法去掉System.String
的尾巴。
能夠試着對FullName下手,可是這個東西有點太長了,並且直接處理字符串我不是很喜歡;試着從Type類型下手。
var ty = Type.GetType(name); Console.WriteLine(ty.Name); Console.WriteLine(ty.Namespace); Console.WriteLine(ty.GenericTypeArguments[0].Name); Console.WriteLine(ty.GenericTypeArguments[0].Namespace); //組合相關的代碼 dynamic data = Serializer.Deserialize(Type.GetType($"{ty.Namespace}.{ty.Name}" + $"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]"), ms); Console.WriteLine(data[1]);
稍微修改一下,經過手動鏈接Namespace與Name屬性就能夠達到咱們的目的了。
List`1這個表明這個泛型裏面只有一個參數,我這邊就硬編碼了,對於其餘泛型,可能有多個參數,須要進行鑑別,並調整構造Type名稱的代碼。
我按照這個思路,完整的代碼以下:
static void Main(string[] args) { var ps = new List<string> { "1346dfg", "31461sfghj", "24576sth" }; var ty = ps.GetType(); //保存Type名稱 var name = $"{ty.Namespace}.{ty.Name}" + $"[{ty.GenericTypeArguments[0].Namespace}.{ty.GenericTypeArguments[0].Name}]"; //實際的程序不涉及文件操做,這裏展現MemoryStream的用法。 using (MemoryStream ms = new MemoryStream()) { Serializer.Serialize(ms, ps); //重置指針,從頭開始讀 ms.Position = 0; //使用Type名稱反序列化 dynamic data = Serializer.Deserialize(Type.GetType(name), ms); Console.WriteLine(data[1]); } }
TypeCode
,而且不是object的對象。補充一下,我經常使用的幾種。定義在System命名空間下的類型,包括DateTime,Int32之類的,都是直接System.類型名稱的形式。
注意,int和float這種是不行的,須要使用Int32和Single。
泛型集合定義在System.Collections.Generic這個命名空間,因此組合起來爲System.Collections.Generic.泛型名稱`參數數量[System.類型名稱]。舉兩個例子:
List<string>
的是System.Collections.Generic.List`1[System.String]。
Dictionary<int,string>
的是System.Collections.Generic.Dictionary`2[[System.Int32],[System.String]]。
直接在名稱後添加[]便可,形式爲System.類型名稱[]。
序列化操做不要求序列化的類型和反序列化的類型徹底一致,好比說Array能夠與List,IEnumerable進行互換。所以,一些單獨定義的、結構比較簡單的類型,能夠經過內置類型進行反序列化,就沒有必要在反序列化的時候加載原始的類了,簡化了操做。
[ProtoContract] public class Message { [ProtoMember(1)] public List<string> values { get; set; } } static void Main(string[] args) { var ps = new Message { values = new List<string> { "1346dfg", "31461sfghj", "24576sth" } }; using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Create)) { Serializer.Serialize(ms, ps); } using (FileStream ms = new FileStream("d:\\a.txt", FileMode.Open)) { //List<string>反序列化,而無需使用Message類。這裏Message的FullName是"ConsoleApp6.Program+Message" dynamic data = Serializer.Deserialize(Type.GetType("System.Collections.Generic.List`1[System.String]"), ms); Console.WriteLine(data[1]); } }
另外,對於上面的dynamic,因爲編譯的時候不檢查,怕操做錯誤的同窗能夠進行類型轉換。分享一個代碼段,可能能有點幫助。
//轉換對象爲某一種類型 public static T ConvertTo<T>(object value) { return (T)Convert.ChangeType(value, typeof(T)); }
若是是限定的幾種類型,可使用switch語句進行判斷,並將對象轉成T,以進行類型安全的操做。若是不是的話,推薦使用接口來定義類的通用行爲,這個回答中提供了一些建議,推薦看看。