Вложенные объекты с Бондом

Я пытаюсь использовать Microsoft Bond для сериализации вложенных объектов. Но Бонд выдает внутренние ошибки (например, KeyNotFoundException).

Мои занятия:

interface IFoo
{
}

[Bond.Schema]
class Foo1 : IFoo
{
     [Bond.Id(0)]
     public string Foo1Field { get; set; }
}

[Bond.Schema]
class Bar
{
     [Bond.Id(0)]
     public IFoo SomeFooInstance { get; set; }
}

Затем я создаю экземпляр и сериализую:

var src = new Bar() { SomeFooInstance = new Foo1() { Foo1Field = "Str" }};

var output = new OutputBuffer();
var writer = new CompactBinaryWriter<OutputBuffer>(output);

Serialize.To(writer, src);

var input = new InputBuffer(output.Data);
var reader = new CompactBinaryReader<InputBuffer>(input);

var dst = Deserialize<Bar>.From(reader);

Но я получаю исключения (такие KeyNotFoundException) в Serialize.To(writer, src);,

Я тоже пытался добавить [Bond.Schema] в IFoo, но тогда Deserialize<Bar>.From(reader); не удается...

Как я могу сериализовать Bar класс, который содержит некоторые Foo класс с Бондом, не получая исключений, как это?

1 ответ

Решение

Если вы хотите использовать интерфейсы с поведенческими различиями (а не структурными различиями), хитрость заключается в том, чтобы предоставить десериализатору фабрику, чтобы он знал, как создать конкретный экземпляр IFoo когда это нужно. Обратите внимание, что реализации не имеют [Bond.Schema] атрибут, так как они оба реализуют IFoo схемы.

namespace NS
{       
    using System;
    using Bond.IO.Unsafe;
    using Bond.Protocols;

    internal static class Program
    {
        [Bond.Schema]
        interface IFoo
        {
            [Bond.Id(10)]
            string FooField { get; set; }
        }

        [Bond.Schema]
        class Bar
        {
            [Bond.Id(20)]
            public IFoo SomeFooInstance { get; set; }
        }

        class AlwaysUppercaseFoo : IFoo
        {
            private string fooField;

            public string FooField
            {
                get
                {
                    return fooField;
                }

                set
                {
                    fooField = value.ToUpperInvariant();
                }
            }
        }

        class IdentityFoo : IFoo
        {
            public string FooField { get; set; }
        }

        public static Expression NewAlwaysUppercaseFoo(Type type, Type schemaType, params Expression[] arguments)
        {
            if (schemaType == typeof(IFoo))
            {
                return Expression.New(typeof(AlwaysUppercaseFoo));
            }

            // tell Bond we don't handle the requested type, so it should use it's default behavior
            return null;
        }

        public static Expression NewIdentityFoo(Type type, Type schemaType, params Expression[] arguments)
        {
            if (schemaType == typeof(IFoo))
            {
                return Expression.New(typeof(IdentityFoo));
            }

            // tell Bond we don't handle the requested type, so it should use it's default behavior
            return null;
        }

        public static void Main(string[] args)
        {
            var src = new Bar() { SomeFooInstance = new IdentityFoo() { FooField = "Str" } };

            var output = new OutputBuffer();
            var writer = new CompactBinaryWriter<OutputBuffer>(output);

            Bond.Serialize.To(writer, src);

            {
                var input = new InputBuffer(output.Data);
                var deserializer = new Bond.Deserializer<CompactBinaryReader<InputBuffer>>(typeof(Bar), NewAlwaysUppercaseFoo);
                var reader = new CompactBinaryReader<InputBuffer>(input);
                var dst = deserializer.Deserialize<Bar>(reader);
                Debug.Assert(dst.SomeFooInstance.FooField == "STR");
            }

            {
                var input = new InputBuffer(output.Data);
                var deserializer = new Bond.Deserializer<CompactBinaryReader<InputBuffer>>(typeof(Bar), NewIdentityFoo);
                var reader = new CompactBinaryReader<InputBuffer>(input);
                var dst = deserializer.Deserialize<Bar>(reader);
                Debug.Assert(dst.SomeFooInstance.FooField == "Str");
            }
        }
    }
}

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

