我正在尝试使用protobuf-net来序列化一些对象,但不幸的是它们广泛使用了DateTimeOffset
,而这种类型目前还不被protobuf-net支持。这导致出现大量:
未定义类型的序列化程序:System.DateTimeOffset
我能为未知类型定义自己的序列化程序吗?(同样的问题之前已经问过,但他的问题已经得到解决。)
我正在使用最新的protobuf-net beta,即v2.0.0.431,在.NET 4下使用运行时定义,因此我无法声明性地指定如何处理某些属性。
我正在尝试使用protobuf-net来序列化一些对象,但不幸的是它们广泛使用了DateTimeOffset
,而这种类型目前还不被protobuf-net支持。这导致出现大量:
未定义类型的序列化程序:System.DateTimeOffset
我能为未知类型定义自己的序列化程序吗?(同样的问题之前已经问过,但他的问题已经得到解决。)
我正在使用最新的protobuf-net beta,即v2.0.0.431,在.NET 4下使用运行时定义,因此我无法声明性地指定如何处理某些属性。
解决未知的“普通”类型问题有两种方法;第一种方法是使用一个shim属性,例如用类似的东西来表示该值(如字符串
或长整型
):
[ProtoMember(8)]
public string Foo {
get { ... read from the other member ... }
set { ... assign the other member ... }
}
另一种方法是使用“代理(surrogate)”,这是第二个protobuf协议,会被自动替换。使用代理的要求如下:
DateTimeOffset
和DateTimeOffsetSurrogate
SetSurrogate(surrogateType)
来指定protobuf-net中的代理,例如RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
using System;
using ProtoBuf;
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public string DateTimeString { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate {DateTimeString = value.ToString("u")};
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return DateTimeOffset.Parse(value.DateTimeString);
}
}
然后像这样注册它
RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
尊重Marc Gravell的答案,如果您关心序列化数据的大小,应使用以下代理类。输出大小为21个字节,而不是35个字节。
using System;
using ProtoBuf;
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public long DateTimeTicks { get; set; }
[ProtoMember(2)]
public short OffsetMinutes { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate
{
DateTimeTicks = value.Ticks,
OffsetMinutes = (short)value.Offset.TotalMinutes
};
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return new DateTimeOffset(value.DateTimeTicks, TimeSpan.FromMinutes(value.OffsetMinutes));
}
}
然后以完全相同的方式注册它:
RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
如果有任何F#开发人员遇到这个问题,这里提供一个F#的答案:
[<ProtoContract>]
type DateTimeOffsetSurrogate() =
[<ProtoMember(1)>]
member val DateTimeString = "" with get, set
static member public op_Implicit(value : DateTimeOffset) : DateTimeOffsetSurrogate =
DateTimeOffsetSurrogate(DateTimeString = value.ToString("o"))
static member public op_Implicit(value : DateTimeOffsetSurrogate) : DateTimeOffset =
DateTimeOffset.Parse(value.DateTimeString)
非明显的是op_Implicit
方面。
您还可以适应Max使用撇号节省空间的技巧。
编辑:以下是如何将代理添加到运行时类型模型中:
let init() =
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typedefof<DateTimeOffset>, false).SetSurrogate(typedefof<DateTimeOffsetSurrogate>)
我现在使用的是与google/protobuf/timestamp.proto中定义的序列化兼容的版本。
(在proto文件中: import "google/protobuf/timestamp.proto"; )
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public Int64 Seconds { get; set; }
[ProtoMember(2)]
public Int32 Nanos { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
var totalSeconds = (value - DateTimeOffset.UnixEpoch).TotalSeconds;
var secondsRounded = Convert.ToInt64(totalSeconds);
return new DateTimeOffsetSurrogate { Seconds = secondsRounded, Nanos = Convert.ToInt32((totalSeconds - secondsRounded) * 1000000000) };
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return DateTimeOffset.FromUnixTimeSeconds(value.Seconds).AddTicks(value.Nanos / 100);
}
}
value.ToString("o")
而不是"u"
之外,一切都很好。否则你会失去偏移信息(时区)。 - Max Shmelevvalue.ToString("o")
,以避免丢失偏移信息。 - Luca Ritossa