Можете ли вы использовать перечисления объектов с protobuf-net?

Имеем следующее..

public class Foo
{
   public string Name { get; private set;}

   private Foo(string name)
   {
      Name = name;
   }

   public Foo Instance1 = new Foo("Hello");
   public Foo Instance2 = new Foo("World");
}

И тогда, ссылаясь на это, у нас будет ..

[ProtoContract]
public class Bar 
{
   [ProtoMember(1)]
   public Foo Foo { get; private set; }

   public Bar(Foo foo)
   {
      Foo = foo;
   }
}

Поэтому, когда я десериализую Bar, мне нужно получить ссылку либо на Foo.Instance1, либо на Foo.Instance2..

Вопрос можно ли это сделать и если да то как?


person Shane Courtrille    schedule 27.10.2011    source источник


Ответы (1)


Есть несколько подходов к этому. самым простым было бы добавить свойство прокладки, т.е.

public Foo Foo { get; private set; }

[ProtoMember(1)]
private SomeBasicEnum FooSerialization {
   /* shim between Foo and SomeBasicEnum in get/set */
}

Однако, если у вас много Foo свойств, это может быть проблемой. Поэтому вместо этого v2 предлагает «суррогатные» типы, то есть, если он может найти оператор преобразования между двумя типами, он с радостью заменит их для вас автоматически. В этом случае мы хотели бы переключиться на перечисление, и, поскольку вы не можете добавлять операторы к перечислениям, вам нужно добавить оператор к Foo:

public static implicit operator Foo(FooSurrogate value)
{
    switch (value)
    {
        case FooSurrogate.Nil: return null;
        case FooSurrogate.Instance1: return Foo.Instance1;
        case FooSurrogate.Instance2: return Foo.Instance2;
        default: throw new InvalidEnumArgumentException("value");
    }
}
public static implicit operator FooSurrogate(Foo value)
{
    if (value == null) return FooSurrogate.Nil;
    if (value == Foo.Instance1) return FooSurrogate.Instance1;
    if (value == Foo.Instance2) return FooSurrogate.Instance2;
    throw new InvalidEnumArgumentException("value");
}

и где-то есть простое перечисление:

public enum FooSurrogate
{
    Nil, Instance1, Instance2
}

И настройте его (где-то при запуске приложения):

RuntimeTypeModel.Default.Add(typeof(Foo), false).SetSurrogate(
                   typeof(FooSurrogate));

И мы готовы идти. Также требуется незначительная настройка, потому что в Bar отсутствует конструктор без параметров; 2 варианта здесь:

  1. добавьте private Bar() {}, который он может использовать
  2. явно скажите ему не использовать конструктор: [ProtoContract(SkipConstructor = true)]

Добавьте тестовую установку:

static void Main()
{
    RuntimeTypeModel.Default.Add(typeof(Foo), false).SetSurrogate(
              typeof(FooSurrogate));
    var obj = new Bar(Foo.Instance1);
    var clone = Serializer.DeepClone(obj);

    bool same = ReferenceEquals(obj.Foo, clone.Foo);
    Debug.Assert(same); // passes
}

Вероятно, я мог бы также сделать возможным использование внешних методов, похожих на операторы, чтобы избежать необходимости иметь оператор на Foo, что немного уродливо.

Последним вариантом для меня было бы добавить поддержку IObjectReference, но, честно говоря (и особенно в этом случае), использование базового перечисления для реализации аккуратнее и эффективнее.

person Marc Gravell    schedule 28.10.2011
comment
Разве это не должно быть default: throw new InvalidEnumArgumentException("value", (int)value, typeof(FooSurrogate));? Подпись с одним аргументом ожидает сообщения об исключении (сообщение, отображаемое с этим исключением). - person vulcan raven; 18.12.2013