net不反序列化DateTime.正确

本文关键字:正确 DateTime 反序列化 net | 更新日期: 2023-09-27 18:01:53

using protobuf-net.dll Version 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);
    }
}

Assert失败,Kind == 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属性。

net不反序列化DateTime.正确

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

事实证明,protobuf.net只编码Ticks字段,您可以在BclHelpers.cs中找到代码。

但是您可以在您的protobuf消息定义中为这个值添加另一个字段。

作为Ben回答的延伸…严格地说,protobuf 没有时间的定义,所以没有什么可以保持兼容性。我很想在v2中增加对此的支持,但遗憾的是,它将为每个值增加2个字节。我还没有考虑这是否可以接受……例如,我可以默认为"unspecified",这样只有显式的本地日期或UTC日期才有值。

另一个解决方案是更改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是很痛苦的。然而,像"长"这样简单的东西是大家都明白的。

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

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

[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);
        }
    }
}

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

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

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

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

你可以在我的博客

上阅读更多内容

从protobuf-net 2.2开始(请参阅提交),可以选择加入DateTime.Kind的序列化。您可以设置一个全局标志。github上的相应问题(仍在打开中)。

下面是一个与NServiceBus相关的用法示例。

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