【问题标题】:protobuf-net does not deserialize DateTime.Kind correctlyprotobuf-net 没有正确反序列化 DateTime.Kind
【发布时间】:2011-10-03 07:17:31
【问题描述】:

使用 protobuf-net.dll 版本 1.0.0.280

当我反序列化 DateTime(包装在对象中)时,日期/时间没问题,但 DateTime.Kind 属性是“未指定”

考虑这个测试用例来序列化/反序列化一个 DateTime。

[TestMethod]
public void TestDateTimeSerialization()
{
    var obj = new DateTimeWrapper {Date = DateTime.UtcNow};
    obj.Date = DateTime.SpecifyKind(obj.Date, DateTimeKind.Utc);
    var serialized = obj.SerializeProto();
    var deserialized = serialized.DeserializeProto<DateTimeWrapper>();
    Assert.AreEqual(DateTimeKind.Utc, deserialized.Date.Kind);
}

public static byte[] SerializeProto<T>(this T item) where T : class
{
    using (var ms = new MemoryStream())
    {
        Serializer.Serialize(ms, item);
        return ms.ToArray();
    }
}

public static T DeserializeProto<T>(this byte[] raw) where T : class, new()
{
    using (var ms = new MemoryStream(raw))
    {
        return Serializer.Deserialize<T>(ms);
    }
}

断言失败,种类 == Unspecified

附录

由于protobuf-net 未序列化此属性(见下文),一种解决方案是在客户端显示日期时假设DateTimeKind 等于 Utc(仅在您知道它的地方当然应该是UTC):

public static DateTime ToDisplayTime(this DateTime utcDateTime, TimeZoneInfo timezone)
{
    if (utcDateTime.Kind != DateTimeKind.Utc)//may be Unspecified due to serialization
        utcDateTime = DateTime.SpecifyKind(utcDateTime, DateTimeKind.Utc);
    DateTime result = TimeZoneInfo.ConvertTime(utcDateTime, timezone);
    return result;
}

这使您不必在接收方分配给每个DateTime 属性。

