Ошибка составной итерации (.net) - PullRequest
1 голос
/ 20 марта 2011

Ниже приведена первая попытка использования составного паттерна.

Это работает в том смысле, что я могу произвольно вкладывать и получать правильные результаты для свойства Duration с фокусом композиции. НО проблема кодирования в том, что итерация дочерних элементов, необходимых для вывода ToString () композита, завершается неудачно:

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

Это несколько методов расширения для GetDescendents в этой публикации , включая тот, который использует стек, чтобы избежать затрат на рекурсию и вложенные итераторы.

Хотелось бы сначала лучше понять шаблон, поэтому у меня есть несколько вопросов здесь:

  • Как я могу изменить существующий код итерации, чтобы предотвратить эту ошибку? Я знаю, как преобразовать его в эквивалент Linq, но я хочу оставить его как цикл, пока не пойму, что с ним не так.
  • Типично в Composite предоставлять свойство Count или каким-то образом кэшировать счет после итерации?
  • В общем случае, когда вам не нужна специализированная коллекция, вы обычно хотите, чтобы свойство Children было IEnumerable, IList или List?

Любые хорошие ссылки с примерами рабочего (нетривиального) кода .net также приветствуются.

Приветствия
Berryl

код

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);
        }
    }

1 Ответ

3 голосов
/ 20 марта 2011

Как изменить существующий код итерации, чтобы предотвратить эту ошибку?

Исключение происходит, поскольку код в получателе свойства Children изменяет коллекцию , в то время как итерируя по нему.

Похоже, у вас сложилось впечатление, что код

var result = _children;

создает копию списка , на который ссылается _children поле.Это не так, он просто копирует ссылку в список (который представляет значение поля) в переменную.

Простое исправление для копирования списка - вместо этогоdo:

var result = _children.ToList();

Я знаю, как преобразовать его в эквивалент Linq.

Эквивалент LINQ вашего текущего кода, который должен работать лениво,это:

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

РЕДАКТИРОВАТЬ: Первоначально у меня было впечатление, что ваш код ограничивает глубину обхода до двух уровней (дети и внуки), но теперь я вижу, что это не так: тамдействительно является рекурсивным вызовом свойства Children, а не просто значением поля _children.Это наименование сбивает с толку, потому что свойство и поле 'backing' представляют разные вещи полностью.Я настоятельно рекомендую переименовать свойство в нечто более значимое, например Descendants.

...