ProtoBuf 序列化结果 长度为 0 的情况

2021-11-17  本文已影响0人  gruan

考虑如下声明:

var t = new Protobuf0Len(){ A = false }
...
...
[ProtoBuf.ProtoContract(ImplicitFields = ProtoBuf.ImplicitFields.AllPublic)]
public class Protobuf0Len
{
    public bool A { get; set; }

    public IEnumerable<int> B { get; set; }

    public int C { get; set; }

    public string D { get; set; }

    public long E { get; set; }

    public DateTime? F { get; set; }
}

变量 t 的各个属性都是默认的值,A = false, B = null, C = 0, D = null, E = 0, F = null.

用以下方法序列化这个 t, 你将得到一个 0 长度的 byte 数组:

public static byte[] Serialize(object obj)
{
    if (obj == null)
        return null;

    using var msm = new MemoryStream();
    Serializer.Serialize(msm, obj);
    return msm.ToArray();
}

原因

因为, ProtoBuf 对这样的数据不感兴趣,都是默认值, 没什么值得去序列化的。。。(没找到官方说明, 我瞎写的)

ProtoBuf 之所以这样返回, 是没有问题的, 因为0 长度的 byte 数组, 可以反序列化成万物的:

ReadOnlySpan<byte> span = new byte[0];
var t1 = ProtoBuf.Serializer.Deserialize<Protobuf0Len>(span);

变量 t1 里的每个属性都是默认值。。。

问题

但是,出于程序员特有的严谨, 我们拿到结果时, 都会去判断一下是否非空, 非空时才会进行下一步操作。。。

比如:

// 锅:
// {A=false, B=null}, 用 Protobuf 序列化后, 就是长度为 0 的字节数组.
// 写到 redis 里,在读出来, HasValue 就是 false,
// false 这里就不会执行了, 返回出去就是 null 了.
if (r.HasValue)
{
    //var d = Deserialize<T>(r);
    var d = await DeserializeAsync<T>(r);
    results.Add(d);
}

这就造成了一个隐藏的BUG, 明明是有结果的, 但是却取不出来。。。

解决方法

为了避免这个问题,序列化反序列化可以用:SerializeWithLengthPrefix / DeserializeWithLengthPrefix

var t = new Protobuf0Len();

byte[] a;
using (var msm = new MemoryStream())
{
    ProtoBuf.Serializer.SerializeWithLengthPrefix<Protobuf0Len>(msm, t, ProtoBuf.PrefixStyle.Base128);
    a = msm.ToArray();
}

using (var msm = new MemoryStream(a))
{
    var t1 = ProtoBuf.Serializer.DeserializeWithLengthPrefix<Protobuf0Len>(msm, ProtoBuf.PrefixStyle.Base128);
}

这样, 序列化出来的就至少是长度为1的 byte 数组了。

上一篇下一篇

猜你喜欢

热点阅读