2011-03-20 76 views
1

下面是使用复合模式的第一次尝试。复合迭代失败(.net)

它的工作原理是,我可以随意嵌套并获得Duration属性的正确结果,并且是合成的焦点。但在一个编码问题迭代超过所需输出复合的ToString()孩子失败:

System.InvalidOperationException : Collection was modified; enumeration operation may not execute. 

的是在这个posting GetDescendents一些扩展方法,包括使用堆栈,以避免一个花费递归和嵌套迭代器。

我想了解的图案最好先的,所以我有几个问题在这里:

  • 我怎样才能改变现有的重复代码,以防止这种错误?我知道如何将它转换为Linq等价物,但我想把它作为循环直到我明白它有什么问题。
  • 在Composite中提供Count属性还是以某种方式缓存迭代后的计数是典型的?
  • 在您不需要专门收藏的一般情况下,您通常会将您的Children属性设置为IEnumerable,IList还是List?

任何工作(非trival).net代码示例的良好链接也将非常感激。

干杯,
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); 
     } 
    } 

回答

3

我怎样才能改变现有的 迭代代码,以防止这种错误?

异常发生,因为在Children属性的getter代码被修改的集合循环访问它。

你似乎有这样一种印象,代码

var result = _children; 

创建列表_children场称为下副本。它没有,它只是将参考复制到变量的列表(这是该字段的值代表什么)。

一个简单的办法,以列表拷贝过来是不是做的事:

var result = _children.ToList(); 

我知道如何将它转换成一个LINQ 等同。

的LINQ相当于您当前密码的,应在一个懒惰的方式工作,就是:

return _children.Concat(_children.SelectMany(child => child.Children)); 

编辑: 我本来是你的代码被限制遍历深度的印象两个级别(子女和孙子女),但现在我可以看到情况并非如此:确实有递归调用属性Children而不仅仅是字段_children的值。这个命名非常混乱,因为属性和'后援'字段完全代表不同的事情。我强烈建议您将该属性重命名为更有意义的内容,例如Descendants

+0

@Ari。是的,这解决了它。我没有看到我限制遍历的深度 - 你将如何修改这个以获得所有的后代? – Berryl 2011-03-20 15:13:52

+0

拼写错了你的名字,对不起。我没有看到我在限制遍历的深度 - 你会如何修改这个以获得所有的后代?干杯 – Berryl 2011-03-20 15:20:22

+0

@Berryl:你说得对;我已经进行了编辑。 – Ani 2011-03-20 15:21:38