Я бы показал пример этого, но при написании этого ответа на 2018-02-21, я обнаружил ошибку в обработке классов с [Bond.Schema] которые реализуют интерфейсы с [Bond.Schema]: поля из интерфейса опущены.

На данный момент, обходной путь должен был бы использовать наследование с классами и использовать виртуальные свойства. Например:

namespace NS
{
    using System;
    using Bond.IO.Unsafe;
    using Bond.Protocols;

    internal static class Program
    {
        enum FooKind
        {
            Unknown = 0,
            AlwaysUppercase = 1,
            Identity = 2,
        }

        // intentionally a class to work around https://github.com/Microsoft/bond/issues/801 but simulate an interface somewhat
        [Bond.Schema]
        class IFoo
        {
            [Bond.Id(0)]
            public virtual string FooField { get; set; }
        }

        [Bond.Schema]
        class Bar
        {
            [Bond.Id(0)]
            public Bond.IBonded<IFoo> SomeFooInstance { get; set; }

            [Bond.Id(1)]
            public FooKind Kind { get; set; }
        }

        [Bond.Schema]
        class AlwaysUppercaseFoo : IFoo
        {
            private string fooField;

            public override string FooField
            {
                get
                {
                    return fooField;
                }

                set
                {
                    fooField = value.ToUpperInvariant();
                }
            }

            [Bond.Id(0)]
            public string JustAlwaysUppercaseFooField { get; set; }
        }

        [Bond.Schema]
        class IdentityFoo : IFoo
        {
            [Bond.Id(42)]
            public string JustIdentityFooField { get; set; }
        }

        static void RoundTripAndPrint(Bar src)
        {
            var output = new OutputBuffer();
            var writer = new CompactBinaryWriter<OutputBuffer>(output);

            Bond.Serialize.To(writer, src);

            var input = new InputBuffer(output.Data);
            var reader = new CompactBinaryReader<InputBuffer>(input);
            var dst = Bond.Deserialize<Bar>.From(reader);

            switch (dst.Kind)
            {
                case FooKind.Identity:
                    {
                        var fooId = dst.SomeFooInstance.Deserialize<IdentityFoo>();
                        Console.WriteLine($"IdFoo: \"{fooId.FooField}\", \"{fooId.JustIdentityFooField}\"");
                    }
                    break;

                case FooKind.AlwaysUppercase:
                    {
                        var fooUc = dst.SomeFooInstance.Deserialize<AlwaysUppercaseFoo>();
                        Console.WriteLine($"UcFoo: \"{fooUc.FooField}\", \"{fooUc.JustAlwaysUppercaseFooField}\"");
                    }
                    break;

                default:
                    Console.WriteLine($"Unknown Kind: {dst.Kind}");
                    break;
            }
        }

        public static void Main(string[] args)
        {
            var o = new OutputBuffer();
            var w = new CompactBinaryWriter<OutputBuffer>(o);
            Bond.Serialize.To(w, new IdentityFoo() { FooField = "Str", JustIdentityFooField = "id" });

            var src_id = new Bar()
            {
                SomeFooInstance = new Bond.Bonded<IdentityFoo>(new IdentityFoo() { FooField = "Str", JustIdentityFooField = "id" }),
                Kind = FooKind.Identity
            };

            var src_uc = new Bar()
            {
                SomeFooInstance = new Bond.Bonded<AlwaysUppercaseFoo>(new AlwaysUppercaseFoo() { FooField = "Str", JustAlwaysUppercaseFooField = "I LIKE TO YELL!" }),
                Kind = FooKind.AlwaysUppercase
            };

            RoundTripAndPrint(src_id);
            RoundTripAndPrint(src_uc);
        }
    }
}

Это печатает:

IdFoo: "Str", "id"
UcFoo: "STR", "I LIKE TO YELL!"
Другие вопросы по тегам