复合迭代失败 (.net)

发布于 2024-10-23 15:41:56 字数 2663 浏览 5 评论 0原文

下面是使用复合模式的第一次尝试。

它的工作原理是我可以任意嵌套并获得 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

維他命╮ 2024-10-30 15:41:56

如何更改现有的
迭代代码来防止此错误?

发生异常的原因是 Children 属性的 getter 中的代码在迭代集合时修改集合。

您似乎认为代码

var result = _children;

创建了 _children 字段引用的列表的副本。事实并非如此,它只是将列表的引用复制到变量中(这就是字段的值所代表的内容)。

复制列表的一个简单修复方法是:

var result = _children.ToList();

我知道如何将其转换为 Linq
等价。

当前代码的 LINQ 等效项应该以惰性方式工作,是:

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

编辑:
我最初的印象是您的代码将遍历深度限制为两个级别(子级和孙级),但现在我发现事实并非如此:确实存在对属性的递归调用 em> Children 而不仅仅是字段 _children 的值。这种命名非常令人困惑,因为属性和“支持”字段代表完全不同的事物。我强烈建议您将该属性重命名为更有意义的名称,例如Descendants

How can I change the existing
iteration code to prevent this error?

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

var result = _children;

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:

var result = _children.ToList();

I know how to convert it to a Linq
equivalent.

The LINQ equivalent of your current code, which should work in a lazy manner, is:

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

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 as Descendants.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文