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
属性。
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
(即UTC
或Local
),有一个简单的(虽然不是很漂亮)解决方案。
由于protobuf-net内部将DateTime
转换为Unix- time表示,因此它有一个单独的DateTime
值表示Unix纪元(1970/01/01),每次都添加相关的增量。
如果您使用反射将该值替换为UTC
或Local
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版本没有帮助,但这是一个老问题,可能对其他人有帮助。