0

I have the following situation:

I need to create a tree-like structure that once instantiated, is immutable .Think a list of list approach for instance. The problem is that during construction, I need to be able to insert nodes. So I need some sort of private interface that allows inserting nodes while the public interface of this structure only allows retrieving them. I tried this:

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

The problem with this (besides the somewhat hackysh look of continuing TreeLike's declaration/definition inside NodeLike), is that while it works, SomeTreeProperty3of TreeLike is not visible to the outside world. That is, if I create an instance of TreeLike at the outmost scope, I can only access the first to properties which are declared in the "global" scope declaration of the partial class.

So I'm wondering if there's way to have properties and methods declared in the nested continuation of the partial class still be visible to the global scope (and therefore, clients of this class). Or if not, what would a better C#-idiomatic way to go about it be? Maybe creating immutable versions of the classes?

SaldaVonSchwartz
  • 3,769
  • 2
  • 41
  • 78
  • Does [protected](http://msdn.microsoft.com/en-us/library/bcd5672a.aspx) (as opposed to private) not do what you want? – Alain Jul 17 '14 at 21:07
  • what do you mean? making the lists protected? these classes do not have an inheritance relationship. Am I misunderstanding you? – SaldaVonSchwartz Jul 17 '14 at 21:09
  • 2
    Why not just give `NodeLike` a constructor that takes another `NodeLike` and adds it as a child? – Mike Zboray Jul 17 '14 at 21:09
  • 3
    `TreeLike` and `NodeLike.TreeLike` are two different classes. The `partial` modifier does nothing in this case. – vgru Jul 17 '14 at 21:10
  • I'm sorry, I confused protected with [internal](http://msdn.microsoft.com/en-us/library/7c5ka91b.aspx). Is there a reason why that wouldn't work? – Alain Jul 17 '14 at 21:11
  • @Groo ok that explains why. Makes sense. I thought nesting a class with partial + a previously introduced identifier implied a continuation in a different scope /namespace (that enclosing class). – SaldaVonSchwartz Jul 17 '14 at 21:21
  • @Alain internal is for assemblies. Even when clients of these classes are in different files, all files will eventually make it into the same assembly, so it would defeat the purpose, since it's not like I'm shipping these 2 classes as separate dynamic lib or anything like that. – SaldaVonSchwartz Jul 17 '14 at 21:23
  • @mikez yeah at this point I'm thinking a builder pattern too.. but was just wondering if there's a different way around it. – SaldaVonSchwartz Jul 17 '14 at 21:23
  • @SaldaVonSchwartz It's not even a builder pattern. It's just a new constructor. There's no additional mutable builder needed here. The answers posted by Groo and BartoszKP are using the same idea. – Mike Zboray Jul 17 '14 at 21:26

2 Answers2

1

You should simply perform all initialization inside constructors, because it allows you to achive true immutability using readonly fields.

For example, if your node interface is defined like this:

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

Then an implementation should simply be:

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

That last constructor overload uses the params keyword to allow you to pass child nodes directly through the constructor, which means you can do this:

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"))
    );
vgru
  • 49,838
  • 16
  • 120
  • 201
0

A possible solution to your design requirements could look simply like this:

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 is unrelated to this problem. params is a helpful syntax, that lets you also do things like this:

new NodeLike(childNode1, childNode2, childNode3);

Side-note: egyptian braces are discouraged in C#.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130