會有這個文章主要是因為,有需要比對序列化後字串是否符合預期的情境,但是卻因為沒有注意到這點讓測試程式碼怎樣都過不了 Orz
這個問題坦白說,若沒有踩到還真不會特別去計較這件事。關於BOM(位元組順序記號),就我的實務經驗來講是很少會使用到的資訊,尤其是最常用到的編碼方式為UTF-8,一般來說我不會馬上想到這個方向。
而UTF-8 with BOM的檔案基本上跟 without BOM用肉眼是看不出差異的,只會在使用游標左右移動時發現需要多按一下才會動,在精神不濟時會以為這些都是幻覺 XD。但若是用Notepad++的話,可以開啟16進位的檢視,就能看到UTF-8 BOM的那該死的位元組「ef bb bf」,在windows平台上有時可以看到存檔時可以選擇是否要包含BOM這個選項。
實驗:
在 XmlTextWriter 實體要 new 出來時會有幾種作法,其中我們要講的就是把Encoding方式傳入的那種。
public class SerializeHelper { ////// 序列化 /// /// 物件 /// 編碼 ///序列化後文字 public string Serialize(object o, Encoding encoding) { XmlSerializer xmls = new XmlSerializer(o.GetType()); var xns = new XmlSerializerNamespaces(); xns.Add(string.Empty, string.Empty); using (MemoryStream ms = new MemoryStream()) { using (var writer = new XmlTextWriter(ms, encoding)) { xmls.Serialize(writer, o, xns); } string xml = Encoding.UTF8.GetString(ms.ToArray()); return xml; } } }
文字是否有加入BOM一般來說很難用肉眼看出來有什麼差異,因此我們使用GetByteCount來識別是否有BOM參入其中,看看是否真的多了那三個該死的位元標記符號。
在驗證的程式碼我們這樣寫:
class Program { static void Main(string[] args) { var demonItem = new DemonClass() { Address = "Taipei", Id = 1, Name = "Ben", Refer = new DemonClass() }; var serialize = new SerializeHelper(); //// Encoding.UTF8預設會帶BOM var stringObj = serialize.Serialize(demonItem, Encoding.UTF8); Console.WriteLine("stringObj:" + Encoding.UTF8.GetByteCount(stringObj)); var stringObj2 = serialize.Serialize(demonItem, new UTF8Encoding(false)); Console.WriteLine("stringObj2:" + Encoding.UTF8.GetByteCount(stringObj2)); } }
執行過後就會發現,我們在.NET平台最常使用到的呼叫方式就是那種會帶入BOM的那種。
這點我們可以從原始碼中略窺一二
public static Encoding UTF8 { [__DynamicallyInvokable] get { if (Encoding.utf8Encoding == null) { Encoding.utf8Encoding = new UTF8Encoding(true); } return Encoding.utf8Encoding; } }而在MSDN中 UTF8Encoding 的建構子資訊中提到這個參數會影響到 GetPreamble 這個方法的行為。
在實測之中這個參數不只會影響GetPreamble這個方法的回傳值,也會影響到輸出的文字是否會含有BOM。
會寫這篇主要是這個問題說大不大,但若踩到的話也可以吃足苦頭,寫下來避免下次遇到時問題找個老半天,才發現是這個編碼問題。
2017.03.30補充:
關於UTF-8編碼的檔案是否要帶BOM的問題,在stackoverflow的這篇討論串還不錯~