1

只是想分享一个关于 protobuf-net 的 R561 版本的观察。使用时DateTimeOffSet,异常

InvalidOperationException(没有为类型定义序列化程序:System.DateTimeOffset)

出现:

我添加了一个带有 agetProto()和 a的方法StreamWriter来编写一个 proto 文件,现在它可以工作了!(而且 proto 文件也完全可用)。但是如果我评论这个方法,就会发生同样的异常。我真的不明白。

希望这个观察可能有用。


我会尽量清楚。我有一个 C# 客户端,其中包含一些使用 DateTimeOffset 参数的对象。我用 protobuf-net (r561) 对它们进行了序列化,并添加了一个 writeProtoFile() 方法来使用 getProto() 方法编写一个 proto 文件。序列化似乎工作得很好,原始文件也可以。所以因为我现在有我的 proto 文件,我可以评论或禁止 writeProtoFile() 方法:我不需要其他 proto 文件。所以这是我的第一个问题:

-> 为什么当这个方法(只是写一个调用 getProto() 方法的原型文件)被注释或抑制时,序列化不再起作用?这是我得到的例外:

没有为类型定义序列化程序:System.DateTimeOffset。

当我取消注释 writeProtoFile() 注释时,它就起作用了。这是方法:

public static void writeProtoFile(String proto)
{
    StreamWriter file = new StreamWriter("c:\\MyprotoFiles\\MyProtoFile.proto");
    file.Write(proto);
    file.Close();

}

我需要这个对象被 java 客户端使用。使用 proto 编译器生成的 java 类似乎没问题,但是当我反序列化它时,出现异常:

com.google.protobuf.InvalidProtocolBufferException:解析协议消息时,输入意外在字段中间结束。这可能意味着输入被截断或者嵌入的消息误报了它自己的长度。

我认为,原因是生成的 DateTimeOffset 类(在原型中,dateTimeOffset 什么都不包含)

message DateTimeOffset {
}

Java 中存在 DateTimeOffset 类型,所以这是我的第二个问题:-> 有什么方法可以序列化 C# 中的 dateTimeOffset 参数,然后在反序列化后成为 java 中的 dateTimeOffset 参数?

4

4 回答 4

2

有什么方法可以序列化 C# 中的 dateTimeOffset 参数,然后在反序列化后成为 java 中的 dateTimeOffset 参数?

任何语言都没有定义基于 .proto 的处理DateTimeDateTimeOffset值,因此不存在通过 protobuf(或任何特定实现,例如 protobuf-net)在平台之间传输此类数据的甚至远程保证的方式。此外,虽然有一种类型在 2 个不同的平台上调用,但仅靠其本身不足以保证它们具有相似的语义/范围/等。DateTimeOffset

对于任何跨平台场景,我建议只使用非常基本的数据,甚至可能只是像整数(64 位)这样的东西来将偏移量(以毫秒为单位)存储到 1970 纪元。或类似的东西。

为什么当这个方法(只是写一个调用 getProto() 方法的原型文件)被注释或抑制时,序列化不再起作用?

protobuf-net 零使用任何getProto方法或writeProtoFile方法。我会非常谨慎地怀疑评论/取消评论这会改变一些内部行为,并且需要具体的重现来调查。老实说,这听起来极不可能。警告:有一种Serializer.GetProto<T>方法,但它做了一些非常不同的事情(并且GetSchema(Type)在 v2 API 中被取代)。

然而,它的说法是完全正确的:

没有为类型定义序列化程序:System.DateTimeOffset。

原因很简单,我没有为这种类型定义标准序列化程序。如果您可以为此定义一些标准处理,则可以使用SetSurrogate将其连接到您选择的任何电线表示。

回覆:

解析协议消息时,输入意外在字段中间结束

这不应该以任何方式发生。这听起来像是一个不相关的问题,很可能是传输数据时数据损坏(例如,数据编码不正确),或者覆盖预先存在的文件而不截断它(最后留下垃圾)。如果您可以准确说明如何在平台之间传输二进制文件,我可能会提供更多建议,但首先要调查的是:您发送的二进制数据是否与您收到的二进制数据相同(与 protobuf 无关- 很简单:您是否正确传输了我的 BLOB?长度相同吗?每个字节都相同吗?)

于 2012-08-20T09:26:22.540 回答
1

