@@ -12,22 +12,65 @@ public sealed partial class Node<T> : INode<Node<T>>
1212
1313 #region IParent<Node<T>> Implementation
1414 private readonly List < Node < T > > _children ;
15+ private readonly IReadOnlyList < Node < T > > _childrenReadOnly ;
16+
1517 /// <inheritdoc />
16- public IReadOnlyList < Node < T > > Children { get ; }
18+ public IReadOnlyList < Node < T > > Children => EnsureChildrenMapped ( ) ;
1719 /// <inheritdoc />
18- IReadOnlyList < object > IParent . Children => Children ;
20+ IReadOnlyList < object > IParent . Children => EnsureChildrenMapped ( ) ;
1921 #endregion
2022
23+ private bool _recycled ;
24+ void AssertNotRecycled ( )
25+ {
26+ if ( _recycled )
27+ throw new InvalidOperationException ( "Attempting to modify a node that has been recyled." ) ;
28+ }
29+
2130 // ReSharper disable once UnusedAutoPropertyAccessor.Global
2231 /// <summary>
2332 /// The value for the node to hold on to.
2433 /// </summary>
25- public T Value { get ; set ; }
34+ private T _value ;
35+ public T Value
36+ {
37+ get => _value ;
38+ set
39+ {
40+ AssertNotRecycled ( ) ;
41+ _needsMapping = false ;
42+ _value = value ;
43+ }
44+ }
45+
46+ private readonly Factory _factory ;
47+ private bool _needsMapping ;
48+
49+ IReadOnlyList < Node < T > > EnsureChildrenMapped ( )
50+ {
51+ if ( ! _needsMapping )
52+ return _childrenReadOnly ;
53+
54+ // Need to avoid double mapping and this method is primarily called when 'reading' from the node and contention will only occur if mapping is needed.
55+ lock ( _children )
56+ {
57+ if ( ! _needsMapping ) return _childrenReadOnly ;
58+ if ( _value is IParent < T > p )
59+ {
60+ foreach ( var child in p . Children )
61+ _children . Add ( _factory . Map ( child ) ) ;
62+ }
63+ _needsMapping = false ;
64+ }
65+
66+ return _childrenReadOnly ;
67+ }
2668
27- private Node ( )
69+ internal Node ( Factory factory )
2870 {
71+ _factory = factory ?? throw new ArgumentNullException ( nameof ( factory ) ) ;
2972 _children = new List < Node < T > > ( ) ;
30- Children = _children . AsReadOnly ( ) ;
73+ _childrenReadOnly = _children . AsReadOnly ( ) ;
3174 }
3275
3376 // WARNING: Care must be taken not to have duplicate nodes anywhere in the tree but having duplicate values are allowed.
@@ -52,6 +95,9 @@ public bool Remove(Node<T> node)
5295 Contract . EndContractBlock ( ) ;
5396
5497 if ( ! _children . Remove ( node ) ) return false ;
98+
99+ AssertNotRecycled ( ) ;
100+ _needsMapping = false ;
55101 node . Parent = null ; // Need to be very careful about retaining parent references as it may cause a 'leak' per-se.
56102 return true ;
57103 }
@@ -68,13 +114,20 @@ public void Add(Node<T> node)
68114 throw new InvalidOperationException ( "Adding a child node more than once." ) ;
69115 throw new InvalidOperationException ( "Adding a node that belongs to another parent." ) ;
70116 }
117+
118+ AssertNotRecycled ( ) ;
119+ EnsureChildrenMapped ( ) ; // Adding to potentially existing nodes.
71120 node . Parent = this ;
72121 _children . Add ( node ) ;
73122 }
74123
75124 /// <inheritdoc />
76125 public void Clear ( )
77126 {
127+ if ( _children . Count == 0 ) return ;
128+
129+ AssertNotRecycled ( ) ;
130+ _needsMapping = false ;
78131 foreach ( var c in _children )
79132 c . Parent = null ;
80133 _children . Clear ( ) ;
@@ -86,15 +139,18 @@ public void Clear()
86139
87140 /// <inheritdoc />
88141 public IEnumerator < Node < T > > GetEnumerator ( )
89- => _children . GetEnumerator ( ) ;
142+ => Children . GetEnumerator ( ) ;
90143
91144 /// <inheritdoc />
92145 IEnumerator IEnumerable . GetEnumerator ( )
93146 => GetEnumerator ( ) ;
94147
95148 /// <inheritdoc />
96149 public void CopyTo ( Node < T > [ ] array , int arrayIndex )
97- => _children . CopyTo ( array , arrayIndex ) ;
150+ {
151+ EnsureChildrenMapped ( ) ;
152+ _children . CopyTo ( array , arrayIndex ) ;
153+ }
98154 #endregion
99155
100156 /// <summary>
@@ -113,6 +169,9 @@ public void Replace(Node<T> node, Node<T> replacement)
113169 var i = _children . IndexOf ( node ) ;
114170 if ( i == - 1 )
115171 throw new InvalidOperationException ( "Node being replaced does not belong to this parent." ) ;
172+
173+ AssertNotRecycled ( ) ;
174+ _needsMapping = false ;
116175 _children [ i ] = replacement ;
117176 node . Parent = null ;
118177 replacement . Parent = this ;
@@ -123,6 +182,7 @@ public void Replace(Node<T> node, Node<T> replacement)
123182 /// </summary>
124183 public void Detatch ( )
125184 {
185+ AssertNotRecycled ( ) ;
126186 Parent ? . Remove ( this ) ;
127187 Parent = null ;
128188 }
@@ -149,6 +209,7 @@ public Node<T> Root
149209 /// </summary>
150210 public void Teardown ( )
151211 {
212+ _needsMapping = false ;
152213 Value = default ;
153214 Detatch ( ) ; // If no parent then this does nothing...
154215 TeardownChildren ( ) ;
@@ -159,6 +220,10 @@ public void Teardown()
159220 /// </summary>
160221 public void TeardownChildren ( )
161222 {
223+ if ( _children . Count == 0 ) return ;
224+
225+ AssertNotRecycled ( ) ;
226+ _needsMapping = false ;
162227 foreach ( var c in _children )
163228 {
164229 c . Parent = null ; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
@@ -170,35 +235,30 @@ public void TeardownChildren()
170235 /// <summary>
171236 /// Recycles this node.
172237 /// </summary>
173- /// <param name="factory">The factory to use as a recycler.</param>
174238 // ReSharper disable once UnusedMethodReturnValue.Global
175- public T Recycle ( Factory factory )
239+ public T Recycle ( )
176240 {
177- if ( factory == null )
178- throw new ArgumentNullException ( nameof ( factory ) ) ;
179- Contract . EndContractBlock ( ) ;
180-
241+ AssertNotRecycled ( ) ; // Avoid double entry in the pool.
242+ _needsMapping = false ;
181243 var value = Value ;
182244 Value = default ;
183245 Detatch ( ) ; // If no parent then this does nothing...
184- RecycleChildren ( factory ) ;
246+ RecycleChildren ( ) ;
185247 return value ;
186248 }
187249
188250 /// <summary>
189251 /// Recycles all the children of this node.
190252 /// </summary>
191- /// <param name="factory">The factory to use as a recycler.</param>
192- public void RecycleChildren ( Factory factory )
253+ public void RecycleChildren ( )
193254 {
194- if ( factory == null )
195- throw new ArgumentNullException ( nameof ( factory ) ) ;
196- Contract . EndContractBlock ( ) ;
255+ if ( _children . Count == 0 ) return ;
197256
257+ _needsMapping = false ;
198258 foreach ( var c in _children )
199259 {
200260 c . Parent = null ; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
201- factory . RecycleInternal ( c ) ;
261+ _factory . RecycleInternal ( c ) ;
202262 }
203263 _children . Clear ( ) ;
204264 }
0 commit comments