Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You piqued my curiousity. The class for a ReadOnlyNode is simple enough to define:</p> <pre><code>public class ReadOnlyNode&lt;T&gt; { public readonly T Value; public readonly ReadOnlyNode&lt;T&gt; Next; public readonly ReadOnlyNode&lt;T&gt; Prev; public Node(T value, ReadOnlyNode&lt;T&gt; next, ReadOnlyNode&lt;T&gt; prev) { Value = value; Next = next; Prev = prev; } } </code></pre> <p>The problem with <code>readonly</code> in a doubly-linked list is that, for each node, you have to specify that node's previous and next nodes in the constructor, so if they're passed from outside the constructor they must already exist. But, Node M needs a pre-existing Node N as its "next" node when you call the constructor, but that node N needs M as its "previous" node in order to be constructed. This creates a "chicken and egg" situation where both N and M need the other node to be instantiated first.</p> <p>However, there's more than one way to skin this cat. What if each node of a list was instantiated recursively from within the constructor of one ReadOnlyNode? Until each constructor was complete, the properties at each level would still be mutable, and the reference to each Node would exist in its constructor, so it wouldn't matter that not everything had been set up until everything is set up. The following code compiles, and given a pre-existing IEnumerable will produce an immutable doubly-linked list:</p> <pre><code>public class ReadOnlyNode&lt;T&gt; { public readonly T Value; public readonly ReadOnlyNode&lt;T&gt; Next; public readonly ReadOnlyNode&lt;T&gt; Prev; private ReadOnlyNode(IEnumerable&lt;T&gt; elements, ReadOnlyNode&lt;T&gt; prev) { if(elements == null || !elements.Any()) throw new ArgumentException( "Enumerable must not be null and must have at least one element"); Next = elements.Count() == 1 ? null : new ReadOnlyNode&lt;T&gt;(elements.Skip(1), this); Value = elements.First(); Prev = prev; } public ReadOnlyNode(IEnumerable&lt;T&gt; elements) : this(elements, null) { } } //Usage - creates an immutable doubly-linked list of integers from 1 to 1000 var immutableList = new ReadOnlyNode&lt;int&gt;(Enumerable.Range(1,1000)); </code></pre> <p>You can use this with any collection that implements IEnumerable (pretty much all built-in collections do, and you can use OfType() to turn non-generic ICollections and IEnumerables into generic IEnumerables). The only thing to worry about is the call stack; there is a limit to how many method calls you can nest, which may cause an SOE on a finite but large list of inputs.</p> <p><strong>EDIT:</strong> JaredPar brings up a very good point; this solution uses Count() and Any() which have to take the results of Skip() into account, and so cannot use the "shortcuts" built into these methods that can use the cardinality property of a collection class. Those calls become linear, which squares the complexity of the algorithm. If you just use the basic members of IEnumerable instead, this becomes much more performant:</p> <pre><code>public class ReadOnlyNode&lt;T&gt; { public readonly T Value; public readonly ReadOnlyNode&lt;T&gt; Next; public readonly ReadOnlyNode&lt;T&gt; Prev; private ReadOnlyNode(IEnumerator&lt;T&gt; elements, ReadOnlyNode&lt;T&gt; prev, bool first) { if (elements == null) throw new ArgumentNullException("elements"); var empty = false; if (first) empty = elements.MoveNext(); if(!empty) { Value = elements.Current; Next = elements.MoveNext() ? new ReadOnlyNode&lt;T&gt;(elements, this, false) : null; Prev = prev; } } public ReadOnlyNode(IEnumerable&lt;T&gt; elements) : this(elements.GetEnumerator(), null, true) { } } </code></pre> <p>With this solution, you lose a little of the more elegant error-checking, but if the IEnumerable is null an exception would have been thrown anyway.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload