Реализация дерева в С# для управления родителем-потомком

Я реализую дерево, думая об этом как о структуре папок, поэтому у меня есть класс, который выглядит так:

public class Folder
{
    //Various Props like Name etc.
    public IList<Folder> Children{get;}
    public Folder Parent {get;}
}

Теперь я хочу иметь возможность ходить вверх и вниз по дереву, чтобы, имея корень, я мог найти лист, а учитывая лист, я мог найти корневой узел. Поэтому каждому ребенку нужен родитель. Теперь вопрос в том, как лучше всего добавить новый узел в дерево. В прошлом я использовал два решения:

  1. Добавьте метод AddChild(Folder) в папку, который обрабатывает добавление папки и может установить родителя. Проблема в том, что теперь я должен заблокировать свою коллекцию Children, чтобы вы не могли обойти этот метод.
  2. Создайте мою собственную коллекцию Children, которой будет дана ссылка на экземпляр, чтобы она могла обрабатывать установку родителя в добавлении. Проблема в том, что я должен реализовать новую коллекцию.
  3. Используйте коллекцию, в которой есть события при добавлении или удалении элементов.

Мне любопытно, какие шаблоны обычно используют люди, а затем, если у кого-нибудь есть какие-либо предложения для моего конкретного случая использования. Я использую nHibernate для сохранения моего дерева на SQL-сервере. Я бы предпочел не реализовывать пользовательскую коллекцию, так как требуется много кода, чтобы заставить ее работать для чего-то, что является очень небольшой частью моего приложения.


person JoshBerke    schedule 13.01.2009    source источник
comment
Я ищу как nHibernate, так и более общие идеи... был хороший пост с коллекцией, которую кто-то удалил...   -  person JoshBerke    schedule 13.01.2009


Ответы (5)


Посмотрев в MSDN, вы можете попробовать это:

List<Folder> children;

public ReadOnlyCollection<Folder> Children
{
    get { return this.children.AsReadOnly(); }
}

Если ваш частный член должен быть объявлен как IList, мы можем скопировать его в список, а затем вернуть. Но я действительно не вижу проблемы с использованием конкретной реализации в качестве частного члена. Изменение реализации позже не нарушит совместимость.

IList<Folder> children;

public ReadOnlyCollection<Folder> Children
{
    get 
    { 
        return new List<Folder>(this.children).AsReadOnly(); 
    }
}
person Jack Ryan    schedule 13.01.2009
comment
Только если вы используете список ;-) IList не поддерживает это. - person JoshBerke; 13.01.2009
comment
Истинный. Но, насколько я вижу, нет интерфейса, определяющего список только для чтения. Таким образом, вы либо используете более общий IEnumerable, либо теряете индексацию. Или верните потомка ReadOnlyList и измените свою частную реализацию. Любое другое решение требует написания собственных классов/интерфейсов. - person Jack Ryan; 13.01.2009
comment
Я добавил новое решение, которое соответствует всем вашим требованиям :-) - person Jack Ryan; 13.01.2009

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

person Rob Prouse    schedule 13.01.2009

Лично я бы выбрал метод 1. Разрешение клиентскому коду напрямую манипулировать коллекцией Children в любом случае нарушает инкапсуляцию, поэтому «блокировка» коллекции Children — это правильно.

«Правильная» стратегия поддержания правильных отношений между узлами зависит от потребностей ваших клиентов. Я предполагаю, что в этой конкретной ситуации вы хотели бы, чтобы клиенты могли изменять сами дочерние узлы, но не коллекцию Children. Если это так, я думаю, что предложение Роба Проуса (заставить свойство Children возвращать IEnumerable), вероятно, является лучшим выбором. В других ситуациях лучше использовать ReadOnlyCollection.

person Harper Shelby    schedule 13.01.2009
comment
Итак, как бы вы помешали им изменить коллекцию? Какую стратегию вы используете? Коллекция только для чтения? Вернуть копию коллекции? - person JoshBerke; 13.01.2009
comment
@Josh: Один из способов вернуть IEnumerable: public IEnumerable‹Folder› Children { get { foreach (дочерняя папка в дочерних элементах) { yield return child; } } } - person SchaeferFFM; 14.01.2009

Реализация пользовательской коллекции требует много работы; реализация оболочки для существующего класса коллекции, которая предоставляет только два или три метода, не является таковой. Судя по вашему ответу JayArr, это то, что вы ищете. Что-то типа:

public class ChildCollection
{
   // _Children is maintained by the Folder class, hence the internal access specifier
   internal Dictionary<KeyType, Folder> _Children = new Dictionary<KeyType, Folder>;

   public this[KeyType key]
   {
      get
      {
          return _Children[key];
      }
   }

   public IEnumerable<KeyType> Keys
   {
      get
      {
         return _Children.Keys;
      }
   }
}
person Robert Rossney    schedule 13.01.2009

Я бы выбрал вариант 1, а затем сделал бы свойство Children таким:

    public IEnumerable<Folder> Children
    {
        get { return this.children.GetEnumerator(); }
    }

Теперь необходимо вызвать AddChild, чтобы добавить дочерние элементы. Коллекция недоступна.

person Jack Ryan    schedule 13.01.2009
comment
Хм, хорошо работает, пока клиент не хочет индексировать IList‹›... ломается, если ваш ребенок является словарем... - person JoshBerke; 13.01.2009