Как в Protobuf-net передать массив объекта типа с объектами разных типов внутри, заранее зная набор потенциальных типов

Я пытаюсь перенести существующий код, который использует XmlSerializer, в protobuf-net из-за повышенной производительности, которую он предлагает, однако у меня возникают проблемы с этим конкретным случаем.

У меня есть объект [], который включает в себя параметры, которые будут отправлены на удаленный хост (своего рода специальное средство мини-RPC). Я знаю набор типов, из которых могут быть эти параметры, но не могу заранее сказать, в каком порядке они будут отправляться. У меня есть три ограничения. Во-первых, я работаю в Compact Framework, поэтому мне нужно что-то, что там работает. Во-вторых, как я уже упоминал, производительность является большой проблемой (со стороны сериализации), поэтому я бы предпочел по возможности избегать использования большого количества отражений. И самое главное, что меня волнует порядок, в котором эти параметры были отправлены. Используя XmlSerializer, было легко просто добавить XmlInclude, но для полей, насколько мне известно, в Protobuf-net нет ничего эквивалентного. Итак, есть ли способ сделать это? Вот упрощенный пример.

    [Serializable]
    [XmlInclude(typeof(MyType1)),
     XmlInclude(typeof(MyType2)),
     XmlInclude(typeof(MyType3))
    public class Message()
    {
         public object[] parameters;

         public Message(object[] parms)
         {
             parameters = parms; 
         }
    }

    Message m = new Message(new object[] {MyType1(), 33, "test", 
                new MyType3(), new MyType3()});
    MemoryStream ms = new MemoryStream();
    XmlSerializer xml = new XmlSerializer(typeof(Message));
    xml.Serialize(ms,xml);

Это будет работать только с XmlSerializer, но если я попытаюсь преобразовать его в protobuf-net, я получу сообщение «Нет кодировки по умолчанию для объекта».

Лучшее, что я придумал, это использовать дженерики и [ProtoInclude], как показано в этом пример. Поскольку у меня могут быть разные типы объектов в массиве, это не совсем так. Я добавил общий список для каждого потенциального типа и свойство с [ProtoIgnore] с типом object[], чтобы добавить и получить их. Я должен использовать отражение при их добавлении (чтобы знать, в какой массив поместить каждый элемент), что нежелательно, и я все еще не могу сохранить порядок, поскольку я просто извлекаю все элементы из каждого списка один за другим и помещаю их в новый массив object[] в свойстве get.

Интересно, есть ли способ сделать это?


Я попробовал то, что Марк предложил ниже, но не смог заставить его работать. Я думаю, что я мог что-то неправильно понять.

Используя код, который вы написали. Я подумал, что должен использовать MessageParam Create для создания объектов MessageParam для добавления в список. Итак, в основном я добавил конструктор в Message следующим образом:

public Message(object[] parms)
{
    foreach (object o in parms)
    {
        parameters.Add(MessageParam.Create(o));
    }
}

Но если я это сделаю, я получу «Неожиданный тип, обнаруженный во время сериализации; типы должны быть включены в ProtoIncludeAttribute; найден MessageParam`1, переданный как MessageParam», потому что я предполагаю, что сериализатор ожидает неуниверсальную версию. Я неправильно понял ваше предложение? Если да, то как правильно поступить?


person cloudraven    schedule 20.04.2010    source источник
comment
(в качестве примечания, неизданная версия 2 изменяет конкретно адресные ограничения в компактной среде; поэтому, если она лучше сейчас, она должна работать, когда v2 стабильна и выпущена)   -  person Marc Gravell    schedule 21.04.2010
comment
Каждый параметр должен быть правильно введен; Я добавил CreateDynamic, который сделает эту работу за вас.   -  person Marc Gravell    schedule 21.04.2010


Ответы (1)


object будет проблематично. Я бы попробовал что-то вроде:

[ProtoContract]
class Message
{
    private readonly List<MessageParam> parameters = new List<MessageParam>();
    [ProtoMember(1)]
    public List<MessageParam> Parameters { get { return parameters; } }
}
[ProtoContract]
[ProtoInclude(3, typeof(MessageParam<int>))]
[ProtoInclude(4, typeof(MessageParam<float>))]
[ProtoInclude(5, typeof(MessageParam<DateTime>))]
//...known types...
abstract class MessageParam {
    public abstract object UntypedValue { get; set; }
    public static MessageParam<T> Create<T>(T value) {
        return new MessageParam<T> { Value = value };
    }
    public static MessageParam CreateDynamic(object value)
    {
        Type type = value.GetType();
        switch (Type.GetTypeCode(value.GetType()))
        {
            // special cases
            case TypeCode.Int32: return Create((int)value);
            case TypeCode.Single: return Create((float)value);
            case TypeCode.DateTime: return Create((DateTime)value);
            // fallback in case we forget to add one, or it isn't a TypeCode
            default:
                MessageParam param = (MessageParam)Activator.CreateInstance(
                    typeof(MessageParam<>).MakeGenericType(type));
                param.UntypedValue = value;
                return param;
        }
    }
}
[ProtoContract]
sealed class MessageParam<T> : MessageParam
{
    [ProtoMember(1)]
    public T Value { get; set; }
    public override object UntypedValue
    {
        get { return Value; }
        set { Value = (T)value; }
    }
}

Обратите внимание, что невыпущенный код «v2» предлагает гораздо больше возможностей для определения отношений во время выполнения, а не через атрибуты (что здесь весьма ограничивает).

person Marc Gravell    schedule 20.04.2010
comment
Спасибо, Марк! Это выглядит довольно хорошо. Я вижу, куда это идет, но я не могу заставить это работать. Я обновил свой вопрос, чтобы показать, что я сделал. Возможно, я что-то неправильно понял. О, и я был бы очень рад попробовать v2 на CF, когда он выйдет! Это отличный материал! - person cloudraven; 21.04.2010
comment
@mark-gravell Это решение все еще актуально или сейчас доступны лучшие решения? - person Mohammad Jafar Mashhadi; 23.08.2014
comment
@MJafar По сути, в формате проводов не хватает мест для ввода дополнительных метаданных. В версии 2 есть еще один вариант, но, честно говоря, вышеописанный вариант является предпочтительным и более надежным. - person Marc Gravell; 23.08.2014