前言

會有這個文章主要是因為,有需要比對序列化後字串是否符合預期的情境,但是卻因為沒有注意到這點讓測試程式碼怎樣都過不了 Orz

這個問題坦白說,若沒有踩到還真不會特別去計較這件事。關於 BOM(位元組順序記號),就我的實務經驗來講是很少會使用到的資訊,尤其是最常用到的編碼方式為 UTF-8,一般來說我不會馬上想到這個方向。 而 UTF-8 with BOM 的檔案基本上跟 without BOM 用肉眼是看不出差異的,只會在使用游標左右移動時發現需要多按一下才會動,在精神不濟時會以為這些都是幻覺 XD。但若是用 Notepad++ 的話,可以開啟 16 進位的檢視,就能看到 UTF-8 BOM 的那該死的位元組「ef bb bf」,在 windows 平台上有時可以看到存檔時可以選擇是否要包含 BOM 這個選項。

實驗

XmlTextWriter 實體要 new 出來時會有幾種作法,其中我們要講的就是把 Encoding 方式傳入的那種。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SerializeHelper
{
    /// <summary>
    /// 序列化物件編碼
    /// 序列化後文字
    /// </summary>
    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 參入其中,看看是否真的多了那三個該死的位元標記符號。

在驗證的程式碼我們這樣寫:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 的那種。這點我們可以從原始碼中略窺一二

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 的這篇討論串還不錯~