В Protobuf-net, как я могу передать массив объектов типа с объектами разных типов внутри, заранее зная набор потенциальных типов - PullRequest
3 голосов
/ 21 апреля 2010

Я пытаюсь перенести существующий код, использующий 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 [], чтобы добавить их и получить их. Я должен использовать отражение при добавлении их (чтобы знать, в какой массив помещать каждый элемент), что нежелательно, и я все еще не могу сохранить порядок, поскольку я просто извлекаю все элементы в каждом списке по одному и помещаю их в массив new object [] для свойства get.

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


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

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

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

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

1 Ответ

4 голосов
/ 21 апреля 2010

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" предлагает гораздо больше возможностей для определения отношений во время выполнения, чем через атрибуты (что здесь довольно ограниченно).

...