Перетворення потоку в рядок і назад ... що нам не вистачає?


162

Я хочу серіалізувати об'єкти до рядків і назад.

Ми використовуємо протобуф-мережу для успішного перетворення об'єкта в Потік і назад.

Однак Потік до струни і назад ... не такий успішний. Після проходження StreamToStringі StringToStream, нове Streamне десеріалізується протобуф-мережею; це породжує Arithmetic Operation resulted in an Overflowвиняток. Якщо ми дезаріалізуємо початковий потік, він працює.

Наші методи:

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);
}

Наш приклад код з використанням цих двох:

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);

1
не повинен Stream бути MemoryStrea?
Ehsan

Відповіді:


60

Це так часто, але так глибоко неправильно. Дані протобуфа - це не рядкові дані. Це, звичайно, не ASCII. Ви використовуєте кодування назад . Кодування тексту передає:

  • довільний рядок до форматованих байтів
  • відформатовані байти в початковий рядок

У вас немає "відформатованих байтів". У вас є довільні байти . Потрібно використовувати щось на зразок кодування base-n (зазвичай: base-64). Це передає

  • довільні байти до відформатованого рядка
  • відформатований рядок до початкових байтів

подивіться на Convert.ToBase64String та Convert. FromBase64String


1
Ви можете скористатися BinaryFormatterподібним до цього дивного прикладу ?
drzaus

@drzaus hm ... можливо, ні:> "Будь-які непарні сурогатні символи втрачаються при двійковій серіалізації"
drzaus

211

Я щойно це перевірив і працює чудово.

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();

Якщо streamце вже було написано, ви, можливо, захочете спробувати до початку, перш ніж прочитати текст:stream.Seek(0, SeekOrigin.Begin);


І не забувайте використовувати блок навколо читателя StreamReader = новий StreamReader (потік);
PRMan

7

перетворення UTF8 MemoryStream в String:

var res = Encoding.UTF8.GetString(stream.GetBuffer(), 0 , (int)stream.Length)

2
Використовуйте замість ToArray (). Буфер може перевищувати розмір використовуваних даних. ToArray () повертає копію даних правильного розміру. var array = stream.ToArray(); var str = Encoding.UTF8.GetString(array, 0, array.Length);. Дивіться також msdn.microsoft.com/en-us/library/…
Mortennobel

1
@Mortennobel ToArray()виділяє новий масив у пам'яті та копіює дані з буфера, що може мати серйозні наслідки, якщо ви маєте справу з великою кількістю даних.
Леві

Зверніть увагу на використання stream.Length, а не stream.GetBuffer (). І Леві правильно відзначив причину не використовувати ToArray ().
Вольфганг Грінфельд

5

Під час тестування спробуйте UTF8кодувати потік, як показано нижче

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


2

Я написав корисний метод для виклику будь-якої дії, яка виконує a, StreamWriterі записує її на рядок. Метод такий;

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);
    }
}

І ви можете використовувати це так;

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);

Я знаю, що це можна зробити набагато простіше з a StringBuilder, справа в тому, що ви можете викликати будь-який метод, який вимагає StreamWriter.


1

Я хочу серіалізувати об'єкти до рядків і назад.

Відмінні від інших відповідей, але найбільш простим способом зробити саме це для більшості типів об’єктів є 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
        }

Усі ваші загальнодоступні властивості підтримуваних типів будуть серіалізовані. Навіть деякі колекційні структури підтримуються, і вони тунелюються до властивостей суб-об'єкта. Ви можете контролювати, як працює серіалізація з атрибутами ваших властивостей.

Це працює не з усіма типами об'єктів, деякі типи даних не підтримуються для серіалізації, але в цілому це досить потужно, і вам не доведеться турбуватися про кодування.


0

У випадку, де ви хочете серіалізувати / десеріалізувати POCO, бібліотека JSON Newtonsoft справді хороша. Я використовую його для збереження POCO в SQL Server як рядки JSON у полі nvarchar. Застереження полягає в тому, що, оскільки не є справжньою де-серіалізацією, вона не збереже приватних / захищених членів та ієрархію класів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.