Двоичная сериализация в C # (действительно, WYSIWYG сериализация) - PullRequest
2 голосов
/ 18 июня 2011

(для WYSIWYG я имею в виду, что I решают ЧТО написано и КАК написано, а не кто-то в Microsoft или в Google) (ОК ..Технически я ничего не решаю ... Кто-то, кто программировал несколько лет назад, решил, и я могу только спросить, насколько высоко я должен прыгнуть)

Сегодня я чувствую себя немного глупо, но я ужепотерял два часа в поисках решения :-(.

Сейчас ...

У меня есть двоичный протокол. Он основан на C, так что это похоже на просмотр структуры C, в которой порядковый номермашина определена (и, к счастью, это то же самое, что и «локальный» порядок байтов), определены размеры различных типов, определено выравнивание структуры данных, определена структура структуры, строки представляют собой фиксированные массивы символов взнать кодировку ... Все определено! И все очень похоже на C # unsafe struct, когда вы используете [(LayoutKind.Explicit)], и вы не очень разборчивы в использовании модификатора fixed для массивов. Теперь мне нужно сериализовать / десериализоватьэто яn C # ... Я оглянулся, но ничего не смог найти ... Что я пропустил?Прежде чем вы спросите, я знаю о BinaryFormatter, но мне не хватает WYSIWYG ... BinaryFormatter реализует язык форматирования.Да, я знаю о BitConverter (и о том факте, что он не реализует конвертеры с прямым порядком байтов), но это не "полное" решение.Это всего лишь «базовый» инструмент.И я знаю о BinaryWriter / BinaryReader, но, похоже, они не поддерживают массивы, которые не byte[] или char[], и, кажется, они не способны "дополнить" массив при записи (у вас есть массив из 5 элементов byte[], и вам нужно записать его как массив из 10 элементов byte[], потому что формат, который вы используете, требует этого ... Вы должны написать строки кода для этого)

* 1026План B (но, возможно, даже План Z) состоит в создании тени unsafe struct для каждого класса, интерфейса IWysiwygSerializable с двумя методами (Read и Write) и реализации интерфейса в каждом классе (запись будетзаполнить unsafe struct и записать его в выходной поток, чтение будет делать противоположное) (или я мог бы даже сделать несколько десятков BitConverter в Read и Write без использования struct, нодля массивов это немного сложнее)

Спасибо!

Ответы [ 3 ]

3 голосов
/ 08 марта 2013

Я написал довольно простую, но расширяемую среду для декларативной двоичной сериализации.Я широко использовал его в своей работе и всегда обнаруживал, что это экономит огромное количество усилий:

binaryserializer.com

2 голосов
/ 18 июня 2011

Не используйте BinaryFormatter. Вместо этого используйте BinaryWriter и BinaryReader для записи точных байтов на диск, которые вы хотите записать на диск, в точном порядке, который вы хотите. Если массивы обрабатываются не так, как вам нравится, то вам просто придется самим проходить по массиву. Чтобы это выглядело чище, вы, возможно, могли бы написать метод расширения для выполнения цикла.

0 голосов
/ 01 сентября 2012

(обратите внимание, что это, возможно, можно считать рекламой, но «продукт» имеет лицензию MIT с открытым исходным кодом, и даже другой упомянутый «продукт» имеет лицензию MIT с открытым исходным кодом) (обратите внимание, что я являюсь автором рекламируемого »продукта"и других упомянутых" product ")

Не было никакого" хорошего "решения, поэтому я сделал свое :-) Мне пришлось создать библиотеку только для создания библиотеки: FluentSerializer .Библиотека может использоваться для создания «описания» того, как вы хотите, чтобы ваши двоичные данные были сериализованы.Это описание написано в свободной записи.Вы можете (через другую библиотеку, которую я написал, FluentStatement ) включить в ваше свободное владение описанием все обычные высказывания, такие как while, if, for ... (ясно, даже используя свободное владение)обозначения).Ваше описание затем компилируется как дерево выражений, а затем как группа динамических методов (сериализация, десериализация и размер (сериализованных данных)).

Небольшой образец сериализатора для тестового класса

