87f6e75131c2a36b5e609479433929437029c7b3
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 using System.Collections.Generic;
5 using System.Diagnostics;
6
7 namespace System.Collections.Immutable
8 {
9     public partial struct ImmutableArray<T>
10     {
11         /// <summary>
12         /// A writable array accessor that can be converted into an <see cref="ImmutableArray{T}"/>
13         /// instance without allocating memory.
14         /// </summary>
15         [DebuggerDisplay("Count = {Count}")]
16         [DebuggerTypeProxy(typeof(ImmutableArrayBuilderDebuggerProxy<>))]
17         public sealed class Builder : IList<T>, IReadOnlyList<T>
18         {
19             /// <summary>
20             /// The backing array for the builder.
21             /// </summary>
22             private T[] _elements;
23
24             /// <summary>
25             /// The number of initialized elements in the array.
26             /// </summary>
27             private int _count;
28
29             /// <summary>
30             /// Initializes a new instance of the <see cref="Builder"/> class.
31             /// </summary>
32             /// <param name="capacity">The initial capacity of the internal array.</param>
33             internal Builder(int capacity)
34             {
35                 Requires.Range(capacity >= 0, nameof(capacity));
36                 _elements = new T[capacity];
37                 _count = 0;
38             }
39
40             /// <summary>
41             /// Initializes a new instance of the <see cref="Builder"/> class.
42             /// </summary>
43             internal Builder()
44                 : this(8)
45             {
46             }
47
48             /// <summary>
49             /// Get and sets the length of the internal array.  When set the internal array is
50             /// reallocated to the given capacity if it is not already the specified length.
51             /// </summary>
52             public int Capacity
53             {
54                 get { return _elements.Length; }
55                 set
56                 {
57                     if (value < _count)
58                     {
59                         throw new ArgumentException(SR.CapacityMustBeGreaterThanOrEqualToCount, paramName: nameof(value));
60                     }
61
62                     if (value != _elements.Length)
63                     {
64                         if (value > 0)
65                         {
66                             var temp = new T[value];
67                             if (_count > 0)
68                             {
69                                 Array.Copy(_elements, temp, _count);
70                             }
71
72                             _elements = temp;
73                         }
74                         else
75                         {
76                             _elements = ImmutableArray<T>.Empty.array!;
77                         }
78                     }
79                 }
80             }
81
82             /// <summary>
83             /// Gets or sets the length of the builder.
84             /// </summary>
85             /// <remarks>
86             /// If the value is decreased, the array contents are truncated.
87             /// If the value is increased, the added elements are initialized to the default value of type <typeparamref name="T"/>.
88             /// </remarks>
89             public int Count
90             {
91                 get
92                 {
93                     return _count;
94                 }
95
96                 set
97                 {
98                     Requires.Range(value >= 0, nameof(value));
99                     if (value < _count)
100                     {
101                         // truncation mode
102                         // Clear the elements of the elements that are effectively removed.
103
104                         // PERF: Array.Clear works well for big arrays,
105                         //       but may have too much overhead with small ones (which is the common case here)
106                         if (_count - value > 64)
107                         {
108                             Array.Clear(_elements, value, _count - value);
109                         }
110                         else
111                         {
112                             for (int i = value; i < this.Count; i++)
113                             {
114                                 _elements[i] = default(T)!;
115                             }
116                         }
117                     }
118                     else if (value > _count)
119                     {
120                         // expansion
121                         this.EnsureCapacity(value);
122                     }
123
124                     _count = value;
125                 }
126             }
127
128             private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException();
129
130             /// <summary>
131             /// Gets or sets the element at the specified index.
132             /// </summary>
133             /// <param name="index">The index.</param>
134             /// <returns></returns>
135             /// <exception cref="IndexOutOfRangeException">
136             /// </exception>
137             public T this[int index]
138             {
139                 get
140                 {
141                     if (index >= this.Count)
142                     {
143                         ThrowIndexOutOfRangeException();
144                     }
145
146                     return _elements[index];
147                 }
148
149                 set
150                 {
151                     if (index >= this.Count)
152                     {
153                         ThrowIndexOutOfRangeException();
154                     }
155
156                     _elements[index] = value;
157                 }
158             }
159
160 #if !NETSTANDARD1_0
161             /// <summary>
162             /// Gets a read-only reference to the element at the specified index.
163             /// </summary>
164             /// <param name="index">The index.</param>
165             /// <returns></returns>
166             /// <exception cref="IndexOutOfRangeException">
167             /// </exception>
168             public ref readonly T ItemRef(int index)
169             {
170                 if (index >= this.Count)
171                 {
172                     ThrowIndexOutOfRangeException();
173                 }
174
175                 return ref this._elements[index];
176             }
177 #endif
178
179             /// <summary>
180             /// Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.
181             /// </summary>
182             /// <returns>true if the <see cref="ICollection{T}"/> is read-only; otherwise, false.
183             ///   </returns>
184             bool ICollection<T>.IsReadOnly
185             {
186                 get { return false; }
187             }
188
189             /// <summary>
190             /// Returns an immutable copy of the current contents of this collection.
191             /// </summary>
192             /// <returns>An immutable array.</returns>
193             public ImmutableArray<T> ToImmutable()
194             {
195                 return new ImmutableArray<T>(this.ToArray());
196             }
197
198             /// <summary>
199             /// Extracts the internal array as an <see cref="ImmutableArray{T}"/> and replaces it
200             /// with a zero length array.
201             /// </summary>
202             /// <exception cref="InvalidOperationException">When <see cref="ImmutableArray{T}.Builder.Count"/> doesn't
203             /// equal <see cref="ImmutableArray{T}.Builder.Capacity"/>.</exception>
204             public ImmutableArray<T> MoveToImmutable()
205             {
206                 if (Capacity != Count)
207                 {
208                     throw new InvalidOperationException(SR.CapacityMustEqualCountOnMove);
209                 }
210
211                 T[] temp = _elements;
212                 _elements = ImmutableArray<T>.Empty.array!;
213                 _count = 0;
214                 return new ImmutableArray<T>(temp);
215             }
216
217             /// <summary>
218             /// Removes all items from the <see cref="ICollection{T}"/>.
219             /// </summary>
220             public void Clear()
221             {
222                 this.Count = 0;
223             }
224
225             /// <summary>
226             /// Inserts an item to the <see cref="IList{T}"/> at the specified index.
227             /// </summary>
228             /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
229             /// <param name="item">The object to insert into the <see cref="IList{T}"/>.</param>
230             public void Insert(int index, T item)
231             {
232                 Requires.Range(index >= 0 && index <= this.Count, nameof(index));
233                 this.EnsureCapacity(this.Count + 1);
234
235                 if (index < this.Count)
236                 {
237                     Array.Copy(_elements, index, _elements, index + 1, this.Count - index);
238                 }
239
240                 _count++;
241                 _elements[index] = item;
242             }
243
244             /// <summary>
245             /// Adds an item to the <see cref="ICollection{T}"/>.
246             /// </summary>
247             /// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
248             public void Add(T item)
249             {
250                 int newCount = _count + 1;
251                 this.EnsureCapacity(newCount);
252                 _elements[_count] = item;
253                 _count = newCount;
254             }
255
256             /// <summary>
257             /// Adds the specified items to the end of the array.
258             /// </summary>
259             /// <param name="items">The items.</param>
260             public void AddRange(IEnumerable<T> items)
261             {
262                 Requires.NotNull(items, nameof(items));
263
264                 int count;
265                 if (items.TryGetCount(out count))
266                 {
267                     this.EnsureCapacity(this.Count + count);
268
269                     if (items.TryCopyTo(_elements, _count))
270                     {
271                         _count += count;
272                         return;
273                     }
274                 }
275
276                 foreach (var item in items)
277                 {
278                     this.Add(item);
279                 }
280             }
281
282             /// <summary>
283             /// Adds the specified items to the end of the array.
284             /// </summary>
285             /// <param name="items">The items.</param>
286             public void AddRange(params T[] items)
287             {
288                 Requires.NotNull(items, nameof(items));
289
290                 var offset = this.Count;
291                 this.Count += items.Length;
292
293                 Array.Copy(items, 0, _elements, offset, items.Length);
294             }
295
296             /// <summary>
297             /// Adds the specified items to the end of the array.
298             /// </summary>
299             /// <param name="items">The items.</param>
300             public void AddRange<TDerived>(TDerived[] items) where TDerived : T
301             {
302                 Requires.NotNull(items, nameof(items));
303
304                 var offset = this.Count;
305                 this.Count += items.Length;
306
307                 Array.Copy(items, 0, _elements, offset, items.Length);
308             }
309
310             /// <summary>
311             /// Adds the specified items to the end of the array.
312             /// </summary>
313             /// <param name="items">The items.</param>
314             /// <param name="length">The number of elements from the source array to add.</param>
315             public void AddRange(T[] items, int length)
316             {
317                 Requires.NotNull(items, nameof(items));
318                 Requires.Range(length >= 0 && length <= items.Length, nameof(length));
319
320                 var offset = this.Count;
321                 this.Count += length;
322
323                 Array.Copy(items, 0, _elements, offset, length);
324             }
325
326             /// <summary>
327             /// Adds the specified items to the end of the array.
328             /// </summary>
329             /// <param name="items">The items.</param>
330             public void AddRange(ImmutableArray<T> items)
331             {
332                 this.AddRange(items, items.Length);
333             }
334
335             /// <summary>
336             /// Adds the specified items to the end of the array.
337             /// </summary>
338             /// <param name="items">The items.</param>
339             /// <param name="length">The number of elements from the source array to add.</param>
340             public void AddRange(ImmutableArray<T> items, int length)
341             {
342                 Requires.Range(length >= 0, nameof(length));
343
344                 if (items.array != null)
345                 {
346                     this.AddRange(items.array, length);
347                 }
348             }
349
350             /// <summary>
351             /// Adds the specified items to the end of the array.
352             /// </summary>
353             /// <param name="items">The items.</param>
354             public void AddRange<TDerived>(ImmutableArray<TDerived> items) where TDerived : T
355             {
356                 if (items.array != null)
357                 {
358                     this.AddRange(items.array);
359                 }
360             }
361
362             /// <summary>
363             /// Adds the specified items to the end of the array.
364             /// </summary>
365             /// <param name="items">The items.</param>
366             public void AddRange(Builder items)
367             {
368                 Requires.NotNull(items, nameof(items));
369                 this.AddRange(items._elements, items.Count);
370             }
371
372             /// <summary>
373             /// Adds the specified items to the end of the array.
374             /// </summary>
375             /// <param name="items">The items.</param>
376             public void AddRange<TDerived>(ImmutableArray<TDerived>.Builder items) where TDerived : T
377             {
378                 Requires.NotNull(items, nameof(items));
379                 this.AddRange(items._elements, items.Count);
380             }
381
382             /// <summary>
383             /// Removes the specified element.
384             /// </summary>
385             /// <param name="element">The element.</param>
386             /// <returns>A value indicating whether the specified element was found and removed from the collection.</returns>
387             public bool Remove(T element)
388             {
389                 int index = this.IndexOf(element);
390                 if (index >= 0)
391                 {
392                     this.RemoveAt(index);
393                     return true;
394                 }
395
396                 return false;
397             }
398
399             /// <summary>
400             /// Removes the <see cref="IList{T}"/> item at the specified index.
401             /// </summary>
402             /// <param name="index">The zero-based index of the item to remove.</param>
403             public void RemoveAt(int index)
404             {
405                 Requires.Range(index >= 0 && index < this.Count, nameof(index));
406
407                 if (index < this.Count - 1)
408                 {
409                     Array.Copy(_elements, index + 1, _elements, index, this.Count - index - 1);
410                 }
411
412                 this.Count--;
413             }
414
415             /// <summary>
416             /// Determines whether the <see cref="ICollection{T}"/> contains a specific value.
417             /// </summary>
418             /// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
419             /// <returns>
420             /// true if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, false.
421             /// </returns>
422             public bool Contains(T item)
423             {
424                 return this.IndexOf(item) >= 0;
425             }
426
427             /// <summary>
428             /// Creates a new array with the current contents of this Builder.
429             /// </summary>
430             public T[] ToArray()
431             {
432                 if (this.Count == 0)
433                 {
434                     return Empty.array!;
435                 }
436
437                 T[] result = new T[this.Count];
438                 Array.Copy(_elements, result, this.Count);
439                 return result;
440             }
441
442             /// <summary>
443             /// Copies the current contents to the specified array.
444             /// </summary>
445             /// <param name="array">The array to copy to.</param>
446             /// <param name="index">The starting index of the target array.</param>
447             public void CopyTo(T[] array, int index)
448             {
449                 Requires.NotNull(array, nameof(array));
450                 Requires.Range(index >= 0 && index + this.Count <= array.Length, nameof(index));
451                 Array.Copy(_elements, 0, array, index, this.Count);
452             }
453
454             /// <summary>
455             /// Resizes the array to accommodate the specified capacity requirement.
456             /// </summary>
457             /// <param name="capacity">The required capacity.</param>
458             private void EnsureCapacity(int capacity)
459             {
460                 if (_elements.Length < capacity)
461                 {
462                     int newCapacity = Math.Max(_elements.Length * 2, capacity);
463                     Array.Resize(ref _elements, newCapacity);
464                 }
465             }
466
467             /// <summary>
468             /// Determines the index of a specific item in the <see cref="IList{T}"/>.
469             /// </summary>
470             /// <param name="item">The object to locate in the <see cref="IList{T}"/>.</param>
471             /// <returns>
472             /// The index of <paramref name="item"/> if found in the list; otherwise, -1.
473             /// </returns>
474             public int IndexOf(T item)
475             {
476                 return this.IndexOf(item, 0, _count, EqualityComparer<T>.Default);
477             }
478
479             /// <summary>
480             /// Searches the array for the specified item.
481             /// </summary>
482             /// <param name="item">The item to search for.</param>
483             /// <param name="startIndex">The index at which to begin the search.</param>
484             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
485             public int IndexOf(T item, int startIndex)
486             {
487                 return this.IndexOf(item, startIndex, this.Count - startIndex, EqualityComparer<T>.Default);
488             }
489
490             /// <summary>
491             /// Searches the array for the specified item.
492             /// </summary>
493             /// <param name="item">The item to search for.</param>
494             /// <param name="startIndex">The index at which to begin the search.</param>
495             /// <param name="count">The number of elements to search.</param>
496             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
497             public int IndexOf(T item, int startIndex, int count)
498             {
499                 return this.IndexOf(item, startIndex, count, EqualityComparer<T>.Default);
500             }
501
502             /// <summary>
503             /// Searches the array for the specified item.
504             /// </summary>
505             /// <param name="item">The item to search for.</param>
506             /// <param name="startIndex">The index at which to begin the search.</param>
507             /// <param name="count">The number of elements to search.</param>
508             /// <param name="equalityComparer">
509             /// The equality comparer to use in the search.
510             /// If <c>null</c>, <see cref="EqualityComparer{T}.Default"/> is used.
511             /// </param>
512             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
513             public int IndexOf(T item, int startIndex, int count, IEqualityComparer<T>? equalityComparer)
514             {
515                 if (count == 0 && startIndex == 0)
516                 {
517                     return -1;
518                 }
519
520                 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
521                 Requires.Range(count >= 0 && startIndex + count <= this.Count, nameof(count));
522
523                 equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
524                 if (equalityComparer == EqualityComparer<T>.Default)
525                 {
526                     return Array.IndexOf(_elements, item, startIndex, count);
527                 }
528                 else
529                 {
530                     for (int i = startIndex; i < startIndex + count; i++)
531                     {
532                         if (equalityComparer.Equals(_elements[i], item))
533                         {
534                             return i;
535                         }
536                     }
537
538                     return -1;
539                 }
540             }
541
542             /// <summary>
543             /// Searches the array for the specified item in reverse.
544             /// </summary>
545             /// <param name="item">The item to search for.</param>
546             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
547             public int LastIndexOf(T item)
548             {
549                 if (this.Count == 0)
550                 {
551                     return -1;
552                 }
553
554                 return this.LastIndexOf(item, this.Count - 1, this.Count, EqualityComparer<T>.Default);
555             }
556
557             /// <summary>
558             /// Searches the array for the specified item in reverse.
559             /// </summary>
560             /// <param name="item">The item to search for.</param>
561             /// <param name="startIndex">The index at which to begin the search.</param>
562             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
563             public int LastIndexOf(T item, int startIndex)
564             {
565                 if (this.Count == 0 && startIndex == 0)
566                 {
567                     return -1;
568                 }
569
570                 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
571
572                 return this.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer<T>.Default);
573             }
574
575             /// <summary>
576             /// Searches the array for the specified item in reverse.
577             /// </summary>
578             /// <param name="item">The item to search for.</param>
579             /// <param name="startIndex">The index at which to begin the search.</param>
580             /// <param name="count">The number of elements to search.</param>
581             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
582             public int LastIndexOf(T item, int startIndex, int count)
583             {
584                 return this.LastIndexOf(item, startIndex, count, EqualityComparer<T>.Default);
585             }
586
587             /// <summary>
588             /// Searches the array for the specified item in reverse.
589             /// </summary>
590             /// <param name="item">The item to search for.</param>
591             /// <param name="startIndex">The index at which to begin the search.</param>
592             /// <param name="count">The number of elements to search.</param>
593             /// <param name="equalityComparer">The equality comparer to use in the search.</param>
594             /// <returns>The 0-based index into the array where the item was found; or -1 if it could not be found.</returns>
595             public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer<T>? equalityComparer)
596             {
597                 if (count == 0 && startIndex == 0)
598                 {
599                     return -1;
600                 }
601
602                 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
603                 Requires.Range(count >= 0 && startIndex - count + 1 >= 0, nameof(count));
604
605                 equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
606                 if (equalityComparer == EqualityComparer<T>.Default)
607                 {
608                     return Array.LastIndexOf(_elements, item, startIndex, count);
609                 }
610                 else
611                 {
612                     for (int i = startIndex; i >= startIndex - count + 1; i--)
613                     {
614                         if (equalityComparer.Equals(item, _elements[i]))
615                         {
616                             return i;
617                         }
618                     }
619
620                     return -1;
621                 }
622             }
623
624             /// <summary>
625             /// Reverses the order of elements in the collection.
626             /// </summary>
627             public void Reverse()
628             {
629                 // The non-generic Array.Reverse is not used because it does not perform
630                 // well for non-primitive value types.
631                 // If/when a generic Array.Reverse<T> becomes available, the below code
632                 // can be deleted and replaced with a call to Array.Reverse<T>.
633                 int i = 0;
634                 int j = _count - 1;
635                 T[] array = _elements;
636                 while (i < j)
637                 {
638                     T temp = array[i];
639                     array[i] = array[j];
640                     array[j] = temp;
641                     i++;
642                     j--;
643                 }
644             }
645
646             /// <summary>
647             /// Sorts the array.
648             /// </summary>
649             public void Sort()
650             {
651                 if (Count > 1)
652                 {
653                     Array.Sort(_elements, 0, this.Count, Comparer<T>.Default);
654                 }
655             }
656
657             /// <summary>
658             /// Sorts the elements in the entire array using
659             /// the specified <see cref="Comparison{T}"/>.
660             /// </summary>
661             /// <param name="comparison">
662             /// The <see cref="Comparison{T}"/> to use when comparing elements.
663             /// </param>
664             /// <exception cref="ArgumentNullException"><paramref name="comparison"/> is null.</exception>
665             public void Sort(Comparison<T> comparison)
666             {
667                 Requires.NotNull(comparison, nameof(comparison));
668
669                 if (Count > 1)
670                 {
671                     // Array.Sort does not have an overload that takes both bounds and a Comparison.
672                     // We could special case _count == _elements.Length in order to try to avoid
673                     // the IComparer allocation, but the Array.Sort overload that takes a Comparison
674                     // allocates such an IComparer internally, anyway.
675                     Array.Sort(_elements, 0, _count, Comparer<T>.Create(comparison));
676                 }
677             }
678
679             /// <summary>
680             /// Sorts the array.
681             /// </summary>
682             /// <param name="comparer">The comparer to use in sorting. If <c>null</c>, the default comparer is used.</param>
683             public void Sort(IComparer<T>? comparer)
684             {
685                 if (Count > 1)
686                 {
687                     Array.Sort(_elements, 0, _count, comparer);
688                 }
689             }
690
691             /// <summary>
692             /// Sorts the array.
693             /// </summary>
694             /// <param name="index">The index of the first element to consider in the sort.</param>
695             /// <param name="count">The number of elements to include in the sort.</param>
696             /// <param name="comparer">The comparer to use in sorting. If <c>null</c>, the default comparer is used.</param>
697             public void Sort(int index, int count, IComparer<T>? comparer)
698             {
699                 // Don't rely on Array.Sort's argument validation since our internal array may exceed
700                 // the bounds of the publicly addressable region.
701                 Requires.Range(index >= 0, nameof(index));
702                 Requires.Range(count >= 0 && index + count <= this.Count, nameof(count));
703
704                 if (count > 1)
705                 {
706                     Array.Sort(_elements, index, count, comparer);
707                 }
708             }
709
710             /// <summary>
711             /// Returns an enumerator for the contents of the array.
712             /// </summary>
713             /// <returns>An enumerator.</returns>
714             public IEnumerator<T> GetEnumerator()
715             {
716                 for (int i = 0; i < this.Count; i++)
717                 {
718                     yield return this[i];
719                 }
720             }
721
722             /// <summary>
723             /// Returns an enumerator for the contents of the array.
724             /// </summary>
725             /// <returns>An enumerator.</returns>
726             IEnumerator<T> IEnumerable<T>.GetEnumerator()
727             {
728                 return this.GetEnumerator();
729             }
730
731             /// <summary>
732             /// Returns an enumerator for the contents of the array.
733             /// </summary>
734             /// <returns>An enumerator.</returns>
735             IEnumerator IEnumerable.GetEnumerator()
736             {
737                 return this.GetEnumerator();
738             }
739
740             /// <summary>
741             /// Adds items to this collection.
742             /// </summary>
743             /// <typeparam name="TDerived">The type of source elements.</typeparam>
744             /// <param name="items">The source array.</param>
745             /// <param name="length">The number of elements to add to this array.</param>
746             private void AddRange<TDerived>(TDerived[] items, int length) where TDerived : T
747             {
748                 this.EnsureCapacity(this.Count + length);
749
750                 var offset = this.Count;
751                 this.Count += length;
752
753                 var nodes = _elements;
754                 for (int i = 0; i < length; i++)
755                 {
756                     nodes[offset + i] = items[i];
757                 }
758             }
759         }
760     }
761 }