【问题讨论】:

    标签: c# .net serialization protobuf-net globalization


    【解决方案1】:

    protobuf.net 必须保持与为 Java 日期/时间数据类型设计的 protobuf 二进制格式的兼容性。 Java 中不支持 Kind 字段 -> 不支持 protobuf 二进制格式的 Kind -> Kind 未通过网络传输。或者类似的东西。

    事实证明,protobuf.net 对 Ticks 字段(仅)进行了编码,您将在 BclHelpers.cs 中找到代码。

    但请随意在您的 protobuf 消息定义中为该值添加另一个字段。

    【讨论】:

    • 谢谢。您是否知道由于 protobuf 二进制格式而导致的其他“有损”序列化?
    • 我最近遇到了这个问题。如果DateTimeKind 不是Unspecified,那么在使用反序列化的DateTime 时调用ToUniversalTime() 应该是安全的。这会将DateTimeKind 设置为Utc,这将是正确的,因为Ticks 不会根据DateTimeKind 而改变。注意:如果DateTimeKindUnspecified序列化之前,这不一定是安全的。
    【解决方案2】:

    作为 Ben 的答案的扩展......严格来说,protobuf 没有时间定义,因此没有什么可以保持兼容性。我很想在 v2 中添加对此的支持,但遗憾的是它会为每个值添加 2 个字节。我还没有考虑这是否可以接受......例如,我可能会默认为“未指定”,以便只有明确的本地日期或 UTC 日期才有值。

    【讨论】:

    • Marc,为什么 DateTime 被视为不同于用户创建的自定义类(其“兼容性”为零) - 我本以为大小/速度之前的 #1 优先级是重新创建对象处于原始状态。
    • @wal 也许它在大多数时候都不是问题。如果我们需要,我不反对在 v2 数据中添加“种类”——仅提及影响,仅此而已。
    • 通常以 UTC 存储日期。当它们通过网络发送时,protobuf 会丢失此信息 (Kind),这意味着在其时区的客户端上显示它将不起作用。我很惊讶之前没有人遇到过这种情况?
    • @MarcGravell,当前版本有什么变化吗?我目前遇到了完全相同的问题,我序列化 utc 并返回未指定。有没有简单的解决方法?谢谢
    【解决方案3】:

    这是一种解决方法的实现。如果您能找到更好的解决方案,请告诉我。谢谢!

    [ProtoContract(SkipConstructor = true)]
    public class ProtoDateTime
    {
        [ProtoIgnore]
        private DateTime? _val;
    
        [ProtoIgnore]
        private DateTime Value
        {
            get
            {
                if (_val != null)
                {
                    return _val.Value;
                }
                lock (this)
                {
                    if (_val != null)
                    {
                        return _val.Value;
                    }
                    _val = new DateTime(DateTimeWithoutKind.Ticks, Kind);
                }
                return _val.Value;
            }
            set
            {
                lock (this)
                {
                    _val = value;
                    Kind = value.Kind;
                    DateTimeWithoutKind = value;
                }
            }
        }
    
        [ProtoMember(1)]
        private DateTimeKind Kind { get; set; }
        [ProtoMember(2)]
        private DateTime DateTimeWithoutKind { get; set; }
    
    
        public static DateTime getValue(ref ProtoDateTime wrapper)
        {
            if (wrapper == null)
            {
                wrapper = new ProtoDateTime();
            }
            return wrapper.Value;
        }
    
        public static DateTime? getValueNullable(ref ProtoDateTime wrapper)
        {
            if (wrapper == null)
            {
                return null;
            }
            return wrapper.Value;
    
        }
    
        public static void setValue(out ProtoDateTime wrapper, DateTime value)
        {
            wrapper = new ProtoDateTime { Value = value };
        }
    
        public static void setValue(out ProtoDateTime wrapper, DateTime? newVal)
        {
            wrapper = newVal.HasValue ? new ProtoDateTime { Value = newVal.Value } : null;
        }
    }
    

    用法:

    [ProtoContract(SkipConstructor = true)]
    public class MyClass
    {
        [ProtoMember(3)]
        [XmlIgnore]
        private ProtoDateTime _timestampWrapper { get; set; }
        [ProtoIgnore]
        public DateTime Timestamp
        {
            get
            {
                return ProtoDateTime.getValue(ref _timestampWrapper);
            }
            set
            {
                return ProtoDateTime.setValue(out _timestampWrapper, value);
            }
        }
    
        [ProtoMember(4)]
        [XmlIgnore]
        private ProtoDateTime _nullableTimestampWrapper { get; set; }
        [ProtoIgnore]
        public DateTime? NullableTimestamp
        {
            get
            {
                return ProtoDateTime.getValueNullable(ref _nullableTimestampWrapper);
            }
            set
            {
                return ProtoDateTime.setValue(out _nullableTimestampWrapper, value);
            }
        }
    
    }
    

    【讨论】:

      【解决方案4】:

      protobuf 使用 UtcKind 自动反序列化 DateTime 可能更有意义,这样如果你使用 Utc 作为你的基础,我认为无论如何这是最佳实践,你不会有任何问题。

      【讨论】:

        【解决方案5】:

        另一种解决方案是更改 DTO 的 kind 属性并始终将其设置为 UTC。这可能不适用于所有应用程序,但对我有用

        class DateTimeWrapper 
        {
            private DateTime _date;
        
            public DateTime Date 
            {
                get { return _date; }
                set { _date = new DateTime(value.Ticks, DateTimeKind.Utc);}
            }
        }
        

        更新

        在使用 protobuf 一年多并集成 C#、Java、Python 和 Scala 之后,我得出结论,应该使用长表示 DateTime。例如使用 UNIX 时间。将 C# DateTime protobuf 对象翻译成其他语言的 DateTime 是很痛苦的。不过,简单的事情大家都懂。

        【讨论】:

          【解决方案6】:

          假设您只需要一个DateTimeKind(即UTCLocal),那么有一个简单(虽然不漂亮)的解决方案。

          由于 protobuf-net 在内部将 DateTime 转换为 Unix-Time 表示形式,因此它有一个单独的 DateTime 值表示 Unix 纪元 (1970/01/01),它每次都会向该纪元添加相关的增量。

          如果您使用反射将该值替换为UTCLocal DateTime 值,则您的所有DateTimes 都将具有指定的DateTimeKind

          typeof (BclHelpers).
              GetField("EpochOrigin", BindingFlags.NonPublic | BindingFlags.Static).
              SetValue(null, new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));
          

          您可以在my blog了解更多信息

          【讨论】:

            【解决方案7】:

            从 protobuf-net 2.2 开始(参见 commit),可以选择加入 DateTime.Kind 的序列化。您可以设置全局标志。 github上对应的issue(依然开放)。

            这是一个与 NServiceBus 相关的usage example

            免责声明:这对 OP 所指的旧 protobuf-net 版本没有帮助,但这是一个老问题,可能对其他人有帮助。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-04-26
              • 2021-01-14
              • 2012-08-31
              • 2013-06-16
              • 2019-08-21
              • 2012-11-16
              • 1970-01-01
              相关资源
              最近更新 更多