/// <summary>
/// 
/// </summary>
public class Serializer : ISerializer<MyClass, EmptyParameters>
{
    #region ISerializer<MyClass,EmptyParameters> Members

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public Expression<Serializer<MyClass, EmptyParameters>> GetSerializer()
    {
        return (op, obj, par) => Statement.Start(fl => fl
            .Serialize(obj.Version)

            // Static objects can be serialized/deserialized.
            .Serialize(MyClass.StaticReadonlyInts1, typeof(FixedLength<>))

            // So can readonly collections.
            .Serialize(obj.ReadonlyInts1, typeof(FixedLength<>))

            // Both array and List<> (and Dictionary<,>, and SortedDictionary<,>, and
            // many other types of collections)
            ////.Serialize(obj.ReadonlyList1)
            .Serialize(obj.ReadonlyList1, typeof(VariableLengthByte<>))

            ////// Readonly fields can be serialized/deserialized.
            ////// Sadly you can't Dump() serializers that replace read only fields
            ////// (replace is the keyword here, readonly int X is a no-no, 
            ////// readonly List<int> X is a yes, readonly int[] X is a yes if it's 
            ////// FixedLength<>.
            ////.Serialize(obj.ReadonlyInt1)

            .Serialize(obj.Bool1)
            .Serialize(obj.Int2)

            // This will be serialized/deserialized only if obj.Version != 0
            // It's only an example of what you can do. You can use the full power of
            // FluentStatement, and remember that if instead of EmptyParameters you
            // had used another class as the parameters, you could have manipulated it
            // through the par object, so par.Version for example.
            .If(obj.Version != 0, fl.Serialize(obj.Int3))

            // This if(s) depend on the operation that is being done
            // (serialization/deserialization/size)
            .If(op == Operation.Serialize, fl.Serialize(obj.Int2))
            .If(op == Operation.Deserialize, fl.Serialize(obj.Int3))

            .Serialize(obj.Short1)

            // Tuples are supported.
            .Serialize(obj.Tuple1)

            // Arrays need to have the length prepended. There are helpers for this.
            // The default helper can be specified in the Serializer<T> constructor and
            // will be used if the field serializer isn't specified.
            ////.Serialize(obj.Ints1)

            // Or you can specify it:
            .Serialize(obj.Ints2, typeof(VariableLengthByte<>))
            .Serialize(obj.Ints3, typeof(VariableLengthByte<int[]>))

            // Nullable types are supported
            .Serialize(obj.NullableInt1, typeof(Nullable<int>))
            ////.Serialize(obj.NullableInt2)

            // But note that you could even use the Optional<> with value types,
            // usefull for example if you have to use a modifier that is a class
            // (like VariableLengthInt32 for example)
            .Serialize(obj.NullableInt1, typeof(Optional<int>))
            .Serialize(obj.NullableInt2, typeof(Optional<>))

            // So are "optional" objects (fields that can be null)
            // (Note that here if we wanted to specify the helper, we would have
            // to use typeof(Optional<VariableLengthByte<int>>)
            .Serialize(obj.OptionalInts1, typeof(Optional<VariableLengthInt32<int[]>>))
            .Serialize(obj.OptionalInts2, typeof(Optional<>))
            .Serialize(obj.OptionalList1, typeof(Optional<VariableLengthInt32<List<int>>>))
            .Serialize(obj.OptionalList2, typeof(Optional<>))

            // You can serialize a DateTime as the full .NET value
            .Serialize(obj.DateTime1)

            // Or, for example, as an Unix datetime (32 or 64 bits)
            .Serialize(obj.DateTime2, typeof(UnixDateTime<int>))

            .Serialize(obj.Float1)
            .Serialize(obj.Double1)
            .Serialize(obj.Decimal1)
            .Serialize(obj.TimeSpan1)

            // For strings it's a little more complex. There are too many combinations 
            // of possible formats (encoding x string length * (use char or byte length))
            // At this time there isn't any helper for C strings (null terminated strings).
            // You have to "manually" register you string formats.
            .Serialize(obj.String1, typeof(Program.MyUtf8VariableLengthInt32String))
            .Serialize(obj.String2, typeof(Program.MyAsciiVariableLengthInt32String))
            .Serialize(obj.String3, typeof(Program.MyUnicodeVariableLengthInt32String))

            // Chain serializing the base class can be done in this way
            .Serialize(obj, typeof(MySimpleClass))

            // This is only to make it easy to add new serialization fields. The last ) is
            // "attached" to the .Empty and doesn't need to be moved.
            .Empty());
    }

    #endregion
}

Очевидно, что эта библиотека хороша, только если вам нужно сериализовать / десериализовать много данных.Если у вас есть только один объект для сериализации / десериализации, вам, вероятно, будет достаточно BinaryReader / BinaryWriter (как я предположил в первоначальном вопросе и Фантиус в своем ответе).

...