由一个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),字符值为65279。.NET Framework 2.0 String的Trim函数内部会去除一组预定义的空白字符集,其中就包括了65279。反编译代码如下(最后一个字符'\ufeff'是62579的16进制表示):

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

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

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

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

public static string TrimPreamble(this string value, Encoding encoding)
{
    if (String.IsNullOrEmpty(value))
    {
        return value;
    }

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

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

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

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

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

public static string TrimPreamble(this string value, Encoding encoding)
{
    if (String.IsNullOrEmpty(value))
    {
        return value;
    }

    var 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并重新定义它的构造函数。

public sealed class EncodingStringWriter : StringWriter
{
    private Encoding _encoding;

    public EncodingStringWriter(StringBuilder sb, Encoding encoding)
        : base(sb)
    {
        _encoding = encoding;
    }

    public override Encoding Encoding
    {
        get
        {
            return _encoding;
        }
    }
}
原文地址:https://www.cnblogs.com/junchu25/p/2761018.html