复合迭代失败 (.net)
下面是使用复合模式的第一次尝试。
它的工作原理是我可以任意嵌套并获得 Duration 属性的正确结果,这是合成的焦点。但是有一个编码问题,因为输出组合的 ToString() 所需的子代迭代失败:
System.InvalidOperationException : Collection was modified; enumeration operation may not execute.
此 发布,包括使用堆栈来避免递归和开销的发布 嵌套迭代器。
不过,我想首先更好地理解该模式,所以我这里有几个问题:
- 如何更改现有的迭代代码以防止此错误?我知道如何将其转换为 Linq 等效项,但我想将其保留为循环,直到我明白它出了什么问题。
- Composite 中是否通常提供 Count 属性,或者在迭代后以某种方式缓存计数?
- 在一般情况下,您不需要专门的集合,您的 Children 属性通常会是 IEnumerable、IList 或 List?
任何有关工作(非平凡).net 代码示例的良好链接也将不胜感激。
干杯,
贝里尔
代码
public interface IComponent {
void Adopt(IComponent node);
void Orphan(IComponent node);
TimeSpan Duration { get; }
IEnumerable<IComponent> Children { get; }
}
public class Allocation : Entity, IAllocationNode {
public void Adopt(IAllocationNode node) { throw new InvalidOperationException(_getExceptionMessage("Adopt", this, node)); }
public void Orphan(IAllocationNode node) { throw new InvalidOperationException(_getExceptionMessage("Orphan", this, node)); }
public IEnumerable<IAllocationNode> Allocations { get { return Enumerable.Empty<IAllocationNode>(); } }
public virtual TimeSpan Duration { get; set; }
}
class MyCompositeClass : IAllocationNode {
public MyCompositeClass() { _children = new List<IAllocationNode>(); }
public void Adopt(IAllocationNode node) { _children.Add(node); }
public void Orphan(IAllocationNode node) { _children.Remove(node); }
public TimeSpan Duration {
get {
return _children.Aggregate(TimeSpan.Zero, (current, child) => current + child.Duration);
}
}
public IEnumerable<IAllocationNode> Children {
get {
var result = _children;
foreach (var child in _children) {
var childOnes = child.Children;
foreach (var node in childOnes) {
result.Add(node);
}
}
return result;
}
}
private readonly IList<IAllocationNode> _children;
#endregion
public override string ToString() {
var count = Children.Count();
var hours = Duration.TotalHours.ToString("F2");
return string.Format("{0} allocations for {1} hours", count, hours);
}
}
Below is a first attempt at using a Composite pattern.
It works in the sense that I can arbitrarily nest and get the correct results for the Duration property, with is the focus of the composition. BUT has a coding problem in that the iteration over the children needed to output the composite's ToString() fails:
System.InvalidOperationException : Collection was modified; enumeration operation may not execute.
The are a few extension methods for GetDescendents in this posting, including one that uses a stack to avoid the expense of recursion and
nested iterators.
I would like to understand the pattern better first though, so I have a few questions here:
- How can I change the existing iteration code to prevent this error? I know how to convert it to a Linq equivalent but I want to leave it as the loops until I understand what is wrong with it.
- Is it typical in the Composite to provide a Count property, or somehow cache the count after an iteration?
- in the general case where you don't need a specialized collection, would you typically have your Children property be IEnumerable, IList, or List?
Any good links for examples of working (non-trival) .net code would also be much appreciated.
Cheers,
Berryl
CODE
public interface IComponent {
void Adopt(IComponent node);
void Orphan(IComponent node);
TimeSpan Duration { get; }
IEnumerable<IComponent> Children { get; }
}
public class Allocation : Entity, IAllocationNode {
public void Adopt(IAllocationNode node) { throw new InvalidOperationException(_getExceptionMessage("Adopt", this, node)); }
public void Orphan(IAllocationNode node) { throw new InvalidOperationException(_getExceptionMessage("Orphan", this, node)); }
public IEnumerable<IAllocationNode> Allocations { get { return Enumerable.Empty<IAllocationNode>(); } }
public virtual TimeSpan Duration { get; set; }
}
class MyCompositeClass : IAllocationNode {
public MyCompositeClass() { _children = new List<IAllocationNode>(); }
public void Adopt(IAllocationNode node) { _children.Add(node); }
public void Orphan(IAllocationNode node) { _children.Remove(node); }
public TimeSpan Duration {
get {
return _children.Aggregate(TimeSpan.Zero, (current, child) => current + child.Duration);
}
}
public IEnumerable<IAllocationNode> Children {
get {
var result = _children;
foreach (var child in _children) {
var childOnes = child.Children;
foreach (var node in childOnes) {
result.Add(node);
}
}
return result;
}
}
private readonly IList<IAllocationNode> _children;
#endregion
public override string ToString() {
var count = Children.Count();
var hours = Duration.TotalHours.ToString("F2");
return string.Format("{0} allocations for {1} hours", count, hours);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
发生异常的原因是
Children
属性的 getter 中的代码在迭代集合时修改集合。您似乎认为代码
创建了
_children
字段引用的列表的副本。事实并非如此,它只是将列表的引用复制到变量中(这就是字段的值所代表的内容)。复制列表的一个简单修复方法是:
当前代码的 LINQ 等效项应该以惰性方式工作,是:
编辑:
我最初的印象是您的代码将遍历深度限制为两个级别(子级和孙级),但现在我发现事实并非如此:确实存在对属性的递归调用 em>
Children
而不仅仅是字段_children
的值。这种命名非常令人困惑,因为属性和“支持”字段代表完全不同的事物。我强烈建议您将该属性重命名为更有意义的名称,例如Descendants
。The exception is occurring because the code in the
Children
property's getter is modifying a collection while iterating over it.You appear to be under the impression that the code
creates a copy of the list referred to by the
_children
field. It does not, it just copies the reference to the list (which is what the value of the field represents) to the variable.An easy fix to copy the list over is to instead do:
The LINQ equivalent of your current code, which should work in a lazy manner, is:
EDIT:
I was originally under the impression that your code was limiting the traversal-depth to two levels (children and grandchildren), but now I can see that this is not the case: there is indeed a recursive call to the property
Children
rather than just the value of the field_children
. This naming is quite confusing because the property and the 'backing' field represent different things entirely. I strongly recommend that you rename the property to something more meaningful, such asDescendants
.