由一个Xml序列化操作看mscorlib.dll 2.0、4.0 String的Trim函数实现

有一段Xml序列化的代码,基于2.0 Runtime传递到Server转换正常。当客户端在4.0 Runtime下调用,Server返回格式错误。序列化代码如下:

XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

using (MemoryStream memoryStream = new MemoryStream())
{
    XmlTextWriter xmlWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
    xmlSerializer.Serialize(xmlWriter, graph, ns);

    return Encoding.UTF8.GetString(memoryStream.ToArray()).Trim();
}

用Microsoft Network Monitor监视4.0 Runtime下发送的数据内容,转换后的Xml头部多出了3个字节:EF(239) BB(187) BF(191),它是UTF-8的preamble(encoding bites),字符值为62579。.NET Framework 2.0 String的Trim函数内部会去除一组预定义的空白字符集,其中就包括了62579。反编译代码如下(最后一个字符):

public string Trim()
{
    return this.TrimHelper(string.WhitespaceChars, 2);
}
string.WhitespaceChars = new char[]
{
    '\t',
    '\n',
    '\v',
    '\f',
    '\r',
    ' ',
    '\u0085',
    '\u00a0',
    '\u1680',
    '\u2000',
    '\u2001',
    '\u2002',
    '\u2003',
    '\u2004',
    '\u2005',
    '\u2006',
    '\u2007',
    '\u2008',
    '\u2009',
    '\u200a',
    '',
    '\u2028',
    '\u2029',
    '\u3000',
    ''
};

.NET Framework 4.0 String的Trim函数,反编译代码如下:

public string Trim()
{
    return this.TrimHelper(2);
}

4.0版本String的Trim函数内部不再去除一组预定义的空白字符集,直接导致了之前Xml序列化无法去除UTF-8编码后的preamble。解决方法就是根据不同字符编码尝试去除转换后的preamble。代码如下:

/// <summary>
/// 修正编码前言
/// </summary>
/// <param name="value">字符串</param>
/// <param name="encoding">编码</param>
/// <returns>修正字符串</returns>
public static String TrimPreamble(this String value, Encoding encoding)
{
    if (String.IsNullOrEmpty(value))
        return value;

    String encodingString = encoding.GetString(encoding.GetPreamble());

    if (value.Length <= encodingString.Length)
        return value;

    for (Int32 i = 0; i < encodingString.Length; ++i)
    {
        if (value[i] != encodingString[i])
            return value;
    }

    return value.Remove(0, encodingString.Length);
}

在我的实现版本并没有使用StartWith或IndexOf函数是有原因的,比如上面的函数可以简写成:

/// <summary>
/// 修正编码前言
/// </summary>
/// <param name="value">字符串</param>
/// <param name="encoding">编码</param>
/// <returns>修正字符串</returns>
public static String TrimPreamble(this String value, Encoding encoding)
{
    if (String.IsNullOrEmpty(value))
        return value;

    String encodingString = encoding.GetString(encoding.GetPreamble());

    if (value.StartsWith(encodingString))
        value = value.Remove(0, encodingString.Length);

    return value;
}

当客户端基于.NET Framework 2.0,发送字符串到基于.NET Framework 4.0运行的Server,preamble的偏移在实际字符的-1位。StartWith、IndexOf函数的匹配并不是针对String实际可见的char array逐一匹配,由.NET平台决定字符串在内存的存储方式,它们会返回字符已存在,但并不存在于可见的char array中。具体原因可以查看System.Globalization.CompareInfo.InternalFindNLSStringEx函数。

不使用XmlTextWriter可以避免encoding bites,比如:StringWriter。其缺点是无法指定Encoding,但可以继承StringWriter并重新定义它的构造函数。

/// <summary>
/// 编码字符写入器
/// </summary>
public sealed class EncodingStringWriter : StringWriter
{
    private Encoding _encoding;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="sb">字符建造器</param>
    /// <param name="encoding">编码</param>
    public EncodingStringWriter(StringBuilder sb, Encoding encoding)
        : base(sb)
    {
        _encoding = encoding;
    }

    /// <summary>
    /// 编码
    /// </summary>
    public override Encoding Encoding
    {
        get
        {
            return _encoding;
        }
    }
}

你可能感兴趣的:(String)