开发者

Composite iteration failure (.net)

开发者 https://www.devze.com 2023-02-19 00:54 出处:网络
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. B

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 InvalidOp开发者_如何转开发erationException(_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);
        }
    }


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消