C# 使用自引用字典序列化标记为AsReferenceDefault的类时protobuf net中出现Stackoverflow异常
我使用protobuf net将方向图序列化为文件 我的(简化)课程如下:C# 使用自引用字典序列化标记为AsReferenceDefault的类时protobuf net中出现Stackoverflow异常,c#,.net-4.5,protobuf-net,C#,.net 4.5,Protobuf Net,我使用protobuf net将方向图序列化为文件 我的(简化)课程如下: [ProtoContract] public class Network { // All of the devices on the network [ProtoMember(1)] public readonly Dictionary<int, Device> Vertex; // The list of connections [ProtoMember(2)]
[ProtoContract]
public class Network
{
// All of the devices on the network
[ProtoMember(1)]
public readonly Dictionary<int, Device> Vertex;
// The list of connections
[ProtoMember(2)]
public readonly Dictionary<int, List<Device>> Nodes;
}
[ProtoContract(AsReferenceDefault = true)]
public class Device
{
[ProtoMember(1)]
public readonly int Id;
// All the devices with a direct path to this node
[ProtoMember(2)]
public readonly Dictionary<int, Device> PathTo;
// All the devices directly reachable from this node
[ProtoMember(3)]
public readonly Dictionary<int, Device> PathFrom;
// the nodes that this is connected to
[ProtoMember(4)]
public readonly int[] Nodes = new int[2];
}
[协议]
公共班级网络
{
//网络上的所有设备
[原成员(1)]
公共只读词典;
//连接列表
[原成员(2)]
公共只读字典节点;
}
[协议(AsReferenceDefault=true)]
公共类设备
{
[原成员(1)]
公共只读int-Id;
//具有到此节点的直接路径的所有设备
[原成员(2)]
公共只读字典路径;
//可从此节点直接访问的所有设备
[原成员(3)]
公共只读字典路径;
//连接到的节点
[原成员(4)]
公共只读int[]节点=新int[2];
}
如果我尝试使用protobuf net序列化网络
类的实例,则只有当设备的路径到
和路径从
字典为空时,它才会起作用
一旦我开始为每个设备
(注意图形的方向)填充这些字典,尝试使用protobuf net序列化会导致堆栈溢出
有人知道为什么会满溢吗
我已经通读了这个问题:,根据Mark对答案的编辑,他通过添加AsReferenceDefault属性修复了这个图形引用,我在设备
类中使用了这个属性
在我看来,它似乎在遍历列表时将词典中的所有元素都视为唯一的个体。考虑到网络中约有300万台设备,这很快会导致堆栈溢出 调试窗口堆栈的屏幕截图:
()作为优化的一部分,我调整了存储边的方式,因此我还修改了数据结构,以便从对象引用中删除循环 我创建了
Edge
类来跟踪对象之间的所有边和边的方向,并向图中添加了一个OnDeserialized
方法,以便它将遍历所有边,并将所有引用重新添加到每个链接类中
此外,该数据结构还可以快速保存/加载(对于约3000000个顶点,加载约5000000条边需要约18秒,而对于旧结构,则需要约30秒),并且遍历速度更快(对于完整遍历,需要3秒;对于旧结构,需要约50秒来修改链接)
[协议]
公共类图
{
//图表上的所有设备
[原成员(1)]
公共只读词典;
//图中的所有链接
[原成员(2)]
公共只读列表链接;
[已序列化]
public void OnDeserialized(){
//添加对所有链接的引用
foreach(链接中的边l){
l、 顶点[0]=this.Vertex[l.VertexIds[0]];
l、 顶点[1]=this.Vertex[l.VertexIds[1]];
}
}
}
//图的顶点
[原始合同]
公共类顶点
{
[原成员(1)]
公共只读int-Id;
/*
我们通过另一个设备的ID对边缘进行索引。
这样可以很容易地找到特定的边,并且只跟踪边的一个副本也很简单
*/
[原成员(2)]
公共只读词典边缘;
}
//边缘
[协议(AsReferenceDefault=true)]
公共阶级边缘
{
公共只读顶点[]个顶点;
[原成员(1)]
公共只读int[]VertexIds;
[原成员(2)]
公共只读方向;
公共边缘(){
这个。顶点=新顶点[2];
this.Direction=Direction.None;
/*
重要的是要注意,我们不在此构造函数中设置VertexIds数组。
protobuf net将为我们创建它,如果我们在这里创建它,我们将得到一个4长度的数组。
*/
}
}
/*
指示边缘的设备数组中的哪个索引以及边缘的方向。
[标志]这样我们可以更轻松地进行检查:
Edge.Direction==方向。两者都表示Edge.Direction和Direction.ZeroToOne!=0
*/
[旗帜]
公共枚举方向{
无=0,
零通=1,
OneToZero=2,
两者均=3
}
您是否有可以共享的stackoverflow stacktrace的重复部分?应该是[ProtoContract(AsReferenceDefault=true)]
?@marcGravel应该很抱歉,输入错误。@KirkWoll调试窗口堆栈的屏幕截图@marcGravel刚刚在这里显示,但什么时候用其唯一标识符标记对象?一旦遍历完对象的属性,或者当它第一次遇到对象时?因为如果是前者,那么这就可以解释为什么会有堆栈溢出——因为在遍历完字典之前,它无法将对象标记为唯一的,所有字典都会相互循环。
[ProtoContract]
public class Graph
{
// All of the devices on the graph
[ProtoMember(1)]
public readonly Dictionary<int, Vertex> Vertex;
// All of the links in the graph
[ProtoMember(2)]
public readonly List<Edge> Links;
[OnDeserialized]
public void OnDeserialized() {
// Add references to all of the links
foreach (Edge l in Links) {
l.Vertices[0] = this.Vertex[l.VertexIds[0]];
l.Vertices[1] = this.Vertex[l.VertexIds[1]];
}
}
}
// A vertex of the graph
[ProtoContract]
public class Vertex
{
[ProtoMember(1)]
public readonly int Id;
/*
we index the edges by the ID of the other device.
It makes it easy to find specific edges and makes it trivial to only track 1 copy of an edge
*/
[ProtoMember(2)]
public readonly Dictionary<int, Edge> Edges;
}
// An edge
[ProtoContract(AsReferenceDefault = true)]
public class Edge
{
public readonly Vertex[] Vertices;
[ProtoMember(1)]
public readonly int[] VertexIds;
[ProtoMember(2)]
public readonly Direction Direction;
public Edge() {
this.Vertices = new Vertex[2];
this.Direction = Direction.None;
/*
important to note that we don't set the VertexIds array in this constructor.
protobuf-net will create it for us, and if we create it here, we'll end up with a 4 length array.
*/
}
}
/*
denotes to which index in a edge's devices array the direction the edge goes.
[Flags] so we can do checks easier:
Edge.Direction == Direction.Both implies Edge.Direction & Direction.ZeroToOne != 0
*/
[Flags]
public enum Direction {
None = 0,
ZeroToOne = 1,
OneToZero = 2,
Both = 3
}