protobuf-net没有正确反序列化DateTime.Kind

时间:2011-07-12 03:25:37

标签: c# .net serialization protobuf-net

使用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);
    }
}

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属性。

7 个答案:

答案 0 :(得分:6)

protobuf.net必须保持与protobuf二进制格式的兼容性,该格式是为Java日期/时间数据类型设计的。 Java中没有Kind字段 - &gt; protobuf二进制格式中没有Kind支持 - &gt; Kind未通过网络传输。或者沿着这些方向发展。

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

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

答案 1 :(得分:5)

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

答案 2 :(得分:2)

另一种解决方案是更改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很痛苦。然而,所有人都理解一个简单的事情。

答案 3 :(得分:1)

这是一种变通方法的实现。如果您能找到更好的解决方案,请与我们联系。谢谢!

[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 :(得分:1)

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

答案 5 :(得分:1)

假设您只需要一个DateTimeKind(即UTCLocal),那就是一个简单(但不是很漂亮)的解决方案。

由于内部protobuf-net将DateTime转换为Unix-Time表示,因此它有一个DateTime值代表Unix纪元(1970/01/01),它每个都添加相关的delta时间。

如果您使用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));

您可以阅读有关my blog

的更多信息

答案 6 :(得分:0)

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

这是与NServiceBus相关的usage example

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