我遇到了同样的问题,并为原型序列化编写了一个包装器对象(请参阅代码答案的结尾)。它有单独的 Ticks 和 Offset 字段。为了使助手的工作更容易,我添加了一些运算符。

您可以将DateTimeOffset对象直接分配给助手类:

[ProtoContract]
public class ExampleContract {
    [ProtoMember(1)]
    public ProtoDateTimeOffset LastLoginTime { get; set; }
}

var contract = new ExampleContract
{
    LastLoginTime = DateTimeOffset.Now
};

反序列化时,您可以直接使用 ProtoDateTimeOffset 实例,也可以在 DTO 中创建重载:

public DateTimeOffset LastLogin { get; set; }

[ProtoMember(4)]
public ProtoDateTimeOffset? ProtoLastLogin
{
    get => LastLogin;
    set => LastLogin = value;
}

这是一个特定于 C# 的实现,我不知道您可以使用哪些语言来处理刻度和偏移量,但在这些语言中的转换应该是微不足道的。


辅助类的实际代码:

/// <summary>
/// Proto contract that represents a <see cref="DateTimeOffset"/>
/// </summary>
[ProtoContract]
public class ProtoDateTimeOffset
{
    /// <summary>
    /// Utc ticks
    /// </summary>
    [ProtoMember(1)]
    public long Ticks { get; set; }

    /// <summary>
    /// The UTC offset in minutes
    /// </summary>
    [ProtoMember(2)]
    public double OffsetMinutes { get; set; }

    /// <summary>
    /// Operator to cast <see cref="ProtoDateTimeOffset"/> to <see cref="DateTimeOffset"/>
    /// </summary>
    /// <param name="other"></param>
    /// <returns></returns>
    public static implicit operator DateTimeOffset?(ProtoDateTimeOffset? other)
    {
        if (other == null)
            return null;
        return new DateTimeOffset(other.Ticks, TimeSpan.FromMinutes(other.OffsetMinutes));
    }

    /// <summary>
    /// Operator to cast <see cref="ProtoDateTimeOffset"/> to <see cref="DateTimeOffset"/> or default
    /// </summary>
    /// <param name="other"></param>
    /// <returns></returns>
    public static implicit operator DateTimeOffset(ProtoDateTimeOffset? other)
    {
        if (other == null)
            return default;
        return new DateTimeOffset(other.Ticks, TimeSpan.FromMinutes(other.OffsetMinutes));
    }

    /// <summary>
    /// Operator to cast <see cref="DateTimeOffset"/> to <see cref="ProtoDateTimeOffset"/>
    /// </summary>
    /// <param name="other"></param>
    /// <returns></returns>
    public static implicit operator ProtoDateTimeOffset? (DateTimeOffset? other)
    {
        if (other == null)
            return null;
        return new ProtoDateTimeOffset
        {
            OffsetMinutes = other.Value.Offset.TotalMinutes,
            Ticks = other.Value.Ticks
        };
    }
}
于 2021-07-29T08:50:36.487 回答
0

我使用这种方法在 C# 中进行序列化:

MemoryStream ms = new MemoryStream();
Serializer.Serialize<MyObjectType>(ms, myObject);
byte[] array = ms.ToArray();
ms.Close();

然后我将它发送到 ActiveMQ 主题(pub-sub 模型)Java 以异步方式接收它,并使用以前的方法(在我的第一个答案中)反序列化它。我读了数组,它似乎没有损坏,但一个已签名,另一个未签名。在 C# 中,我有 10 个大于 126 的值。这些相同的值在 Java 中变为负数:

byte #58 =  144 in C# , -112 in Java
byte #67 =  160 in C# , -96 in Java

经过多次测试,当我不序列化 dataTimeOffset 参数时,这些值似乎消失了。

于 2012-08-20T14:22:31.503 回答
-1

这是java的反序列化方法:

public MyObjectProto.MyObject deserialize(byte[] array) {
    try {
        CodedInputStream stream = CodedInputStream.newInstance(array);
        return MyObjectProto.MyObject.parseFrom(stream);
    } catch (IOException ex) {
        displayLogs("Error while deserializing the content of message : " + ex);
        return null;
    }
}

这种反序列化方法会损坏它吗?

于 2012-08-20T09:49:06.417 回答