防止 Parent/Child 层次结构中无限递归的防御代码
Defensive Code to prevent Infinite Recursion in Parent/Child Hierarchy
给定一个 Object
public class Thing
{
public Thing() { this.children = new List<Thing>();}
public int Id {get; set;}
public string Name {get; set;}
public List<Thing> children{ get; set;}
public string ToString(int level = 0)
{
//Level is added purely to add a visual hierarchy
var sb = new StringBuilder();
sb.Append(new String('-',level));
sb.AppendLine($"id:{Id} Name:{Name}");
foreach(var child in children)
{
sb.Append(child.ToString(level + 1));
}
return sb.ToString();
}
}
如果以这种方式使用(滥用!?)
public static void Main()
{
var root = new Thing{Id = 1,Name = "Thing1"};
var thing2 = new Thing{Id = 2,Name = "Thing2"};
var thing3 = new Thing{Id = 3,Name = "Thing3"};
root.children.Add(thing2);
thing2.children.Add(thing3);
thing3.children.Add(root); //problem is here
Console.WriteLine(root.ToString());
}
如何防御这种情况。
此代码会产生 Whosebug、无限递归或内存超出错误。
在 (IIS) 网站中,这导致 w3 工作进程崩溃,并最终关闭应用程序池(Rapid-Fail 保护)
以上代码仅用于重现问题。在实际场景中,该结构来自具有 Id 和 ParentId.
的数据库
数据库table结构类似于
CREATE TABLE Thing(
Id INT NOT NULL PRIMARY KEY,
Name NVARCHAR(255) NOT NULL,
ParentThingId INT NULL //References self
)
问题是用户创建 'things' 并不能阻止乱伦关系(即 Parent 可能有 children(谁可能有 children等等....最终再次指向parent。可以对数据库施加约束以防止事物不是它自己的parent(有意义),但这取决于深度可能会变得丑陋,并且有一些论点认为可能需要循环引用(我们仍在争论这个....)
所以可以说结构可以是圆形的,但是如果你想在网页上呈现这种结构,比如说 parent/child 菜单中的 <ul><li><a>
标签之类的东西,如何一个人变得积极主动地在代码中处理这个用户生成的数据问题?
.NET fiddle here
您可以展开树结构,方法是将每个元素放在堆栈或队列中,并在集合中有项目时从那里弹出项目。在 while 循环中,您将每个项目的子项放入队列中。
如果您关心项目在树中的级别,您可以使用存储它的辅助对象。
编辑:
展开树时,您可以将每个项目放在一个新列表中,并将其用作循环问题的参考。
一种方法是在递归调用中包含一组已访问节点。如果在你进入循环之前访问过。
public string ToString(int level = 0, HashSet<int> visited)
{
foreach(var child in children)
{
if(visited.Add(child.Id))
sb.Append(child.ToString(level + 1, visited));
else
//Handle the case when a cycle is detected.
}
return sb.ToString();
}
如果您可以 a) 消除想要循环引用的可能性,并且 b) 保证所有子级都知道该父级的创建时间,那么这是一个让子级成为 不可变对象的好机会 仅通过构造函数设置的集合。
这给了你一个 class,通过结构递归,你知道不能包含任何循环,无论整体结构有多大。类似于:
public sealed class Thing
{
public Thing(IEnumerable<Thing> children) {
this._children = children.ToList().AsReadOnly();
}
private readonly ReadOnlyCollection<Thing> _children;
public int Id {get; set;}
public string Name {get; set;}
public IEnumerable<Thing> children {
get {
return _children;
}
}
public string ToString(int level = 0)
{
//Level is added purely to add a visual hierarchy
var sb = new StringBuilder();
sb.Append(new String('-',level));
sb.AppendLine($"id:{Id} Name:{Name}");
foreach(var child in children)
{
sb.Append(child.ToString(level + 1));
}
return sb.ToString();
}
}
当然啦,我上面说的那些条件都比较大了"if",所以你要考虑是否适合自己。
给定一个 Object
public class Thing
{
public Thing() { this.children = new List<Thing>();}
public int Id {get; set;}
public string Name {get; set;}
public List<Thing> children{ get; set;}
public string ToString(int level = 0)
{
//Level is added purely to add a visual hierarchy
var sb = new StringBuilder();
sb.Append(new String('-',level));
sb.AppendLine($"id:{Id} Name:{Name}");
foreach(var child in children)
{
sb.Append(child.ToString(level + 1));
}
return sb.ToString();
}
}
如果以这种方式使用(滥用!?)
public static void Main()
{
var root = new Thing{Id = 1,Name = "Thing1"};
var thing2 = new Thing{Id = 2,Name = "Thing2"};
var thing3 = new Thing{Id = 3,Name = "Thing3"};
root.children.Add(thing2);
thing2.children.Add(thing3);
thing3.children.Add(root); //problem is here
Console.WriteLine(root.ToString());
}
如何防御这种情况。
此代码会产生 Whosebug、无限递归或内存超出错误。
在 (IIS) 网站中,这导致 w3 工作进程崩溃,并最终关闭应用程序池(Rapid-Fail 保护)
以上代码仅用于重现问题。在实际场景中,该结构来自具有 Id 和 ParentId.
的数据库数据库table结构类似于
CREATE TABLE Thing(
Id INT NOT NULL PRIMARY KEY,
Name NVARCHAR(255) NOT NULL,
ParentThingId INT NULL //References self
)
问题是用户创建 'things' 并不能阻止乱伦关系(即 Parent 可能有 children(谁可能有 children等等....最终再次指向parent。可以对数据库施加约束以防止事物不是它自己的parent(有意义),但这取决于深度可能会变得丑陋,并且有一些论点认为可能需要循环引用(我们仍在争论这个....)
所以可以说结构可以是圆形的,但是如果你想在网页上呈现这种结构,比如说 parent/child 菜单中的 <ul><li><a>
标签之类的东西,如何一个人变得积极主动地在代码中处理这个用户生成的数据问题?
.NET fiddle here
您可以展开树结构,方法是将每个元素放在堆栈或队列中,并在集合中有项目时从那里弹出项目。在 while 循环中,您将每个项目的子项放入队列中。
如果您关心项目在树中的级别,您可以使用存储它的辅助对象。
编辑:
展开树时,您可以将每个项目放在一个新列表中,并将其用作循环问题的参考。
一种方法是在递归调用中包含一组已访问节点。如果在你进入循环之前访问过。
public string ToString(int level = 0, HashSet<int> visited)
{
foreach(var child in children)
{
if(visited.Add(child.Id))
sb.Append(child.ToString(level + 1, visited));
else
//Handle the case when a cycle is detected.
}
return sb.ToString();
}
如果您可以 a) 消除想要循环引用的可能性,并且 b) 保证所有子级都知道该父级的创建时间,那么这是一个让子级成为 不可变对象的好机会 仅通过构造函数设置的集合。
这给了你一个 class,通过结构递归,你知道不能包含任何循环,无论整体结构有多大。类似于:
public sealed class Thing
{
public Thing(IEnumerable<Thing> children) {
this._children = children.ToList().AsReadOnly();
}
private readonly ReadOnlyCollection<Thing> _children;
public int Id {get; set;}
public string Name {get; set;}
public IEnumerable<Thing> children {
get {
return _children;
}
}
public string ToString(int level = 0)
{
//Level is added purely to add a visual hierarchy
var sb = new StringBuilder();
sb.Append(new String('-',level));
sb.AppendLine($"id:{Id} Name:{Name}");
foreach(var child in children)
{
sb.Append(child.ToString(level + 1));
}
return sb.ToString();
}
}
当然啦,我上面说的那些条件都比较大了"if",所以你要考虑是否适合自己。