1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
4 using System.Collections.Generic;
5 using System.Diagnostics;
7 namespace System.Collections.Immutable
9 public partial struct ImmutableArray<T>
12 /// A writable array accessor that can be converted into an <see cref="ImmutableArray{T}"/>
13 /// instance without allocating memory.
15 [DebuggerDisplay("Count = {Count}")]
16 [DebuggerTypeProxy(typeof(ImmutableArrayBuilderDebuggerProxy<>))]
17 public sealed class Builder : IList<T>, IReadOnlyList<T>
20 /// The backing array for the builder.
22 private T[] _elements;
25 /// The number of initialized elements in the array.
30 /// Initializes a new instance of the <see cref="Builder"/> class.
32 /// <param name="capacity">The initial capacity of the internal array.</param>
33 internal Builder(int capacity)
35 Requires.Range(capacity >= 0, nameof(capacity));
36 _elements = new T[capacity];
41 /// Initializes a new instance of the <see cref="Builder"/> class.
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.
54 get { return _elements.Length; }
59 throw new ArgumentException(SR.CapacityMustBeGreaterThanOrEqualToCount, paramName: nameof(value));
62 if (value != _elements.Length)
66 var temp = new T[value];
69 Array.Copy(_elements, temp, _count);
76 _elements = ImmutableArray<T>.Empty.array!;
83 /// Gets or sets the length of the builder.
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"/>.
98 Requires.Range(value >= 0, nameof(value));
102 // Clear the elements of the elements that are effectively removed.
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)
108 Array.Clear(_elements, value, _count - value);
112 for (int i = value; i < this.Count; i++)
114 _elements[i] = default(T)!;
118 else if (value > _count)
121 this.EnsureCapacity(value);
128 private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException();
131 /// Gets or sets the element at the specified index.
133 /// <param name="index">The index.</param>
134 /// <returns></returns>
135 /// <exception cref="IndexOutOfRangeException">
137 public T this[int index]
141 if (index >= this.Count)
143 ThrowIndexOutOfRangeException();
146 return _elements[index];
151 if (index >= this.Count)
153 ThrowIndexOutOfRangeException();
156 _elements[index] = value;
162 /// Gets a read-only reference to the element at the specified index.
164 /// <param name="index">The index.</param>
165 /// <returns></returns>
166 /// <exception cref="IndexOutOfRangeException">
168 public ref readonly T ItemRef(int index)
170 if (index >= this.Count)
172 ThrowIndexOutOfRangeException();
175 return ref this._elements[index];
180 /// Gets a value indicating whether the <see cref="ICollection{T}"/> is read-only.
182 /// <returns>true if the <see cref="ICollection{T}"/> is read-only; otherwise, false.
184 bool ICollection<T>.IsReadOnly
186 get { return false; }
190 /// Returns an immutable copy of the current contents of this collection.
192 /// <returns>An immutable array.</returns>
193 public ImmutableArray<T> ToImmutable()
195 return new ImmutableArray<T>(this.ToArray());
199 /// Extracts the internal array as an <see cref="ImmutableArray{T}"/> and replaces it
200 /// with a zero length array.
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()
206 if (Capacity != Count)
208 throw new InvalidOperationException(SR.CapacityMustEqualCountOnMove);
211 T[] temp = _elements;
212 _elements = ImmutableArray<T>.Empty.array!;
214 return new ImmutableArray<T>(temp);
218 /// Removes all items from the <see cref="ICollection{T}"/>.
226 /// Inserts an item to the <see cref="IList{T}"/> at the specified index.
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)
232 Requires.Range(index >= 0 && index <= this.Count, nameof(index));
233 this.EnsureCapacity(this.Count + 1);
235 if (index < this.Count)
237 Array.Copy(_elements, index, _elements, index + 1, this.Count - index);
241 _elements[index] = item;
245 /// Adds an item to the <see cref="ICollection{T}"/>.
247 /// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
248 public void Add(T item)
250 int newCount = _count + 1;
251 this.EnsureCapacity(newCount);
252 _elements[_count] = item;
257 /// Adds the specified items to the end of the array.
259 /// <param name="items">The items.</param>
260 public void AddRange(IEnumerable<T> items)
262 Requires.NotNull(items, nameof(items));
265 if (items.TryGetCount(out count))
267 this.EnsureCapacity(this.Count + count);
269 if (items.TryCopyTo(_elements, _count))
276 foreach (var item in items)
283 /// Adds the specified items to the end of the array.
285 /// <param name="items">The items.</param>
286 public void AddRange(params T[] items)
288 Requires.NotNull(items, nameof(items));
290 var offset = this.Count;
291 this.Count += items.Length;
293 Array.Copy(items, 0, _elements, offset, items.Length);
297 /// Adds the specified items to the end of the array.
299 /// <param name="items">The items.</param>
300 public void AddRange<TDerived>(TDerived[] items) where TDerived : T
302 Requires.NotNull(items, nameof(items));
304 var offset = this.Count;
305 this.Count += items.Length;
307 Array.Copy(items, 0, _elements, offset, items.Length);
311 /// Adds the specified items to the end of the array.
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)
317 Requires.NotNull(items, nameof(items));
318 Requires.Range(length >= 0 && length <= items.Length, nameof(length));
320 var offset = this.Count;
321 this.Count += length;
323 Array.Copy(items, 0, _elements, offset, length);
327 /// Adds the specified items to the end of the array.
329 /// <param name="items">The items.</param>
330 public void AddRange(ImmutableArray<T> items)
332 this.AddRange(items, items.Length);
336 /// Adds the specified items to the end of the array.
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)
342 Requires.Range(length >= 0, nameof(length));
344 if (items.array != null)
346 this.AddRange(items.array, length);
351 /// Adds the specified items to the end of the array.
353 /// <param name="items">The items.</param>
354 public void AddRange<TDerived>(ImmutableArray<TDerived> items) where TDerived : T
356 if (items.array != null)
358 this.AddRange(items.array);
363 /// Adds the specified items to the end of the array.
365 /// <param name="items">The items.</param>
366 public void AddRange(Builder items)
368 Requires.NotNull(items, nameof(items));
369 this.AddRange(items._elements, items.Count);
373 /// Adds the specified items to the end of the array.
375 /// <param name="items">The items.</param>
376 public void AddRange<TDerived>(ImmutableArray<TDerived>.Builder items) where TDerived : T
378 Requires.NotNull(items, nameof(items));
379 this.AddRange(items._elements, items.Count);
383 /// Removes the specified element.
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)
389 int index = this.IndexOf(element);
392 this.RemoveAt(index);
400 /// Removes the <see cref="IList{T}"/> item at the specified index.
402 /// <param name="index">The zero-based index of the item to remove.</param>
403 public void RemoveAt(int index)
405 Requires.Range(index >= 0 && index < this.Count, nameof(index));
407 if (index < this.Count - 1)
409 Array.Copy(_elements, index + 1, _elements, index, this.Count - index - 1);
416 /// Determines whether the <see cref="ICollection{T}"/> contains a specific value.
418 /// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
420 /// true if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, false.
422 public bool Contains(T item)
424 return this.IndexOf(item) >= 0;
428 /// Creates a new array with the current contents of this Builder.
437 T[] result = new T[this.Count];
438 Array.Copy(_elements, result, this.Count);
443 /// Copies the current contents to the specified array.
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)
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);
455 /// Resizes the array to accommodate the specified capacity requirement.
457 /// <param name="capacity">The required capacity.</param>
458 private void EnsureCapacity(int capacity)
460 if (_elements.Length < capacity)
462 int newCapacity = Math.Max(_elements.Length * 2, capacity);
463 Array.Resize(ref _elements, newCapacity);
468 /// Determines the index of a specific item in the <see cref="IList{T}"/>.
470 /// <param name="item">The object to locate in the <see cref="IList{T}"/>.</param>
472 /// The index of <paramref name="item"/> if found in the list; otherwise, -1.
474 public int IndexOf(T item)
476 return this.IndexOf(item, 0, _count, EqualityComparer<T>.Default);
480 /// Searches the array for the specified item.
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)
487 return this.IndexOf(item, startIndex, this.Count - startIndex, EqualityComparer<T>.Default);
491 /// Searches the array for the specified item.
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)
499 return this.IndexOf(item, startIndex, count, EqualityComparer<T>.Default);
503 /// Searches the array for the specified item.
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.
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)
515 if (count == 0 && startIndex == 0)
520 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
521 Requires.Range(count >= 0 && startIndex + count <= this.Count, nameof(count));
523 equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
524 if (equalityComparer == EqualityComparer<T>.Default)
526 return Array.IndexOf(_elements, item, startIndex, count);
530 for (int i = startIndex; i < startIndex + count; i++)
532 if (equalityComparer.Equals(_elements[i], item))
543 /// Searches the array for the specified item in reverse.
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)
554 return this.LastIndexOf(item, this.Count - 1, this.Count, EqualityComparer<T>.Default);
558 /// Searches the array for the specified item in reverse.
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)
565 if (this.Count == 0 && startIndex == 0)
570 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
572 return this.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer<T>.Default);
576 /// Searches the array for the specified item in reverse.
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)
584 return this.LastIndexOf(item, startIndex, count, EqualityComparer<T>.Default);
588 /// Searches the array for the specified item in reverse.
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)
597 if (count == 0 && startIndex == 0)
602 Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
603 Requires.Range(count >= 0 && startIndex - count + 1 >= 0, nameof(count));
605 equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
606 if (equalityComparer == EqualityComparer<T>.Default)
608 return Array.LastIndexOf(_elements, item, startIndex, count);
612 for (int i = startIndex; i >= startIndex - count + 1; i--)
614 if (equalityComparer.Equals(item, _elements[i]))
625 /// Reverses the order of elements in the collection.
627 public void Reverse()
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>.
635 T[] array = _elements;
653 Array.Sort(_elements, 0, this.Count, Comparer<T>.Default);
658 /// Sorts the elements in the entire array using
659 /// the specified <see cref="Comparison{T}"/>.
661 /// <param name="comparison">
662 /// The <see cref="Comparison{T}"/> to use when comparing elements.
664 /// <exception cref="ArgumentNullException"><paramref name="comparison"/> is null.</exception>
665 public void Sort(Comparison<T> comparison)
667 Requires.NotNull(comparison, nameof(comparison));
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));
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)
687 Array.Sort(_elements, 0, _count, comparer);
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)
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));
706 Array.Sort(_elements, index, count, comparer);
711 /// Returns an enumerator for the contents of the array.
713 /// <returns>An enumerator.</returns>
714 public IEnumerator<T> GetEnumerator()
716 for (int i = 0; i < this.Count; i++)
718 yield return this[i];
723 /// Returns an enumerator for the contents of the array.
725 /// <returns>An enumerator.</returns>
726 IEnumerator<T> IEnumerable<T>.GetEnumerator()
728 return this.GetEnumerator();
732 /// Returns an enumerator for the contents of the array.
734 /// <returns>An enumerator.</returns>
735 IEnumerator IEnumerable.GetEnumerator()
737 return this.GetEnumerator();
741 /// Adds items to this collection.
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
748 this.EnsureCapacity(this.Count + length);
750 var offset = this.Count;
751 this.Count += length;
753 var nodes = _elements;
754 for (int i = 0; i < length; i++)
756 nodes[offset + i] = items[i];