none
使用MemoryStream,再用Encoding.UTF8.GetString(),出現看不見的字的問題 RRS feed

  • 問題

  • 程式碼如下:

    namespace TryEncoding
    {
        class Program
        {
            static void Main(string[] args)
            {            
                var entity = new Employee { Id = "joey" };
                var result = EntityToXml<Employee>(entity);
                Console.WriteLine(result);
    
            }
    
            /// <summary>
            /// Entitiy序列化成Xml,直接輸出Xml字串
            /// </summary>
            /// <typeparam name="T">Entity的型別</typeparam>
            /// <param name="entity">The entity.</param>
            /// <returns>Entity序列化完的Xml內容</returns>
            public static string EntityToXml<T>(T entity)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
    
                var stream = new MemoryStream();
                var ns = new XmlSerializerNamespaces();
                ns.Add(string.Empty, string.Empty);
    
                var writer = new XmlTextWriter(stream, Encoding.UTF8);
                serializer.Serialize(writer, entity, ns);
                var content = writer.BaseStream as MemoryStream;
    
                var result = Encoding.UTF8.GetString(content.ToArray());
                return result;
            }
        }
    
        public class Employee
        {
            public string Id { get; set; }
        }
    }
    


    當用監看式看的時候,看起來好像是正常的:

    但實際其實有個看不見的值,呈現時會以問號呈現:

     

    能請教一下原理跟解決的方式嗎?

    我後來是改採用StringWriter來取代MemoryStream的部分,但StringWriter預設的編碼是UTF-16,且Encoding屬性為唯讀,使用XmlWriterSetting也無法改變,因為它還是會以StringWriter為主。

    後來是直接新增一個class,繼承StringWriter,overrides Encode屬性,強迫return Encoding.UTF8。

     


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
    2011年10月13日 下午 03:47

解答

  • 如果你是要用 XML DOM Parser 去解它的話,是有一個迂迴的方法,就是把 XML 資料存到暫存檔,再用 XmlDocument.Load() 把檔讀入看看行不行。

    另外,與其用 StringWriter,不如用 StreamWriter,還可以指定編碼...


    小朱的技術隨手寫:http://www.dotblogs.com.tw/regionbbs/
    雲端學堂Facebook: http://www.facebook.com/studyazure
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 03:53
    版主
  • 會不會前面是 BOM ?

    用 16 進位輸出來看看~


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    提問時,錯誤情境描述與錯誤訊息很重要,情境描述包含你做了什麼,預期的結果與實際發生的結果。一個最爛的問法範例:「我的電腦電腦怎麼不能開機?」誰知道你家是不是沒電還是你根本找不到電源鈕。
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 04:29
  • 對耶,是BOM,改成這樣

    var writer = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false));

     

    補個連結上來

    http://blog.darkthread.net/post-2007-10-31-kb-utf8encoding-and-bom.aspx

    • 已編輯 阿尼 2011年10月13日 下午 04:56 補連結
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 04:50
  • 補充一下,其實有沒有加 BOM 都不至於影響 Serialization / Deserialization,因為如果你採用 .Net 既有的方法,有沒有加 BOM 都不會影響結果,如下例:

    string entity = "Johnny", result;
    
    byte[] serialized = null;
    using (MemoryStream stream = new MemoryStream())
    {
        using (XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8))
        //using (XmlTextWriter writer = new XmlTextWriter(stream, new System.Text.UTF8Encoding((false))))
        {
            XmlSerializer xs = new XmlSerializer(typeof(string));
            xs.Serialize(writer, entity);
            serialized = ((MemoryStream)writer.BaseStream).ToArray();
        }
    }
    
    using (MemoryStream stream = new MemoryStream(serialized))
    {
        using (XmlTextReader reader = new XmlTextReader(stream))
        {
            XmlSerializer xs1 = new XmlSerializer(typeof(string));
            result = (string)xs1.Deserialize(reader);
        }
    }
    

    如果是使用 FileStream 寫入檔案的話,不管有沒有加 BOM 都不會出現最前面的問號。

    所以只有當你必須把 serialized 的結果放在字串時才會發生這個問題。


    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 07:21

