Protobuf-net序列化时如何通过代理跳过不符合要求的数据?
嘿,我完全懂你遇到的困境——想用protobuf-net的surrogate做序列化时,根据上下文里的条件过滤掉不符合的Person条目,但BeforeSerialize方法触发太晚,没法直接跳过整个条目对吧?别着急,咱们换个思路来解决这个问题。
核心问题分析
你当前的尝试是在BeforeSerialize里做校验,但这个方法是在代理实例已经创建完成、即将被序列化的时候才调用的。此时protobuf-net已经把这个条目放进了序列化队列,再想移除它就没那么直接了。所以咱们得把校验逻辑提前到代理转换的源头,从根上过滤掉不符合条件的元素。
解决方案1:利用AsyncLocal传递上下文+转换阶段过滤
这个方案适合需要在Person级别灵活控制过滤的场景,步骤如下:
1. 在FamilySerializer中添加上下文传递机制
因为静态转换方法没法直接拿到SerializationContext,咱们可以用AsyncLocal来安全传递当前的过滤条件(它能保证异步/多线程场景下的上下文隔离):
public class FamilySerializer { public GenderType GenderToInclude; public RuntimeTypeModel Model { get; protected set; } // 用AsyncLocal保存当前的过滤条件,线程/异步上下文安全 private static readonly AsyncLocal<GenderType?> _currentGenderFilter = new AsyncLocal<GenderType?>(); // 新增序列化方法,负责设置和清理上下文 public void Serialize(Family family, Stream stream) { _currentGenderFilter.Value = GenderToInclude; try { Model.Serialize(stream, family); } finally { _currentGenderFilter.Value = null; // 必须清理,避免污染后续序列化操作 } } // 原FillModel方法调整:给FamilySurrogate的People字段开启SkipNulls protected virtual void FillModel() { Model = RuntimeTypeModel.Create(); Model.Add(typeof(Family), false) .SetSurrogate(typeof(FamilySurrogate)); Model[typeof(FamilySurrogate)] .Add(1, "People") .SkipNulls = true; // 关键配置:让protobuf-net跳过列表中的null元素 .UseConstructor = false; Model.Add(typeof(Person), false) .SetSurrogate(typeof(PersonSurrogate)); MetaType mt = Model[typeof(PersonSurrogate)] .Add(1, "Name") .Add(2, "Gender"); mt.UseConstructor = false; } }
2. 修改Person到PersonSurrogate的转换逻辑
在静态转换方法里,先获取当前的过滤条件,不符合就返回null:
public class PersonSurrogate { public string Name { get; set; } public byte Gender { get; set; } public PersonSurrogate(string name, byte gender) { Name = name; Gender = gender; } public static implicit operator Person(PersonSurrogate surrogate) { if (surrogate == null) return null; return new Person(surrogate.Name, (GenderType)surrogate.Gender); } public static implicit operator PersonSurrogate(Person source) { if (source == null) return null; // 获取当前的过滤条件 var genderFilter = FamilySerializer._currentGenderFilter.Value; if (genderFilter.HasValue) { var filter = genderFilter.Value; // 校验是否符合条件,不符合直接返回null if (!(filter == GenderType.Both || source.Gender == filter)) { return null; } } return new PersonSurrogate(source.Name, (byte)source.Gender); } }
这样一来,不符合条件的Person会被转换成null,再加上SkipNulls = true的配置,protobuf-net在序列化列表时就会自动跳过这些null元素,完美实现你的需求。
解决方案2:在Family代理中直接过滤列表
如果你的过滤逻辑是针对整个Family的People列表,这个方案更直接——在Family转换为代理的时候就把不符合条件的Person过滤掉,不需要依赖null处理:
public class FamilySurrogate { public List<PersonSurrogate> People { get; set; } // 从Family转换到代理时直接过滤 public static implicit operator FamilySurrogate(Family source) { if (source == null) return null; // 获取当前的过滤条件(同样用AsyncLocal传递) var genderFilter = FamilySerializer._currentGenderFilter.Value ?? GenderType.Both; return new FamilySurrogate { People = source.People .Where(p => genderFilter == GenderType.Both || p.Gender == genderFilter) .Select(p => (PersonSurrogate)p) .ToList() }; } // 反向转换回Family public static implicit operator Family(FamilySurrogate surrogate) { if (surrogate == null) return null; return new Family { People = surrogate.People.Select(ps => (Person)ps).ToList() }; } }
这个方案的好处是逻辑更集中,不需要给Person代理加额外判断,也不用配置SkipNulls,适合列表级别的批量过滤。
为什么不推荐用BeforeSerialize?
正如你发现的,BeforeSerialize是在代理实例已经创建后才触发的,此时protobuf-net已经把这个条目纳入了序列化流程,没有直接的API可以让你跳过它。即使你在方法里修改代理的属性,也只是改变序列化的内容,没法移除整个条目。所以从源头过滤才是更靠谱的方式。
内容的提问来源于stack exchange,提问作者ilCosmico

