Подтвердить что ты не робот

Encoding.UTF8.GetString не учитывает преамбулу/спецификацию

В .NET я пытаюсь использовать метод Encoding.UTF8.GetString, который принимает массив байтов и преобразует его в string.

Похоже, что этот метод игнорирует спецификацию (байтовый порядок), который может быть частью законного двоичного представления строки UTF8, и принимает его как символ.

Я знаю, что я могу использовать TextReader для переваривания спецификации по мере необходимости, но я думал, что метод 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"));
}
4b9b3361

Ответ 1

Похоже, что этот метод игнорирует спецификацию (Byte Order Mark), которая может быть частью законного двоичного представления строки UTF8 и принимает ее как символ.

Не похоже, что он "игнорирует" его вообще - он добросовестно преобразует его в характер спецификации. Что это, в конце концов.

Если вы хотите, чтобы ваш код игнорировал спецификацию в любой строке, которую она преобразует, это вам делать или использовать StreamReader.

Обратите внимание, что если вы используете Encoding.GetBytes, а затем Encoding.GetString или используете StreamWriter, за которым следует StreamReader, обе формы будут либо производить, либо проглатывать, либо не создавать спецификацию. Это происходит только тогда, когда вы смешиваете с помощью StreamWriter (который использует Encoding.GetPreamble) с прямым вызовом Encoding.GetString, который заканчивается символом "extra".

Ответ 2

Основываясь на ответе Джона Скита (спасибо!), вот как я это сделал:

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

Обратите внимание, что это, вероятно, будет работать только надежно, если в массиве байтов, который вы читаете, есть спецификация. Если нет, вы можете захотеть заглянуть в другой конструктор конструктора StreamReader, который принимает параметр Encoding, чтобы вы могли рассказать ему, что содержит массив байтов.

Ответ 3

Я знаю, что я опаздываю на вечеринку, но здесь код, который я использую (не стесняйтесь приспосабливаться к С#), если вам нужно:

 Public Function Serialize(Of YourXMLClass)(ByVal obj As YourXMLClass,
                                                      Optional ByVal omitXMLDeclaration As Boolean = True,
                                                      Optional ByVal omitXMLNamespace As Boolean = True) As String

        Dim serializer As New XmlSerializer(obj.GetType)
        Using memStream As New MemoryStream()
            Dim settings As New XmlWriterSettings() With {
                    .Encoding = Encoding.UTF8,
                    .Indent = True,
                    .OmitXmlDeclaration = omitXMLDeclaration}

            Using writer As XmlWriter = XmlWriter.Create(memStream, settings)
                Dim xns As New XmlSerializerNamespaces
                If (omitXMLNamespace) Then xns.Add("", "")
                serializer.Serialize(writer, obj, xns)
            End Using

            Return Encoding.UTF8.GetString(memStream.ToArray())
        End Using
    End Function

 Public Function Deserialize(Of YourXMLClass)(ByVal obj As YourXMLClass, ByVal xml As String) As YourXMLClass
        Dim result As YourXMLClass
        Dim serializer As New XmlSerializer(GetType(YourXMLClass))

        Using memStream As New MemoryStream()
            Dim bytes As Byte() = Encoding.UTF8.GetBytes(xml.ToArray)
            memStream.Write(bytes, 0, bytes.Count)
            memStream.Seek(0, SeekOrigin.Begin)

            Using reader As XmlReader = XmlReader.Create(memStream)
                result = DirectCast(serializer.Deserialize(reader), YourXMLClass)
            End Using

        End Using
        Return result
    End Function

Ответ 4

для тех, кто не хочет использовать потоки, я нашел довольно простое решение с использованием 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);
    }
}