@@ -12,22 +12,45 @@ namespace Vectorial1024.Collections.Generic
1212 /// </summary>
1313 public class DictionaryList < TValue > : IEnumerable < KeyValuePair < int , TValue > >
1414 {
15- internal List < TValue > _list = new List < TValue > ( ) ;
15+ // the version of the DictionaryList, to ensure the enumerator can work correctly
16+ private int _version ;
1617
17- internal bool [ ] _issetLookup ;
18+ #region Backing Storage
19+
20+ private static readonly TValue [ ] EmptyArray = Array . Empty < TValue > ( ) ;
21+
22+ private static readonly bool [ ] EmptyLookupArray = Array . Empty < bool > ( ) ;
23+
24+ private const int DefaultCapacity = 4 ;
25+
26+ /// <summary>
27+ /// Actual usable capacity of the backing storage. Indexes smaller than this value are usable.
28+ /// <para/>
29+ /// Value increased by Add(); value decreased by CompactAndTrimExcess().
30+ /// </summary>
31+ internal int _size ;
1832
1933 internal int _actualCount ;
2034
21- // the version of the DictionaryList, to ensure the enumerator can work correctly
22- private int _version ;
35+ /// <summary>
36+ /// The backing storage of this DictionaryList.
37+ /// </summary>
38+ internal TValue [ ] _items ;
39+
40+ /// <summary>
41+ /// The index existence lookup array of this DictionaryList.
42+ /// </summary>
43+ internal bool [ ] _issetLookup ;
44+
45+ #endregion
2346
2447 /// <summary>
2548 /// Initializes a new instance of the `DictionaryList<TValue>` class that is empty and has the default capacity.
2649 /// </summary>
2750 public DictionaryList ( )
2851 {
29- var capacity = Capacity ;
30- _issetLookup = new bool [ capacity ] ;
52+ _items = EmptyArray ;
53+ _issetLookup = EmptyLookupArray ;
3154 }
3255
3356 /// <summary>
@@ -37,9 +60,10 @@ public DictionaryList()
3760 /// <param name="collection">The collection to take copies from.</param>
3861 public DictionaryList ( DictionaryList < TValue > collection )
3962 {
40- _list = new List < TValue > ( collection . _list ) ;
63+ _items = ( TValue [ ] ) collection . _items . Clone ( ) ;
4164 _actualCount = collection . _actualCount ;
4265 _issetLookup = ( bool [ ] ) collection . _issetLookup . Clone ( ) ;
66+ _size = collection . _size ;
4367 }
4468
4569 /// <summary>
@@ -48,7 +72,7 @@ public DictionaryList(DictionaryList<TValue> collection)
4872 /// <param name="capacity"></param>
4973 public DictionaryList ( int capacity )
5074 {
51- _list = new List < TValue > ( capacity ) ;
75+ _items = new TValue [ capacity ] ;
5276 _issetLookup = new bool [ capacity ] ;
5377 }
5478
@@ -65,32 +89,37 @@ public DictionaryList(int capacity)
6589 /// Indexes that are unset are still counted towards the usage of the internal data structure.
6690 /// </summary>
6791 /// <seealso cref="CompactAndTrimExcess"/>
68- public int Capacity => _list . Capacity ;
92+ public int Capacity => _size ;
6993
7094 public TValue this [ int index ]
7195 {
7296 get
7397 {
74- var tempItem = _list [ index ] ;
98+ if ( ( uint ) index >= ( uint ) _size )
99+ {
100+ // out of range!
101+ ThrowHelper . ThrowArgumentOutOfRangeException ( index ) ;
102+ }
103+ // definitely in range
75104 if ( ! IndexIsSet ( index ) )
76105 {
77106 throw new KeyNotFoundException ( $ "The given index { index } was unset in the list.") ;
78107 }
79- return tempItem ;
108+ return _items [ index ] ;
80109 }
81110 set
82111 {
83- if ( ( uint ) index >= ( uint ) _list . Count )
112+ if ( ( uint ) index >= ( uint ) _size )
84113 {
85114 // out of range!
86115 ThrowHelper . ThrowArgumentOutOfRangeException ( index ) ;
87116 }
88- if ( ! _issetLookup [ index ] )
117+ if ( ! IndexIsSet ( index ) )
89118 {
90119 // adding items during iteration is not allowed!
91120 _version ++ ;
92121 }
93- _list [ index ] = value ;
122+ _items [ index ] = value ;
94123 _issetLookup [ index ] = true ;
95124 }
96125 }
@@ -102,17 +131,26 @@ public TValue this[int index]
102131 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
103132 public void Add ( TValue value )
104133 {
105- var nextIndex = _list . Count ;
106- _list . Add ( value ) ;
107- _actualCount ++ ;
108- _version ++ ;
109-
110- // react to resizing if happened
111- if ( _issetLookup . Length != _list . Capacity )
134+ var nextIndex = _size ;
135+ if ( nextIndex == _items . Length )
112136 {
113- Array . Resize ( ref _issetLookup , _list . Capacity ) ;
137+ // we need a larger backing storage
138+ GrowBackingStorage ( ) ;
114139 }
140+ _items [ nextIndex ] = value ;
115141 _issetLookup [ nextIndex ] = true ;
142+ _actualCount ++ ;
143+ _size ++ ;
144+ _version ++ ;
145+ }
146+
147+ private void GrowBackingStorage ( )
148+ {
149+ // what should be our next array length?
150+ var nextLength = _items . Length == 0 ? DefaultCapacity : _items . Length * 2 ;
151+ // then, grow it
152+ Array . Resize ( ref _items , nextLength ) ;
153+ Array . Resize ( ref _issetLookup , nextLength ) ;
116154 }
117155
118156 /// <summary>
@@ -124,12 +162,16 @@ public void Add(TValue value)
124162 /// <seealso cref="CompactAndTrimExcess"/>
125163 public void UnsetAt ( int index )
126164 {
165+ if ( ( uint ) index >= ( uint ) _size )
166+ {
167+ ThrowHelper . ThrowArgumentOutOfRangeException ( index ) ;
168+ }
127169 if ( ! IndexIsSet ( index ) )
128170 {
129171 return ;
130172 }
131173
132- _list [ index ] = default ! ;
174+ _items [ index ] = default ! ;
133175 _actualCount -- ;
134176 _issetLookup [ index ] = false ;
135177 }
@@ -141,12 +183,7 @@ public void UnsetAt(int index)
141183 /// <returns>Returns true if the index is in use by an element; false otherwise (e.g. unused memory left behind by RemoveAt())</returns>
142184 public bool ContainsIndex ( int index )
143185 {
144- if ( index < 0 )
145- {
146- return false ;
147- }
148-
149- if ( index > _list . Count )
186+ if ( ( uint ) index >= ( uint ) _size )
150187 {
151188 return false ;
152189 }
@@ -169,9 +206,23 @@ internal bool IndexIsSet(int index)
169206 /// </summary>
170207 public void Clear ( )
171208 {
172- _list . Clear ( ) ;
209+ _version ++ ;
210+ var oldSize = _size ;
211+ if ( RuntimeHelpers . IsReferenceOrContainsReferences < TValue > ( ) )
212+ {
213+ // Clear the elements so that the gc can reclaim the references.
214+ _size = 0 ;
215+ if ( oldSize > 0 )
216+ {
217+ Array . Clear ( _items , 0 , oldSize ) ;
218+ }
219+ }
220+ else
221+ {
222+ _size = 0 ;
223+ }
173224 _actualCount = 0 ;
174- _issetLookup = new bool [ Capacity ] ;
225+ Array . Clear ( _issetLookup , 0 , oldSize ) ;
175226 }
176227
177228 /// <summary>
@@ -181,19 +232,39 @@ public void Clear()
181232 /// </summary>
182233 public void CompactAndTrimExcess ( )
183234 {
184- var newList = new List < TValue > ( _actualCount ) ;
185- for ( var index = 0 ; index < _list . Count ; index ++ )
235+ _version ++ ;
236+ if ( _actualCount == 0 )
237+ {
238+ // somehow reset this
239+ _items = EmptyArray ;
240+ _issetLookup = EmptyLookupArray ;
241+ _size = 0 ;
242+ return ;
243+ }
244+
245+ // prepare the new backing storage
246+ var newItems = new TValue [ _actualCount ] ;
247+ var newIndex = 0 ;
248+ for ( var index = 0 ; index < _size ; index ++ )
186249 {
187250 if ( ! IndexIsSet ( index ) )
188251 {
252+ // this is an empty slot; skip
189253 continue ;
190254 }
191- newList . Add ( _list [ index ] ) ;
255+ // this has items; move it over!
256+ newItems [ newIndex ] = _items [ index ] ;
257+ newIndex ++ ;
258+ if ( newIndex == _actualCount )
259+ {
260+ // we have moved everything; early finish!
261+ break ;
262+ }
192263 }
193- _list = newList ;
194- _actualCount = newList . Count ;
195- _version ++ ;
264+
265+ _items = newItems ;
196266 _issetLookup = Enumerable . Repeat ( true , _actualCount ) . ToArray ( ) ;
267+ _size = _actualCount ;
197268 }
198269
199270 #endregion
@@ -205,25 +276,21 @@ internal struct DictionaryListEnumerator : IEnumerator<KeyValuePair<int, TValue>
205276 private readonly DictionaryList < TValue > _dictList ;
206277 private int _index ;
207278 private KeyValuePair < int , TValue > _current ;
208- private readonly int _size ;
209279 private readonly int _version ;
210- private readonly bool [ ] _issetLookup ;
211280
212281 internal DictionaryListEnumerator ( DictionaryList < TValue > dictList )
213282 {
214283 _index = 0 ;
215284 _dictList = dictList ;
216285 _current = default ;
217- _size = _dictList . _list . Count ;
218286 _version = dictList . _version ;
219- _issetLookup = _dictList . _issetLookup ;
220287 }
221288
222289 public bool MoveNext ( )
223290 {
224291 var iterIndex = _index ;
225292 var theDictList = _dictList ;
226- while ( _version == theDictList . _version && ( uint ) iterIndex < ( uint ) _size )
293+ while ( _version == theDictList . _version && ( uint ) iterIndex < ( uint ) theDictList . _size )
227294 {
228295 if ( ! theDictList . IndexIsSet ( iterIndex ) )
229296 {
0 commit comments