Использование типа bool в Proto-Buf - PullRequest
2 голосов
/ 01 июля 2010

Я использую protobuf-net в своем приложении для сериализации / десериализации.Я столкнулся с проблемой.

[ProtoContract()]
ClsTest
{
    private bool _isPeriodic

    [ProtoMember(1)]
    public bool IsPeriodic
    {
        get
        {
             return _isPeriodic;
        }

        set
        {
            isPeriodic = value;
        }
   }

}

Я использую этот класс в своем объекте сопоставления.

Процесс сериализации работает нормально, но после десериализации значение свойства IsPeriodic по умолчанию равно true, хотяэто было ложно в некоторых случаях.Кто-нибудь может мне помочь?

Ответы [ 4 ]

6 голосов
/ 02 июля 2010

Я предполагаю, что ваш код устанавливает IsPeriodic в true для значений по умолчанию new экземпляров, возможно:

private bool _isPeriodic = true;

или, в конструкторе у вас есть:

_isPeriodic = true; // or IsPeriodic = true

В основном , существует неявное значение по умолчанию (в соответствии с руководством по языку protobuf), где предполагается, что по умолчанию bool имеет значение false. Он не отправляет данные, которые считаются значениями по умолчанию. Если это значение по умолчанию неверно, сообщите ему:

[ProtoMember(1), DefaultValue(true)]

или IIRC вы можете попробовать вместо IsRequired установить true:

[ProtoMember(1, IsRequired = true)]

и есть несколько других способов заставить его всегда отправлять значение:

private bool ShouldSerializeIsPeriodic() { return true;}

(который использует тот же шаблон, который поддерживается ядром .NET для PropertyGrid, XmlSerializer, PropertyDescriptor и т. Д. - я не изобрел случайные шаблоны)

Обратите внимание, что в "v2" я сделал еще два изменения, чтобы помочь устранить эту странность:

  • при желании вы можете обойти конструктор (стиль WCF)
  • новая метамодель предоставляет другой способ решить, принимать ли по умолчанию
1 голос
/ 23 мая 2014

Я обнаружил, что работа с типами bool и enum с protobuf-net имеет некоторые хитрости.Первый вопрос о значении по умолчанию для типов bool и enum: Вот мой фрагмент кода Linq:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5) ]
    public bool m_bool = true ;                   (B)
}

void Main()
{
    string fname = @"D:/test.dat";
    if (File.Exists(fname) )
    {
        File.Delete(fname);
    }
    using(FileStream fs=  new FileStream(fname, FileMode.OpenOrCreate, FileAccess.Write) )
    {
        MyOption opt = new MyOption();
        opt.m_printListView = View.LargeIcon; // (1)
        opt.m_bool = false;                   // (2)

        Serializer.SerializeWithLengthPrefix(fs, opt, PrefixStyle.Fixed32);
    }
    using(FileStream fs=  new FileStream(@"D:/test.dat", FileMode.Open, FileAccess.Read) )
    {
        MyOption opt;
        opt = Serializer.DeserializeWithLengthPrefix<MyOption>(fs, PrefixStyle.Fixed32);
        Console.WriteLine(opt.m_printListView);
        Console.WriteLine(opt.m_bool);
    }
}

Теперь угадайте вывод.Это:

Details
True

Обратите внимание, что в (A) и (B) я установил значение по умолчанию для View.Details и true.В (1) и (2) я явно устанавливаю значение View.LargeIcon и false, после сериализации и десериализации proto-buf мы получили неправильное значение.

Причина в том, что: для значения boolзначение по умолчанию - false, в соответствии с принципом разработки proto-buf, оно будет экономить место, когда это возможно, поэтому значение по умолчанию не сохраняется в файле, сохраняется только флаг, указывающий, что следует использовать значение по умолчанию (я думаю, не проверено).

При десериализации сначала вызывается конструктор по умолчанию, а линии (B) фактически являются частью конструктора по умолчанию во время выполнения CLR, затем запускается процесс десериализации proto-buf и обнаруживаетсяЭлемент m_bool имеет флаг значения по умолчанию, затем используйте значение по умолчанию false, чтобы установить m_bool, который перезаписывает значение по умолчанию в (B).

Для типа enum причина аналогична, в приведенном выше примере View.LargeIcon isзначение по умолчанию, числовое значение которого равно 0 (проверено с помощью Reflected).

Чтобы исправить это, используйте DefaultValueAttribute для bool и enum member:

[ProtoContract]
public class MyOption
{
    [ProtoMember(2), DefaultValue(View.Details)]
    public View m_printListView = View.Details;   (A)
    [ProtoMember(5), DefaultValue(true) ]
    public bool m_bool = true ;                   (B)
}