所有回覆

  • 如果你是要用 XML DOM Parser 去解它的話,是有一個迂迴的方法,就是把 XML 資料存到暫存檔,再用 XmlDocument.Load() 把檔讀入看看行不行。

    另外,與其用 StringWriter,不如用 StreamWriter,還可以指定編碼...


    小朱的技術隨手寫:http://www.dotblogs.com.tw/regionbbs/
    雲端學堂Facebook: http://www.facebook.com/studyazure
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 03:53
    版主
  • 會不會前面是 BOM ?

    用 16 進位輸出來看看~


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    提問時,錯誤情境描述與錯誤訊息很重要,情境描述包含你做了什麼,預期的結果與實際發生的結果。一個最爛的問法範例:「我的電腦電腦怎麼不能開機?」誰知道你家是不是沒電還是你根本找不到電源鈕。
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 04:29
  • 對耶,是BOM,改成這樣

    var writer = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false));

     

    補個連結上來

    http://blog.darkthread.net/post-2007-10-31-kb-utf8encoding-and-bom.aspx

    • 已編輯 阿尼 2011年10月13日 下午 04:56 補連結
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 04:50
  • 補充一下,其實有沒有加 BOM 都不至於影響 Serialization / Deserialization,因為如果你採用 .Net 既有的方法,有沒有加 BOM 都不會影響結果,如下例:

    string entity = "Johnny", result;
    
    byte[] serialized = null;
    using (MemoryStream stream = new MemoryStream())
    {
        using (XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8))
        //using (XmlTextWriter writer = new XmlTextWriter(stream, new System.Text.UTF8Encoding((false))))
        {
            XmlSerializer xs = new XmlSerializer(typeof(string));
            xs.Serialize(writer, entity);
            serialized = ((MemoryStream)writer.BaseStream).ToArray();
        }
    }
    
    using (MemoryStream stream = new MemoryStream(serialized))
    {
        using (XmlTextReader reader = new XmlTextReader(stream))
        {
            XmlSerializer xs1 = new XmlSerializer(typeof(string));
            result = (string)xs1.Deserialize(reader);
        }
    }
    

    如果是使用 FileStream 寫入檔案的話,不管有沒有加 BOM 都不會出現最前面的問號。

    所以只有當你必須把 serialized 的結果放在字串時才會發生這個問題。


    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    • 已標示為解答 91MVP 2011年10月14日 上午 01:56
    2011年10月13日 下午 07:21
  • 感謝上面幾位前輩的說明。

    1. 謝謝小朱,我的確有另外一個方法是寫xml檔到某個路徑。當時為了其他需求,所以想說是否可以做到不依賴外在檔案,透過memory來做。
      我會試著改用StreamWriter試試看是不是可以更乾淨的達到需求。
    2. 謝謝心冷大跟阿尼,讓我知道為啥會有個看不見得東西。
    3. 謝謝Johnny,的確我是需要 Serialization / Deserialization,這問題是發生在我的單元測試程式上,我的test case得自己給字串,然後再跟序列化的結果做比較。因為用兩個字串比較,所以產生了這個詭異的問題。

     

    感謝大家的解釋,又多學了一點了 :)


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
    2011年10月14日 上午 02:01
  • 其實如果只是做單元測試的話,那麼你或許可以做完 Serialization 之後馬上做 Deserialization,看看是不是相等,這樣可以節省做一個測試樣本的工夫。不過,當然你的類別必須有實做 == 運算子,但是可以順便再測試這個 == 運算子一次,摸蛤兼洗褲。
    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    2011年10月14日 上午 03:45
  • 因為class對外Serialize跟Deserialize兩個方法都有,所以在做Unit Test的時候,我不太習慣裡面用到兩個class的方法,才會分開獨立測試。

    一個input是string,output是Entity。

    一個input是entity,output是string。

    至於Unit Test的測試程式裡面,我也是偏向盡量不用原生的XmlSerializer 的方法,因為不想加邏輯進去。

    不想有機會造成:實際程式無誤,但測試程式寫錯(例如XmlSerializer用錯了),導致測試結果失敗。

    但還是很感謝Johnny大的建議,沒記錯的話,記得您在QA方面的經驗相當豐富,真的很感謝 :)


    常用資源參考:
    小弟的blog: In 91,wiki: my wiki
    2011年10月14日 上午 04:33
  • 我曾經有一段時間我寫的自訂類別都會實作 Serializer 跟 Deserializer,詳細原因我忘了,記得是因為這些自訂類別裡有些比較特別的欄位,使用 XmlSerializer 之後轉得出去卻轉不回來,只好通通自已做。而在測試的部份,我不曉得別人都怎麼測的,但是我每次提到測試資料很難做的時候,開發人員好像都不相信... 事實上測試資料真的很難做,Tester 跟 Developer 根本是在做攻防戰,不只是鬥智,還要花時間。不管做白箱還是黑箱,好的 Tester 會準備一大堆不同 pattern 的測試資料,才能測出最多的問題,而往往程式邏輯一改,資料就得全部重做。這就是為什麼我覺得測試項目能節省一個是一個的原因。

     


    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    2011年10月14日 上午 05:02