考虑如下声明:
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 数组了。