Нужна помощь в самом простом примере с protobuf-net 2 - PullRequest
3 голосов
/ 24 мая 2011

Обратите внимание на следующий код (взят из этого вопроса ):

  [ProtoContract]
  public class B
  {
    [ProtoMember(1)] public int Y;
  }

  [ProtoContract]
  public class C
  {
    [ProtoMember(1)] public int Y;
  }

  class Program
  {
    static void Main()
    {
      object b = new B { Y = 2 };
      object c = new C { Y = 4 };
      using (var ms = new MemoryStream())
      {
        Serializer.SerializeWithLengthPrefix(ms, b, PrefixStyle.Base128);
        Serializer.SerializeWithLengthPrefix(ms, c, PrefixStyle.Base128);
        ms.Position = 0;
        var b2 = Serializer.DeserializeWithLengthPrefix<B>(ms, PrefixStyle.Base128);
        Debug.Assert(((B)b).Y == b2.Y);
        var c2 = Serializer.DeserializeWithLengthPrefix<C>(ms, PrefixStyle.Base128);
        Debug.Assert(((C)c).Y == c2.Y);
      }
    }
  }

Очевидно, что код неправильный, потому что b и c объявлены как object, но я сериализую их, используя общий метод Serializer.Serialize<T>:

System.ArgumentOutOfRangeException occurred
  Message=Specified argument was out of the range of valid values.
Parameter name: index
  Source=protobuf-net
  ParamName=index
  StackTrace:
       at ProtoBuf.Meta.BasicList.Node.get_Item(Int32 index)
  InnerException: 

Все работает нормально, если я переименую b как B и c как C. Однако мне нужно, чтобы они были объявлены как object, поэтому я предполагаю, что мне нужно их сериализовать, используя неуниверсальный метод Serializer.NonGeneric.SerializeWithLengthPrefix, проблема, которую я не понимаю значения дополнительного аргумента fieldNumber, ожидаемого этим методом. Может кто-нибудь объяснить, что это такое и как мне использовать это здесь?

Я использую protobuf-net v2.

Спасибо.

1027 * EDIT *

Мне удалось заставить его работать с добавлением следующего кода:

RuntimeTypeModel.Default.Add(typeof(object), false).AddSubType(1, typeof(B)).AddSubType(2, typeof(C));

Хотя это работает, проблема в том, что мне нужно знать во время компиляции типы, используемые в сериализации (B = 1, C = 2), что плохо для меня. Есть ли лучший способ?

EDIT2

ОК, я изменил код следующим образом:

public class GenericSerializationHelper<T> : IGenericSerializationHelper
{
  void IGenericSerializationHelper.SerializeWithLengthPrefix(Stream stream, object obj, PrefixStyle prefixStyle)
  {
    Serializer.SerializeWithLengthPrefix(stream, (T)obj, prefixStyle);
  }
}

public interface IGenericSerializationHelper
{
  void SerializeWithLengthPrefix(Stream stream, object obj, PrefixStyle prefixStyle);
}

...

static void Main()
{
  var typeMap = new Dictionary<Type, IGenericSerializationHelper>();
  typeMap[typeof(B)] = new GenericSerializationHelper<B>();
  typeMap[typeof(C)] = new GenericSerializationHelper<C>();

  object b = new B { Y = 2 };
  object c = new C { Y = 4 };
  using (var ms = new MemoryStream())
  {
    typeMap[b.GetType()].SerializeWithLengthPrefix(ms, b, PrefixStyle.Base128);
    typeMap[c.GetType()].SerializeWithLengthPrefix(ms, c, PrefixStyle.Base128);
    ms.Position = 0;
    var b2 = Serializer.DeserializeWithLengthPrefix<B>(ms, PrefixStyle.Base128);
    Debug.Assert(((B)b).Y == b2.Y);
    var c2 = Serializer.DeserializeWithLengthPrefix<C>(ms, PrefixStyle.Base128);
    Debug.Assert(((C)c).Y == c2.Y);
  }
}

Теперь, чтобы сериализовать объекты, мне не нужно отображать время компиляции, я просто перехожу к соответствующему универсальному методу. Конечно, я понимаю, что в этой схеме я должен знать типы при десериализации, которая все еще облом.

1 Ответ

2 голосов
/ 24 мая 2011

Если вы сериализуете однородные данные, номер поля в значительной степени не имеет значения, с небольшим предостережением, что сохранение его равным 1 (он же Serializer.ListItemTag) будет тривиально, если вы хотите прочитать его обратно в виде списка (но довольно легков любом случае).

В случае разнородных данных, как в этом примере - для этой цели был разработан неуниверсальный API (фактически, в v2 all API не-generic - универсальный API просто пересылает в неуниверсальный).Передав TypeResolver, вы можете сказать ему на лету , как интерпретировать любой обнаруженный тег (в корне потока).Если вы решите вернуть null для данного тега, он предполагает, что вы не заинтересованы в этом объекте, и пропускает его полностью (в приведенном ниже примере он просто взорвется, очевидно - но это только потому, что пример кода минимален):

// I'm giving the example in terms of the v2 API, because there is a bug in the 
// Serializer.NonGeneric code in the beta - simply, in the first beta cut this
// doesn't correctly forward to the type-model. This will be fixed ASAP.
TypeModel model = RuntimeTypeModel.Default;
using (var ms = new MemoryStream())
{
    var tagToType = new Dictionary<int, Type>
    {  // somewhere, we need a registry of what field maps to what Type
        {1, typeof(B)}, {2, typeof(C)}
    };
    var typeToTag = tagToType.ToDictionary(pair => pair.Value, pair => pair.Key);

    object b = new B { Y = 2 };
    object c = new C { Y = 4 };
    // in v1, comparable to Serializer.NonGeneric.SerializeWithLengthPrefix(ms, b, PrefixStyle.Base128, typeToTag[b.GetType()]);
    model.SerializeWithLengthPrefix(ms, b, null, PrefixStyle.Base128, typeToTag[b.GetType()]);
    model.SerializeWithLengthPrefix(ms, c, null, PrefixStyle.Base128, typeToTag[c.GetType()]);
    ms.Position = 0;

    // in v1, comparable to Serializer.NonGeneric.TryDeserializeWithLengthPrefix(ms, PrefixStyle.Base128, key => tagToType[key], out b2);
    object b2 = model.DeserializeWithLengthPrefix(ms, null, null, PrefixStyle.Base128, 0, key => tagToType[key]);
    object c2 = model.DeserializeWithLengthPrefix(ms, null, null, PrefixStyle.Base128, 0, key => tagToType[key]);

    Assert.AreEqual(((B)b).Y, ((B)b2).Y);
    Assert.AreEqual(((C)c).Y, ((C)c2).Y);
}
...