.net Encoding.UTF8.GetString不';t考虑序言/BOM

.net Encoding.UTF8.GetString不';t考虑序言/BOM,.net,unicode,character-encoding,byte-order-mark,.net,Unicode,Character Encoding,Byte Order Mark,在.NET中,我尝试使用Encoding.UTF8.GetString方法,该方法获取字节数组并将其转换为字符串 此方法似乎忽略了,它可能是UTF8字符串的合法二进制表示形式的一部分,并将其作为字符 我知道我可以使用TextReader根据需要对BOM进行摘要,但我认为GetString方法应该是某种宏,可以使代码更短 我错过什么了吗?这是故意的吗 这是一个复制代码: static void Main(string[] args) { string s1 = "abc"; byt

在.NET中,我尝试使用
Encoding.UTF8.GetString
方法,该方法获取字节数组并将其转换为
字符串

此方法似乎忽略了,它可能是UTF8字符串的合法二进制表示形式的一部分,并将其作为字符

我知道我可以使用
TextReader
根据需要对BOM进行摘要,但我认为GetString方法应该是某种宏,可以使代码更短

我错过什么了吗?这是故意的吗

这是一个复制代码:

static void Main(string[] args)
{
    string s1 = "abc";
    byte[] abcWithBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(true)))
    {
        sw.Write(s1);
        sw.Flush();
        abcWithBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithBom)); // ef, bb, bf, 61, 62, 63
    }

    byte[] abcWithoutBom;
    using (var ms = new MemoryStream())
    using (var sw = new StreamWriter(ms, new UTF8Encoding(false)))
    {
        sw.Write(s1);
        sw.Flush();
        abcWithoutBom = ms.ToArray();
        Console.WriteLine(FormatArray(abcWithoutBom)); // 61, 62, 63
    }

    var restore1 = Encoding.UTF8.GetString(abcWithoutBom);
    Console.WriteLine(restore1.Length); // 3
    Console.WriteLine(restore1); // abc

    var restore2 = Encoding.UTF8.GetString(abcWithBom);
    Console.WriteLine(restore2.Length); // 4 (!)
    Console.WriteLine(restore2); // ?abc
}

private static string FormatArray(byte[] bytes1)
{
    return string.Join(", ", from b in bytes1 select b.ToString("x"));
}
此方法似乎忽略了BOM(字节顺序标记),它可能是UTF8字符串的合法二进制表示形式的一部分,并将其作为字符

它看起来一点也不“忽略”它——它忠实地将其转换为BOM字符。毕竟是这样的

如果你想让你的代码忽略它转换的任何字符串中的BOM表,那就由你来做。。。或者使用
StreamReader


请注意,如果使用
Encoding.GetBytes
后跟
Encoding.GetString
或使用
StreamWriter
后跟
StreamReader
,这两种表单都将生成然后吞并BOM表或不生成BOM表。只有当你混合使用
StreamWriter
(它使用
编码.GetPreamble
)和直接
编码.GetString
调用时,你才会得到“额外”字符。

根据Jon Skeet的回答(谢谢!),我就是这样做的:

var memoryStream = new MemoryStream(byteArray);
var s = new StreamReader(memoryStream).ReadToEnd();

请注意,只有在您正在读取的字节数组中存在BOM表时,此功能才可能可靠地工作。如果没有,您可能需要查看哪个接受了编码参数,这样您就可以告诉它字节数组包含什么。

我知道我参加聚会有点晚了,但如果您需要,下面是我正在使用的代码(请随意适应C#):

公共函数序列化(您的XmlClass)(ByVal obj作为您的XmlClass,
可选的ByVal omitXMLDeclaration为Boolean=True,
可选的ByVal省略XMLNamespace作为Boolean=True)作为字符串
Dim序列化程序作为新的XmlSerializer(obj.GetType)
将memStream用作新的MemoryStream()
将设置设置设置为新的XmlWriterSettings(){
.Encoding=Encoding.UTF8,
.Indent=True,
.omitXMLDeclaration=omitXMLDeclaration}
使用writer作为XmlWriter=XmlWriter.Create(memStream,设置)
Dim xns作为新的XmlSerializerNamespaces
如果是(省略XMLNamespace),那么xns.Add(“,”)
serializer.Serialize(writer、obj、xns)
终端使用
返回Encoding.UTF8.GetString(memStream.ToArray())
终端使用
端函数
公共函数反序列化(您的XmlClass)(ByVal obj作为您的XmlClass,ByVal xml作为字符串)作为您的XmlClass
将结果设置为您的XmlClass
Dim序列化程序作为新的XmlSerializer(GetType(YourXMLClass))
将memStream用作新的MemoryStream()
Dim bytes As Byte()=Encoding.UTF8.GetBytes(xml.ToArray)
memStream.Write(字节,0,字节.计数)
memStream.Seek(0,SeekOrigin.Begin)
将reader用作XmlReader=XmlReader.Create(memStream)
结果=DirectCast(序列化程序.反序列化(读取器),YourXMLClass)
终端使用
终端使用
返回结果
端函数

对于那些不想使用streams的人,我发现了一个使用Linq的非常简单的解决方案:

public static string GetStringExcludeBOMPreamble(this Encoding encoding, byte[] bytes)
{
    var preamble = encoding.GetPreamble();
    if (preamble?.Length > 0 && bytes.Length >= preamble.Length && bytes.Take(preamble.Length).SequenceEqual(preamble))
    {
        return encoding.GetString(bytes, preamble.Length, bytes.Length - preamble.Length);
    }
    else
    {
        return encoding.GetString(bytes);
    }
}

@RonKlein另外,您可以说
restore2=restore2.TrimStart('\uFEFF')
来删除BOM表的前导字符。我也曾经想知道为什么
(新的UTF8Encoding(true)).GetBytes(“abc”)和
(新的UTF8Encoding(false)).GetBytes(“abc”)
会产生相同的输出,但正如您现在可能知道的那样,
GetBytes
并不假定您位于文件的开头,因此它从不使用
GetPreamble
。如果使用
GetBytes
GetString
,则必须显式地
GetPreamble
,或者显式地跳过preamble。我认为您可能需要使用它来指定是否应该查找BOM以确定编码。