我可以使用protobuf-net序列化任意类型吗?

27

我正在尝试使用protobuf-net来序列化一些对象,但不幸的是它们广泛使用了DateTimeOffset,而这种类型目前还不被protobuf-net支持。这导致出现大量:

未定义类型的序列化程序:System.DateTimeOffset

我能为未知类型定义自己的序列化程序吗?(同样的问题之前已经问过,但他的问题已经得到解决。)

我正在使用最新的protobuf-net beta,即v2.0.0.431,在.NET 4下使用运行时定义,因此我无法声明性地指定如何处理某些属性。

4个回答

34

解决未知的“普通”类型问题有两种方法;第一种方法是使用一个shim属性,例如用类似的东西来表示该值(如字符串长整型):

[ProtoMember(8)]
public string Foo {
    get { ... read from the other member ... }
    set { ... assign the other member ... }
}
另一种方法是使用“代理(surrogate)”,这是第二个protobuf协议,会被自动替换。使用代理的要求如下:
  • 必须存在两种类型之间的定义转换运算符(隐式或显式),例如DateTimeOffsetDateTimeOffsetSurrogate
  • 您可以使用SetSurrogate(surrogateType)来指定protobuf-net中的代理,例如RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
shim属性更简单,但需要在每个成员上重复设置。代理会自动应用于模型中类型的所有实例。代理遵循标准的protobuf-net规则,因此您需要指定哪些成员进行序列化等。
编辑:添加代码示例
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));

4
除了应该是 value.ToString("o") 而不是 "u" 之外,一切都很好。否则你会失去偏移信息(时区)。 - Max Shmelev
1
其他protobuf库如何反序列化此输出?如果您有一个具有DateTimeOffset类型属性的Parent类,那么其他库是否需要使用带有字符串属性的代理类型?或者在另一个没有DateTimeOffset类型的语言中,其他系统中的Parent类是否可以简单地使用字符串类型来表示该属性? - Rush Frisby
1
我刚刚测试了 @MarcGravell 的解决方案,并确认了 @MaxShmelev 所说的:必须使用 value.ToString("o"),以避免丢失偏移信息。 - Luca Ritossa

24

尊重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));

我不太明白如何在我的情况下使用它。我正在尝试序列化System.Data.DataSet类。如果能给个小提示,我将不胜感激。 - i know nothing
@iknownothing,我认为这并不容易实现:System.Data.DataSet比DateTimeOffset更为复杂。 - Tamas Ionut

3

如果有任何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>)

0

我现在使用的是与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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接