Protobuf.NET - 当触发引用在列表中时出现 "Possible Recursion Detected" 问题
Protobuf.NET - Issue with "Possible Recursion Detected" when the triggering references are in a List
首先让我展示一个简单的问题测试用例以及如何触发它。这是 class:
class ProtoRecurseTest
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
}
没有 protobuf 注释,因为它是以编程方式处理的。我手动确保 class 连同 Back 和 Children 都添加到 .AsReferenceDefault = true 的架构中。
当一个实例填充到至少 8 的深度足够奇怪时,就会触发递归。 7 很好。人口代码很简单:
ProtoRecurseTest recurseTest = new ProtoRecurseTest();
ProtoRecurseTest recurseItem = recurseTest;
for (int i = 0; i < 8; i++)
recurseItem = recurseItem.Add();
然后序列化recurseTest。此行为仅在 children 在列表中时才会发生,但在列表中,即使每个列表只有 1 child 也会发生,因为您最终会从示例填充代码中得到。当我用单个引用替换 children 时,所有序列化都很好。
这是使用 ProtoBuf.NET 2.1.0.0.
这是一个解决方案,但我不是特别喜欢。它基于@marc-gravell 对 this 问题的回答。
class ProtoRecurseTest : ISerializationManagementCallbacks
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
static void PreSerializationHelper(ProtoRecurseTest instance)
{
instance.Back = null;
foreach (var child in instance.Children)
PreSerializationHelper(child);
}
public void BeforeSerialization()
{
PreSerializationHelper(this);
}
static void PostDeserializationHelper(ProtoRecurseTest instance, ProtoRecurseTest parent)
{
if (instance.Children == null)
instance.Children = new List<ProtoRecurseTest>();
instance.Back = parent;
foreach (var child in instance.Children)
PostDeserializationHelper(child, instance);
}
public void AfterDeserialization()
{
PostDeserializationHelper(this, null);
}
}
对 serialize/deserialize 的调用现在只需在执行业务之前检查类型是否可以转换为 ISerializationManagementCallbacks(它只是提供 BeforeSerialization 和 AfterDeserialization 方法),然后调用适当的方法(如果可以)。它工作正常。
但是,我真的不喜欢将更多的序列化问题混入我的代码库中 - 这实际上就是以编程方式生成模式的原因。理想情况下,我想完全分离序列化,但空列表 -> null 问题(并不是说它本身是 "issue" 而是 protobuf 标准的一个不受欢迎的部分)已经使这成为不可能,但这会是另一个需要解决的更深奥的问题,我确实认为这可能确实是一个问题。
首先让我展示一个简单的问题测试用例以及如何触发它。这是 class:
class ProtoRecurseTest
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
}
没有 protobuf 注释,因为它是以编程方式处理的。我手动确保 class 连同 Back 和 Children 都添加到 .AsReferenceDefault = true 的架构中。
当一个实例填充到至少 8 的深度足够奇怪时,就会触发递归。 7 很好。人口代码很简单:
ProtoRecurseTest recurseTest = new ProtoRecurseTest();
ProtoRecurseTest recurseItem = recurseTest;
for (int i = 0; i < 8; i++)
recurseItem = recurseItem.Add();
然后序列化recurseTest。此行为仅在 children 在列表中时才会发生,但在列表中,即使每个列表只有 1 child 也会发生,因为您最终会从示例填充代码中得到。当我用单个引用替换 children 时,所有序列化都很好。
这是使用 ProtoBuf.NET 2.1.0.0.
这是一个解决方案,但我不是特别喜欢。它基于@marc-gravell 对 this 问题的回答。
class ProtoRecurseTest : ISerializationManagementCallbacks
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
static void PreSerializationHelper(ProtoRecurseTest instance)
{
instance.Back = null;
foreach (var child in instance.Children)
PreSerializationHelper(child);
}
public void BeforeSerialization()
{
PreSerializationHelper(this);
}
static void PostDeserializationHelper(ProtoRecurseTest instance, ProtoRecurseTest parent)
{
if (instance.Children == null)
instance.Children = new List<ProtoRecurseTest>();
instance.Back = parent;
foreach (var child in instance.Children)
PostDeserializationHelper(child, instance);
}
public void AfterDeserialization()
{
PostDeserializationHelper(this, null);
}
}
对 serialize/deserialize 的调用现在只需在执行业务之前检查类型是否可以转换为 ISerializationManagementCallbacks(它只是提供 BeforeSerialization 和 AfterDeserialization 方法),然后调用适当的方法(如果可以)。它工作正常。
但是,我真的不喜欢将更多的序列化问题混入我的代码库中 - 这实际上就是以编程方式生成模式的原因。理想情况下,我想完全分离序列化,但空列表 -> null 问题(并不是说它本身是 "issue" 而是 protobuf 标准的一个不受欢迎的部分)已经使这成为不可能,但这会是另一个需要解决的更深奥的问题,我确实认为这可能确实是一个问题。