Convertendo Stream em String e vice-versa ... o que estamos perdendo?

162

Eu quero serializar objetos em strings e voltar.

Usamos o protobuf-net para transformar um objeto em um fluxo e retornar com êxito.

No entanto, transmitir para string e voltar ... não é tão bem-sucedido. Depois de passar por StreamToStringe StringToStream, o novo Streamnão é desserializado pelo protobuf-net; isso gera uma Arithmetic Operation resulted in an Overflowexceção. Se desserializarmos o fluxo original, ele funcionará.

Nossos métodos:

public static string StreamToString(Stream stream)
{
    stream.Position = 0;
    using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
    {
        return reader.ReadToEnd();
    }
}

public static Stream StringToStream(string src)
{
    byte[] byteArray = Encoding.UTF8.GetBytes(src);
    return new MemoryStream(byteArray);
}

Nosso exemplo de código usando estes dois:

MemoryStream stream = new MemoryStream();
Serializer.Serialize<SuperExample>(stream, test);
stream.Position = 0;
string strout = StreamToString(stream);
MemoryStream result = (MemoryStream)StringToStream(strout);
var other = Serializer.Deserialize<SuperExample>(result);
flipuhdelphia
fonte
1
Stream não deve ser MemoryStrea?
Ehsan

Respostas:

60

Isso é tão comum, mas profundamente errado. Dados do Protobuf não são dados de sequência. Certamente não é ASCII. Você está usando a codificação para trás . Uma codificação de texto é transferida:

  • uma sequência arbitrária para bytes formatados
  • bytes formatados para a sequência original

Você não possui "bytes formatados". Você tem bytes arbitrários . Você precisa usar algo como uma codificação base-n (geralmente: base-64). Isso transfere

  • bytes arbitrários em uma sequência de caracteres formatada
  • uma sequência formatada para os bytes originais

veja Convert.ToBase64String e Convert. FromBase64String

Marc Gravell
fonte
1
você poderia usar um BinaryFormatter, semelhante a este exemplo estranho ?
drzaus
@drzaus hm ... talvez não:> "Quaisquer caracteres substitutos não emparelhados são perdidos na serialização binária"
drzaus
210

Acabei de testar isso e funciona bem.

string test = "Testing 1-2-3";

// convert string to stream
byte[] byteArray = Encoding.ASCII.GetBytes(test);
MemoryStream stream = new MemoryStream(byteArray);

// convert stream to string
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();

Se streamjá foi gravado, você pode procurar o começo antes da leitura antes de ler o texto:stream.Seek(0, SeekOrigin.Begin);

Ehsan
fonte
E não se esqueça de usar um bloco ao redor do StreamReader reader = new StreamReader (stream);
PRMan 12/02
7

uma conversão UTF8 MemoryStream para String:

var res = Encoding.UTF8.GetString(stream.GetBuffer(), 0 , (int)stream.Length)
Wolfgang Grinfeld
fonte
2
Use ToArray () em vez disso. O buffer pode ser maior que o tamanho dos dados usados. ToArray () retorna uma cópia dos dados com o tamanho correto. var array = stream.ToArray(); var str = Encoding.UTF8.GetString(array, 0, array.Length);. Consulte também msdn.microsoft.com/en-us/library/…
Mortennobel
1
O @Mortennobel ToArray()aloca uma nova matriz na memória e copia os dados do buffer, o que pode ter sérias implicações se você estiver lidando com muitos dados.
Levi Botelho
Observe o uso de stream.Length, em vez de stream.GetBuffer (). Length. E Levi anotou corretamente o motivo de não usar o ToArray ().
Wolfgang Grinfeld
5

Ao testar, tente com o UTF8codificar stream como abaixo

var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
Serializer.Serialize<SuperExample>(streamWriter, test);
Damith
fonte
5

Tente isso.

string output1 = Encoding.ASCII.GetString(byteArray, 0, byteArray.Length)
user3588327
fonte
2

Eu escrevi um método útil para chamar qualquer ação que receba um StreamWritere escrevê-lo em uma string. O método é assim;

static void SendStreamToString(Action<StreamWriter> action, out string destination)
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream, Encoding.Unicode))
    {
        action(writer);
        writer.Flush();
        stream.Position = 0;
        destination = Encoding.Unicode.GetString(stream.GetBuffer(), 0, (int)stream.Length);
    }
}

E você pode usá-lo assim;

string myString;

SendStreamToString(writer =>
{
    var ints = new List<int> {1, 2, 3};
    writer.WriteLine("My ints");
    foreach (var integer in ints)
    {
        writer.WriteLine(integer);
    }
}, out myString);

Eu sei que isso pode ser feito muito mais fácil com a StringBuilder, o ponto é que você pode chamar qualquer método que use a StreamWriter.

Steztric
fonte
1

Eu quero serializar objetos em strings e voltar.

Diferente das outras respostas, mas a maneira mais direta de fazer exatamente isso para a maioria dos tipos de objetos é o XmlSerializer:

        Subject subject = new Subject();
        XmlSerializer serializer = new XmlSerializer(typeof(Subject));
        using (Stream stream = new MemoryStream())
        {
            serializer.Serialize(stream, subject);
            // do something with stream
            Subject subject2 = (Subject)serializer.Deserialize(stream);
            // do something with subject2
        }

Todas as suas propriedades públicas dos tipos suportados serão serializadas. Até algumas estruturas de coleção são suportadas e serão direcionadas para as propriedades do subobjeto. Você pode controlar como a serialização funciona com atributos em suas propriedades.

Isso não funciona com todos os tipos de objetos, alguns tipos de dados não são suportados para serialização, mas no geral é bastante poderoso e você não precisa se preocupar com a codificação.

Denise Skidmore
fonte
0

No caso em que você deseja serializar / desserializar os POCOs, a biblioteca JSON da Newtonsoft é realmente boa. Eu o uso para persistir POCOs no SQL Server como seqüências JSON em um campo nvarchar. Advertência é que, uma vez que não é verdadeira de / serialização, ele não preservará membros privados / protegidos e hierarquia de classes.

MD Luffy
fonte