#region Copyright 2012-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; namespace CSharpTest.Net.Collections { /// /// Implements an IDictionary interface for an in-memory B+Tree /// [System.Diagnostics.DebuggerDisplay("Count = {_count}")] public class BTreeDictionary : IDictionaryEx, ICloneable { /// The default `order` of the B+Tree structure. public const int DefaultOrder = 64; private Node _root; private readonly int _order; private readonly IComparer _comparer; private readonly KvComparer _kvcomparer; private int _count; private bool _isReadOnly; private Node _first, _last; private KeyCollection _keys; private ValueCollection _values; /// Constructs a BTreeList instance. public BTreeDictionary() : this(DefaultOrder, Comparer.Default) { } /// Constructs a BTreeList instance. public BTreeDictionary(IComparer comparer) : this(DefaultOrder, comparer) { } /// Constructs a BTreeList instance. public BTreeDictionary(int order, IComparer comparer) { if (order < 4) throw new ArgumentOutOfRangeException("order"); if (comparer == null) throw new ArgumentNullException("comparer"); _isReadOnly = false; _order = order; _comparer = comparer; _kvcomparer = new KvComparer(_comparer); Clear(); } /// Constructs a BTreeList instance. public BTreeDictionary(IEnumerable> copyFrom) : this(DefaultOrder, Comparer.Default, copyFrom) { } /// Constructs a BTreeList instance. public BTreeDictionary(IComparer comparer, IEnumerable> copyFrom) : this(DefaultOrder, comparer, copyFrom) { } /// Constructs a BTreeList instance. public BTreeDictionary(int order, IComparer comparer, IEnumerable> copyFrom) : this(order, comparer) { if (copyFrom == null) throw new ArgumentNullException("copyFrom"); AddRange(copyFrom); } /// /// Gets the number of elements contained in the . /// public int Count { get { return _count; } } /// /// Gets a value indicating whether the is read-only. /// public bool IsReadOnly { get { return _isReadOnly; } } /// /// Gets the Comparer provided to the constructor or Comparer<TKey>.Default if it was not provided. /// public IComparer Comparer { get { return _comparer; } } void IDisposable.Dispose() { Clear(); } #region IDictionary Members /// /// Removes all items from the . /// /// The Collection is read-only. public void Clear() { Modify(); _count = 0; _root = new Node(_order); _first = _last = _root; } /// /// Gets or sets the element with the specified key. /// /// /// The element with the specified key. /// /// The key of the element to get or set. /// The property is retrieved and is not found. /// The property is set and the is read-only. public TValue this[TKey key] { get { SeekResult result; if (Seek(_root, key, out result)) return result.Value; throw new KeyNotFoundException(); } set { Modify(); if (!Add(null, -1, _root, new KeyValuePair(key, value), false)) throw new KeyNotFoundException(); } } /// /// Gets the value associated with the specified key. /// /// /// true if the object that implements contains an element with the specified key; otherwise, false. /// public bool TryGetValue(TKey key, out TValue value) { SeekResult result; if (Seek(_root, key, out result)) { value = result.Value; return true; } value = default(TValue); return false; } /// /// Adds an element with the provided key and value to the . /// /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. /// An element with the same key already exists in the . /// The is read-only. public void Add(TKey key, TValue value) { Modify(); Add(new KeyValuePair(key, value)); } /// /// Adds an item to the . /// /// The object to add to the . /// An element with the same key already exists in the . /// The is read-only. public void Add(KeyValuePair item) { Modify(); if (!Add(null, -1, _root, item, true)) throw new ArgumentException("An item with the same key has already been added."); } /// /// Adds a set of items to the . /// /// The items to add to the . /// An element with the same key already exists in the . /// The is read-only. public void AddRange(IEnumerable> items) { Modify(); foreach (KeyValuePair pair in items) Add(pair); } /// /// Determines whether the contains an element with the specified key. /// /// /// true if the contains an element with the key; otherwise, false. /// public bool ContainsKey(TKey key) { SeekResult result; return Seek(_root, key, out result); } /// /// Determines whether the contains a specific key and value pair. /// /// true if is found in the ; otherwise, false. /// The object to locate in the . public bool Contains(KeyValuePair item) { SeekResult result; return Seek(_root, item.Key, out result) && EqualityComparer.Default.Equals(item.Value, result.Value); } /// /// Removes the element with the specified key from the . /// /// /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original . /// /// The key of the element to remove. /// The is read-only. public bool Remove(TKey key) { Modify(); KeyValuePair item = new KeyValuePair(key, default(TValue)); return Remove(null, -1, _root, ref item); } /// /// Removes the first occurrence of a specific object from the . /// /// /// true if was successfully removed from the ; otherwise, false. /// This method also returns false if is not found in the original . /// /// The object to remove from the . /// The is read-only. public bool Remove(KeyValuePair item) { Modify(); SeekResult result; if (Seek(_root, item.Key, out result) && EqualityComparer.Default.Equals(item.Value, result.Value)) return Remove(null, -1, _root, ref item); return false; } /// /// Gets an containing the keys of the . /// public ICollection Keys { get { return _keys != null ? _keys : (_keys = new KeyCollection(this)); } } /// /// Gets an containing the values in the . /// public ICollection Values { get { return _values != null ? _values : (_values = new ValueCollection(this)); } } /// /// Copies the elements of the to an , starting at a particular index. /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { if(array == null) throw new ArgumentNullException(); if(arrayIndex < 0 || (array.Length - arrayIndex) < _count) throw new ArgumentOutOfRangeException(); Node node = _first; while(node != null) { Array.Copy(node.Values, 0, array, arrayIndex, node.Count); arrayIndex += node.Count; node = node.Next; } } /// /// Returns an enumerator that iterates through the collection. /// public IEnumerator> GetEnumerator() { return new Enumerator(_first, 0); } [Obsolete] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Public Members /// /// Adds a key/value pair to the if the key does not already exist. /// /// The key of the element to add. /// The value to be added, if the key does not already exist. /// The is read-only. public TValue GetOrAdd(TKey key, TValue value) { Modify(); SeekResult result; if (Seek(_root, key, out result)) return result.Value; Add(key, value); return value; } /// /// Adds an element with the provided key and value to the . /// /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. /// The is read-only. public bool TryAdd(TKey key, TValue value) { Modify(); return Add(null, -1, _root, new KeyValuePair(key, value), true); } /// /// Updates an element with the provided key to the value if it exists. /// /// Returns true if the key provided was found and updated to the value. /// The object to use as the key of the element to update. /// The new value for the key if found. /// The is read-only. public bool TryUpdate(TKey key, TValue value) { Modify(); SeekResult result; if (Seek(_root, key, out result)) { result.Parent.Values[result.ParentIx] = new KeyValuePair(key, value); return true; } return false; } /// /// Updates an element with the provided key to the value if it exists. /// /// Returns true if the key provided was found and updated to the value. /// The object to use as the key of the element to update. /// The new value for the key if found. /// The value that is compared to the value of the element with key. /// The is read-only. public bool TryUpdate(TKey key, TValue value, TValue comparisonValue) { Modify(); SeekResult result; if (Seek(_root, key, out result) && EqualityComparer.Default.Equals(result.Parent.Values[result.ParentIx].Value, comparisonValue)) { result.Parent.Values[result.ParentIx] = new KeyValuePair(key, value); return true; } return false; } /// /// Removes the element with the specified key from the . /// /// /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original . /// /// The key of the element to remove. /// The value that was removed. /// The is read-only. public bool TryRemove(TKey key, out TValue value) { Modify(); KeyValuePair item = new KeyValuePair(key, default(TValue)); if (Remove(null, -1, _root, ref item)) { value = item.Value; return true; } value = default(TValue); return false; } /// /// Returns all the items of this collection as an array of . /// public KeyValuePair[] ToArray() { KeyValuePair[] array = new KeyValuePair[_count]; CopyTo(array, 0); return array; } /// /// Returns the first key and it's associated value. /// public KeyValuePair First() { KeyValuePair result; if (!TryGetFirst(out result)) throw new InvalidOperationException(); return result; } /// /// Returns the first key and it's associated value. /// public bool TryGetFirst(out KeyValuePair item) { if(_count == 0) { item = default(KeyValuePair); return false; } item = _first.Values[0]; return true; } /// /// Returns the last key and it's associated value. /// public KeyValuePair Last() { KeyValuePair result; if (!TryGetLast(out result)) throw new InvalidOperationException(); return result; } /// /// Returns the last key and it's associated value. /// public bool TryGetLast(out KeyValuePair item) { if (_count == 0) { item = default(KeyValuePair); return false; } item = _last.Values[_last.Count-1]; return true; } /// /// Inclusivly enumerates from start key to the end of the collection /// public IEnumerable> EnumerateFrom(TKey start) { SeekResult result; Seek(_root, start, out result); return new Enumerator(result.Parent, result.ParentIx); } /// /// Inclusivly enumerates from start key to stop key /// public IEnumerable> EnumerateRange(TKey start, TKey end) { SeekResult result; Seek(_root, start, out result); return new Enumerator(result.Parent, result.ParentIx, x => (_comparer.Compare(x.Key, end) <= 0)); } object ICloneable.Clone() { return Clone(); } /// /// Returns a writable clone of this collection. /// public BTreeDictionary Clone() { BTreeDictionary copy = (BTreeDictionary)MemberwiseClone(); copy._first = copy._last = null; copy._root = copy._root.Clone(ref copy._first, ref copy._last); copy._isReadOnly = false; return copy; } /// /// Returns a read-only clone of this collection. If this instance is already read-only the method will return this. /// public BTreeDictionary MakeReadOnly() { if (_isReadOnly) return this; BTreeDictionary copy = Clone(); copy._isReadOnly = true; return copy; } #endregion #region Implementation Details void Modify() { if (_isReadOnly) throw new NotSupportedException("Collection is read-only."); } struct SeekResult { public TValue Value; public Node Parent; public int ParentIx; } bool Seek(Node from, TKey item, out SeekResult found) { found = new SeekResult(); found.Value = default(TValue); found.Parent = from; found.ParentIx = -1; KeyValuePair seek = new KeyValuePair(item, null); while (!found.Parent.IsLeaf) { int ix = Array.BinarySearch>(found.Parent.Children, 1, found.Parent.Count - 1, seek, _kvcomparer); if (ix < 0 && (ix = ~ix) > 0) ix--; found.Parent = found.Parent.Children[ix].Value; } found.ParentIx = Array.BinarySearch>(found.Parent.Values, 0, found.Parent.Count, new KeyValuePair(item, found.Value), _kvcomparer); if (found.ParentIx < 0) { found.ParentIx = ~found.ParentIx; return false; } found.Value = found.Parent.Values[found.ParentIx].Value; return true; } bool Add(Node parent, int myIx, Node me, KeyValuePair item, bool adding) { if (me.Count == _order) { if (parent == null) { _root = parent = new Node(_order, default(TKey), me); myIx = 0; } TKey split; Node next = new Node(_order, me, out split, ref _first, ref _last); parent.Add(myIx + 1, split, next); if (_comparer.Compare(split, item.Key) <= 0) me = next; } int ix; if (me.IsLeaf) { ix = Array.BinarySearch>(me.Values, 0, me.Count, item, _kvcomparer); if (ix < 0) ix = ~ix; else { if (adding) return false; me.Values[ix] = item; return true; } me.Add(ix, item); _count++; return true; } KeyValuePair seek = new KeyValuePair(item.Key, null); ix = Array.BinarySearch>(me.Children, 1, me.Count - 1, seek, _kvcomparer); if (ix < 0 && (ix = ~ix) > 0) ix--; return Add(me, ix, me.Children[ix].Value, item, adding); } bool Remove(Node parent, int myIx, Node me, ref KeyValuePair item) { if (parent == null && ReferenceEquals(me, _root) && me.Count == 1 && me.IsLeaf == false) _root = me.Children[0].Value; if (parent != null && me.Count <= (_order >> 2)) { if (myIx == 0) parent.Join(myIx, myIx + 1, ref _first, ref _last); else parent.Join(myIx - 1, myIx, ref _first, ref _last); return Remove(null, -1, parent, ref item); } int ix; if (me.IsLeaf) { ix = Array.BinarySearch>(me.Values, 0, me.Count, item, _kvcomparer); if (ix < 0) return false; item = me.Values[ix]; me.RemoveValue(ix); _count--; return true; } KeyValuePair seek = new KeyValuePair(item.Key, null); ix = Array.BinarySearch>(me.Children, 1, me.Count - 1, seek, _kvcomparer); if (ix < 0 && (ix = ~ix) > 0) ix--; Node child = me.Children[ix].Value; if (!Remove(me, ix, child, ref item)) return false; return true; } #endregion #region class KeyCollection class KeyCollection : ICollection { private readonly BTreeDictionary _owner; public KeyCollection(BTreeDictionary owner) { _owner = owner; } #region ICollection Members public int Count { get { return _owner.Count; } } public bool IsReadOnly { get { return true; } } public bool Contains(TKey item) { return _owner.ContainsKey(item); } public void CopyTo(TKey[] array, int arrayIndex) { foreach (TKey key in this) array[arrayIndex++] = key; } public IEnumerator GetEnumerator() { return new KeyEnumerator(_owner.GetEnumerator()); } [Obsolete] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } class KeyEnumerator : IEnumerator { private readonly IEnumerator> _e; public KeyEnumerator(IEnumerator> e) { _e = e; } public TKey Current { get { return _e.Current.Key; } } [Obsolete] object System.Collections.IEnumerator.Current { get { return Current; } } public void Dispose() { _e.Dispose(); } public bool MoveNext() { return _e.MoveNext(); } public void Reset() { _e.Reset(); } } [Obsolete] void ICollection.Add(TKey item) { throw new NotSupportedException(); } [Obsolete] void ICollection.Clear() { throw new NotSupportedException(); } [Obsolete] bool ICollection.Remove(TKey item) { throw new NotSupportedException(); } #endregion } #endregion #region class ValueCollection class ValueCollection : ICollection { private readonly BTreeDictionary _owner; public ValueCollection(BTreeDictionary owner) { _owner = owner; } #region ICollection Members public int Count { get { return _owner.Count; } } public bool IsReadOnly { get { return true; } } public bool Contains(TValue item) { IEqualityComparer c = EqualityComparer.Default; foreach (TValue value in this) if (c.Equals(item, value)) return true; return false; } public void CopyTo(TValue[] array, int arrayIndex) { foreach (TValue value in this) array[arrayIndex++] = value; } public IEnumerator GetEnumerator() { return new ValueEnumerator(_owner.GetEnumerator()); } [Obsolete] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } class ValueEnumerator : IEnumerator { private readonly IEnumerator> _e; public ValueEnumerator(IEnumerator> e) { _e = e; } public TValue Current { get { return _e.Current.Value; } } [Obsolete] object System.Collections.IEnumerator.Current { get { return Current; } } public void Dispose() { _e.Dispose(); } public bool MoveNext() { return _e.MoveNext(); } public void Reset() { _e.Reset(); } } [Obsolete] void ICollection.Add(TValue item) { throw new NotSupportedException(); } [Obsolete] void ICollection.Clear() { throw new NotSupportedException(); } [Obsolete] bool ICollection.Remove(TValue item) { throw new NotSupportedException(); } #endregion } #endregion #region class Enumerator class Enumerator : IEnumerator>, IEnumerable> { private readonly Node _start; private readonly int _startIx; private readonly Predicate> _fnContinue; private Node _current; private int _index; private bool _valid; public Enumerator(Node start, int index) : this(start, index, null) { } public Enumerator(Node start, int index, Predicate> fnContinue) { _start = start; _startIx = index; _fnContinue = fnContinue; Reset(); } public void Reset() { _valid = false; _current = _start; _index = _startIx - 1; } public bool MoveNext() { if (_current == null) return false; if (++_index >= _current.Count) { _current = _current.Next; _index = 0; } _valid = _current != null && _index >= 0 && _index < _current.Count; if (_valid && _fnContinue != null && !_fnContinue(Current)) _valid = false; return _valid; } [Obsolete] object System.Collections.IEnumerator.Current { get { return Current; } } public KeyValuePair Current { get { if (_valid) return _current.Values[_index]; throw new InvalidOperationException(); } } public void Dispose() { } [Obsolete] IEnumerator> IEnumerable>.GetEnumerator() { return this; } [Obsolete] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this; } } #endregion #region class KvComparer class KvComparer : IComparer>, IComparer> { private readonly IComparer _comparer; public KvComparer(IComparer comparer) { _comparer = comparer; } int IComparer>.Compare(KeyValuePair x, KeyValuePair y) { return _comparer.Compare(x.Key, y.Key); } int IComparer>.Compare(KeyValuePair x, KeyValuePair y) { return _comparer.Compare(x.Key, y.Key); } } #endregion #region class Node [System.Diagnostics.DebuggerDisplay("Count = {_count}")] private class Node { int _count; KeyValuePair[] _children; KeyValuePair[] _values; public Node Next, Prev; public int Count { get { return _count; } } public bool IsLeaf { get { return _values != null; } } public KeyValuePair[] Children { get { return _children; } } public KeyValuePair[] Values { get { return _values; } } public Node(int order) { _count = 0; _values = new KeyValuePair[order]; } public Node(int order, TKey key, Node node) { _count = 1; _children = new KeyValuePair[order]; _children[0] = new KeyValuePair(key, node); } public Node(int order, Node split, out TKey splitKey, ref Node first, ref Node last) { if (split._values != null) { _values = new KeyValuePair[order]; int half = split._count >> 1; Array.Copy(split._values, half, _values, 0, _count = (split._count - half)); Array.Clear(split._values, half, _count); split._count = half; splitKey = _values[0].Key; Prev = split; Node oldNext = split.Next; split.Next = this; if ((Next = oldNext) == null) last = this; else Next.Prev = this; } else //if (split.Children != null) { _children = new KeyValuePair[order]; int half = split._count >> 1; Array.Copy(split._children, half, _children, 0, _count = (split._count - half)); Array.Clear(split._children, half, _count); split._count = half; splitKey = _children[0].Key; } } public void Add(int ix, KeyValuePair kv) { if (ix < _count) Array.Copy(_values, ix, _values, ix + 1, _count - ix); _values[ix] = kv; _count++; } public void Add(int ix, TKey key, Node child) { if (ix < _count) Array.Copy(_children, ix, _children, ix + 1, _count - ix); _children[ix] = new KeyValuePair(key, child); _count++; } public void RemoveValue(int ix) { _values[ix] = new KeyValuePair(); _count--; if (ix < _count) { Array.Copy(_values, ix + 1, _values, ix, _count - ix); _values[_count] = new KeyValuePair(); } } void RemoveChild(int ix, ref Node first, ref Node last) { Node child = _children[ix].Value; if(child != null) { if (child.Next != null) child.Next.Prev = child.Prev; else if (ReferenceEquals(child, last)) last = child.Prev; if (child.Prev != null) child.Prev.Next = child.Next; else if (ReferenceEquals(child, first)) first = child.Next; } _children[ix] = new KeyValuePair(); _count--; if (ix < _count) { Array.Copy(_children, ix + 1, _children, ix, _count - ix); _children[_count] = new KeyValuePair(); } } public void Join(int firstIx, int secondIx, ref Node nodeFirst, ref Node nodeLast) { if (IsLeaf || firstIx < 0 || secondIx >= Count) return; Node first = Children[firstIx].Value; Node second = Children[secondIx].Value; int order = first.IsLeaf ? first.Values.Length : first.Children.Length; int total = first.Count + second.Count; if (total < order) { if (first.IsLeaf) Array.Copy(second.Values, 0, first.Values, first.Count, second.Count); else Array.Copy(second.Children, 0, first.Children, first.Count, second.Count); first._count += second.Count; RemoveChild(secondIx, ref nodeFirst, ref nodeLast); } else if (first.IsLeaf) { KeyValuePair[] list = new KeyValuePair[total]; Array.Copy(first.Values, 0, list, 0, first.Count); Array.Copy(second.Values, 0, list, first.Count, second.Count); Array.Clear(first.Values, 0, first.Count); Array.Clear(second.Values, 0, second.Count); int half = total >> 1; Array.Copy(list, 0, first.Values, 0, first._count = half); Array.Copy(list, half, second.Values, 0, second._count = (total - half)); Children[secondIx] = new KeyValuePair(second.Values[0].Key, second); } else //if (first.IsLeaf == false) { KeyValuePair[] list = new KeyValuePair[total]; Array.Copy(first.Children, 0, list, 0, first.Count); Array.Copy(second.Children, 0, list, first.Count, second.Count); Array.Clear(first.Children, 0, first.Count); Array.Clear(second.Children, 0, second.Count); list[first.Count] = new KeyValuePair(Children[secondIx].Key, list[first.Count].Value); int half = total >> 1; Array.Copy(list, 0, first.Children, 0, first._count = half); Array.Copy(list, half, second.Children, 0, second._count = (total - half)); Children[secondIx] = new KeyValuePair(second.Children[0].Key, second); } } public Node Clone(ref Node first, ref Node last) { Node copy = (Node)MemberwiseClone(); if (copy.IsLeaf) { copy._values = (KeyValuePair[]) copy._values.Clone(); copy.Next = null; copy.Prev = last; if (first == null) first = copy; if (last != null) last.Next = copy; last = copy; } else { copy._children = new KeyValuePair[copy._children.Length]; for( int i=0; i < copy._count; i++) copy._children[i] = new KeyValuePair( _children[i].Key, _children[i].Value.Clone(ref first, ref last) ); } return copy; } } #endregion /// /// Ensures data integrity or raises exception /// [Conditional("DEBUG")] public void DebugAssert() { #if DEBUG Node foundFirst = _root; while (!foundFirst.IsLeaf) foundFirst = foundFirst.Children[0].Value; if (!ReferenceEquals(_first, foundFirst)) throw new ApplicationException("The _first node reference is incorrect."); Node foundLast = _root; while (!foundLast.IsLeaf) foundLast = foundLast.Children[foundLast.Count - 1].Value; if (!ReferenceEquals(_last, foundLast)) throw new ApplicationException("The _last node reference is incorrect."); int counter = 0; var tmp = _first; while (tmp != null) { if (tmp.Next == null && !ReferenceEquals(_last, tmp)) throw new ApplicationException("Forward links corrupted."); if (!tmp.IsLeaf) throw new ApplicationException("Non Leaf in linked list."); counter += tmp.Count; tmp = tmp.Next; } if (counter != _count) throw new ApplicationException(String.Format("Found {0} items in links, {1} expected.", counter, _count)); counter = 0; tmp = _last; while (tmp != null) { if (tmp.Prev == null && !ReferenceEquals(_first, tmp)) throw new ApplicationException("Forward links corrupted."); if (!tmp.IsLeaf) throw new ApplicationException("Non Leaf in linked list."); counter += tmp.Count; tmp = tmp.Prev; } if (counter != _count) throw new ApplicationException(String.Format("Found {0} items in links, {1} expected.", counter, _count)); counter = DescendAssert(_root); if (counter != _count) throw new ApplicationException(String.Format("Tree crawl found {0} items, {1} expected.", counter, _count)); #endif } #if DEBUG private int DescendAssert(Node node) { var eq = EqualityComparer.Default; int count = 0; if (node.IsLeaf) { for (int i = 0; i < node.Values.Length; i++) { if (i < node.Count) count++; else { if (!eq.Equals(default(TValue), node.Values[i].Value)) throw new ApplicationException("Unexpected non-default value after count."); } } } else { for (int i=0; i < node.Children.Length; i++) { if (i < node.Count) count += DescendAssert(node.Children[i].Value); else { if (node.Children[i].Value != null) throw new ApplicationException("Unexpected non-null child after count."); } } } if (count == 0 && _count != 0) throw new ApplicationException("Unexpected empty count."); return count; } #endif } }