Для типа enum есть другие проблемы: во-первых, все перечислители должны иметь разные значения, в противном случае proto-buf выдаст исключение при сериализации, например, System.Тип перечисления Drawing.RotateFlip имеет следующее определение:

    public enum RotateFlipType
{
    Rotate180FlipNone = 2,
    Rotate180FlipX = 6,
    Rotate180FlipXY = 0,
    Rotate180FlipY = 4,
    Rotate270FlipNone = 3,
    Rotate270FlipX = 7,
    Rotate270FlipXY = 1,
    Rotate270FlipY = 5,
    Rotate90FlipNone = 1,
    Rotate90FlipX = 5,
    Rotate90FlipXY = 3,
    Rotate90FlipY = 7,
    RotateNoneFlipNone = 0,
    RotateNoneFlipX = 4,
    RotateNoneFlipXY = 2,
    RotateNoneFlipY = 6
}

С точки зрения обработки изображений RotateNoneFlipNone и Rotate180FlipXY имеют одинаковый эффект, поэтому они имеют одинаковое базовое значение, это разумный дизайн, однако такое перечисление не можетработает с proto-buf:

    The enum System.Drawing.RotateFlipType has conflicting values RotateNoneFlipNone and RotateNoneFlipNone
Serializer.ThrowInner (Exception exception)
  at ProtoBuf.Serializer.ThrowInner(Exception exception)
  at ProtoBuf.Serializer.Serialize[T](Stream destination, T instance)
  at ProtoBuf.Serializer.SerializeWithLengthPrefix[T](Stream destination, T instance, PrefixStyle style, Int32 tag)

Мой обходной путь - создать собственное перечисление и использовать сопоставление «один к одному» между My_RotateFlipType и System.Drawing.RotateFlipType.Только My_RotateFlipType будет сериализован proto-buf. * ​​1024 *

public enum RotateFlipType              public enum My_RotateFlipType
{                                       {
    Rotate180FlipNone = 2,                  Rotate180FlipNone,
    Rotate180FlipX = 6,                     Rotate180FlipX,
    Rotate180FlipXY = 0,                    Rotate180FlipXY,
    Rotate180FlipY = 4,                     Rotate180FlipY,
    Rotate270FlipNone = 3,                  Rotate270FlipNone,
    Rotate270FlipX = 7,                     Rotate270FlipX,
    Rotate270FlipXY = 1,                    Rotate270FlipXY,
    Rotate270FlipY = 5,                     Rotate270FlipY,
    Rotate90FlipNone = 1,                   Rotate90FlipNone,
    Rotate90FlipX = 5,                      Rotate90FlipX,
    Rotate90FlipXY = 3,                     Rotate90FlipXY,
    Rotate90FlipY = 7,                      Rotate90FlipY,
    RotateNoneFlipNone = 0,                 RotateNoneFlipNone,
    RotateNoneFlipX = 4,                    RotateNoneFlipX,
    RotateNoneFlipXY = 2,                   RotateNoneFlipXY,
    RotateNoneFlipY = 6                     RotateNoneFlipY
}                                       }

Чтобы избежать ручной синхронизации с двумя элементами данных, я использую функции ProtoBeforeSerialization и OnProtoAfterDeserialization для его автоматизации:

[ProtoAfterDeserialization()]
public void OnProtoAfterDeserialization()
{
    Console.WriteLine("called OnProtoAfterDeserialization");
    bool ret = Enum.TryParse(m_rotate.ToString(), out m_rotate_protobuf);
}

[ProtoBeforeSerialization()]
public void OnProtoBeforeSerialization()
{
    Console.WriteLine("called OnProtoBeforeSerialization");
    bool ret = Enum.TryParse(m_rotate_protobuf.ToString(), out m_rotate);
}

ВторойВопрос о enum - это перечислитель с 0 значениями.Если enum не имеет перечислителя со значением 0, то для protobuf очень легко вызвать исключение во время выполнения.

Я буду следовать правилам при работе с protobuf-net: 1. Всякий раз, когда конструктор по умолчанию устанавливает значение otherчем значение по умолчанию, используйте DefaultValueAttribute.2. Для системного или стороннего типа enum, проверьте его с помощью отражателя (статически) или linq (время выполнения), чтобы увидеть, есть ли у него вышеуказанная проблема, прежде чем добавлять его в protobuf.Если конфликтует, используйте вышеуказанный обходной путь.

0 голосов
/ 15 января 2015

У меня была похожая проблема, когда bool члены, которые по умолчанию true не читали свои значения из файла конфигурации.

[ProtoMember(1, IsRequired=true), DefaultValue(true)]
public bool IsPeriodic
...
0 голосов
/ 01 июля 2010

У меня прекрасно работает:

[ProtoContract]
class ClsTest
{
    [ProtoMember(1)]
    public bool IsPeriodic { get; set; }
}

Десериализация:

   // stream is a NetworkStream object

   ClsTest clsTestObj = Serializer.DeserializeWithLengthPrefix<ClsTest>(stream, PrefixStyle.Fixed32);
   bool value = clsTestObj.IsPeriodic;
...