моделирование дружбы C ++ на C # с частичными классами

У меня такая ситуация:

Мне нужно создать древовидную структуру, которая после создания экземпляра является неизменной. Подумайте, например, о подходе со списком. Проблема в том, что во время строительства мне нужно иметь возможность вставлять узлы. Поэтому мне нужен какой-то частный интерфейс, который позволяет вставлять узлы, в то время как открытый интерфейс этой структуры позволяет только их извлекать. Я пробовал это:

public partial class TreeLike {
  public SomeType1 SomeTreeProperty1 { get; private set; }
  public SomeType2 SomeTreeProperty2 { get; private set; }

  private List<NodeLike> _Nodes = new List<NodeLike>();
  public ReadOnlyCollection<NodeLike> Nodes {
    get {
      return _Nodes.AsReadOnly();
    }
  }
}

public class NodeLike {
  public SomeType1 SomeNodeProperty { get; private set; }

  private List<NodeLike> _Children = new List<NodeLike>();
  public ReadOnlyCollection<NodeLike> Children {
    get {
      return _Children.AsReadOnly();
    }
  }

  public partial class TreeLike {
    public SomeType3 SomeTreeProperty3 { get; private set; }

    TreeLike() {
      NodeLike n0 = new NodeLike();
      NodeLike n1 = new NodeLike();
      n0._Children.Add(n1);
      _Nodes.Add(n0);
    }
  }
}

Проблема с этим (помимо несколько хакерского вида продолжения объявления / определения TreeLike внутри NodeLike) заключается в том, что пока он работает, SomeTreeProperty3 из TreeLike не виден внешнему миру. То есть, если я создаю экземпляр TreeLike в самой внешней области, я могу получить доступ только к первым свойствам, которые объявлены в объявлении «глобальной» области частичного класса.

Поэтому мне интересно, есть ли способ, чтобы свойства и методы, объявленные во вложенном продолжении частичного класса, оставались видимыми для глобальной области (и, следовательно, для клиентов этого класса). Или, если нет, что может быть лучше C # -идиоматический способ сделать это? Может быть, создать неизменяемые версии классов?


person SaldaVonSchwartz    schedule 17.07.2014    source источник
comment
protected (в отличие от частного) не делает то, что вы хотите?   -  person Alain    schedule 18.07.2014
comment
что ты имеешь в виду? сделать списки защищенными? эти классы не имеют отношения наследования. Я вас неправильно понимаю?   -  person SaldaVonSchwartz    schedule 18.07.2014
comment
Почему бы просто не дать NodeLike конструктор, который берет другой NodeLike и добавляет его как дочерний?   -  person Mike Zboray    schedule 18.07.2014
comment
TreeLike и NodeLike.TreeLike - два разных класса. Модификатор partial в этом случае ничего не делает.   -  person Groo    schedule 18.07.2014
comment
Извините, я перепутал защищенный с внутренним. Есть ли причина, по которой это не сработает?   -  person Alain    schedule 18.07.2014
comment
@Groo ok, это объясняет, почему. Имеет смысл. Я думал, что вложение класса с частичным + ранее введенным идентификатором подразумевает продолжение в другой области / пространстве имен (который включает класс).   -  person SaldaVonSchwartz    schedule 18.07.2014
comment
@Alain internal предназначен для сборок. Даже когда клиенты этих классов находятся в разных файлах, все файлы в конечном итоге превратятся в одну и ту же сборку, поэтому это нарушит цель, поскольку я не отправляю эти 2 класса как отдельные динамические библиотеки или что-то в этом роде.   -  person SaldaVonSchwartz    schedule 18.07.2014
comment
@mikez да, на данный момент я тоже думаю о шаблоне строителя ... но мне просто интересно, есть ли другой способ обойти это.   -  person SaldaVonSchwartz    schedule 18.07.2014
comment
@SaldaVonSchwartz Это даже не шаблон строителя. Это просто новый конструктор. Здесь не нужен дополнительный изменяемый конструктор. Ответы, опубликованные Groo и BartoszKP, используют ту же идею.   -  person Mike Zboray    schedule 18.07.2014


Ответы (2)


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

Например, если интерфейс вашего узла определен следующим образом:

interface INode
{
    public string Name { get; }
    public ReadOnlyCollection<INode> Children { get; }
}

Тогда реализация должна быть просто:

class Node : INode
{
    private readonly string _name;
    public string Name
    {
        get { return _name; }
    }

    private readonly ReadOnlyCollection<INode> _children;
    public ReadOnlyCollection<INode> Children
    {
        get { return _children; }
    }

    public Node(string name, IEnumerable<INode> children)
    {
        _name = name;
        _children = new List<INode>(children).AsReadOnly();
    }

    public Node(string name, params INode[] children)
        : this(name, (IEnumerable<INode>)children)
    { }
}

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

var root = new Node(
    "root",
    new Node("left",
        new Node("left-left"),
        new Node("left-right")),
    new Node("right",
        new Node("right-left"),
        new Node("right-right"))
    );
person Groo    schedule 17.07.2014

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

public partial class TreeLike {
    public SomeType1 SomeTreeProperty1 { get; private set; }
    public SomeType2 SomeTreeProperty2 { get; private set; }
    public SomeType3 SomeTreeProperty3 { get; private set; }

    private List<NodeLike> _Nodes = new List<NodeLike>();
    public ReadOnlyCollection<NodeLike> Nodes {
        get {
            return _Nodes.AsReadOnly();
        }
    }

    TreeLike() {
        NodeLike n1 = new NodeLike();
        NodeLike n0 = new NodeLike(n1);

        _Nodes.Add(n0);
    }
}

public class NodeLike {
    public SomeType1 SomeNodeProperty { get; private set; }

    private List<NodeLike> _Children = new List<NodeLike>();
    public ReadOnlyCollection<NodeLike> Children {
        get {
            return _Children.AsReadOnly();
    }

    public NodeLike(params NodeLike[] children) {
        _Children = children.ToList();
    }
}

partial не имеет отношения к этой проблеме. params - полезный синтаксис, который также позволяет делать такие вещи:

new NodeLike(childNode1, childNode2, childNode3);

Примечание: египетские фигурные скобки не приветствуются в C #.

person BartoszKP    schedule 17.07.2014