ReadOnlyArray.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. ////REVIEW: switch to something that doesn't require the backing store to be an actual array?
  5. //// (maybe switch m_Array to an InlinedArray and extend InlinedArray to allow having three configs:
  6. //// 1. firstValue only, 2. firstValue + additionalValues, 3. everything in additionalValues)
  7. namespace UnityEngine.InputSystem.Utilities
  8. {
  9. /// <summary>
  10. /// Read-only access to an array or to a slice of an array.
  11. /// </summary>
  12. /// <typeparam name="TValue">Type of values stored in the array.</typeparam>
  13. /// <remarks>
  14. /// The purpose of this struct is to allow exposing internal arrays directly such that no
  15. /// boxing and no going through interfaces is required but at the same time not allowing
  16. /// the internal arrays to be modified.
  17. ///
  18. /// It differs from <c>ReadOnlySpan&lt;T&gt;</c> in that it can be stored on the heap and differs
  19. /// from <c>ReadOnlyCollection&lt;T&gt;</c> in that it supports slices directly without needing
  20. /// an intermediate object representing the slice.
  21. ///
  22. /// Note that in most cases, the ReadOnlyArray instance should be treated as a <em>temporary</em>.
  23. /// The actual array referred to by a ReadOnlyArray instance is usually owned and probably mutated
  24. /// by another piece of code. When that code makes changes to the array, the ReadOnlyArray
  25. /// instance will not get updated.
  26. /// </remarks>
  27. public struct ReadOnlyArray<TValue> : IReadOnlyList<TValue>
  28. {
  29. internal TValue[] m_Array;
  30. internal int m_StartIndex;
  31. internal int m_Length;
  32. /// <summary>
  33. /// Construct a read-only array covering all of the given array.
  34. /// </summary>
  35. /// <param name="array">Array to index.</param>
  36. public ReadOnlyArray(TValue[] array)
  37. {
  38. m_Array = array;
  39. m_StartIndex = 0;
  40. m_Length = array?.Length ?? 0;
  41. }
  42. /// <summary>
  43. /// Construct a read-only array that covers only the given slice of <paramref name="array"/>.
  44. /// </summary>
  45. /// <param name="array">Array to index.</param>
  46. /// <param name="index">Index at which to start indexing <paramref name="array"/>. The given element
  47. /// becomes index #0 for the read-only array.</param>
  48. /// <param name="length">Length of the slice to index from <paramref name="array"/>.</param>
  49. public ReadOnlyArray(TValue[] array, int index, int length)
  50. {
  51. m_Array = array;
  52. m_StartIndex = index;
  53. m_Length = length;
  54. }
  55. /// <summary>
  56. /// Convert to array.
  57. /// </summary>
  58. /// <returns>A new array containing a copy of the contents of the read-only array.</returns>
  59. public TValue[] ToArray()
  60. {
  61. var result = new TValue[m_Length];
  62. if (m_Length > 0)
  63. Array.Copy(m_Array, m_StartIndex, result, 0, m_Length);
  64. return result;
  65. }
  66. public int IndexOf(Predicate<TValue> predicate)
  67. {
  68. if (predicate == null)
  69. throw new ArgumentNullException(nameof(predicate));
  70. for (var i = 0; i < m_Length; ++i)
  71. if (predicate(m_Array[m_StartIndex + i]))
  72. return i;
  73. return -1;
  74. }
  75. /// <inheritdoc />
  76. public IEnumerator<TValue> GetEnumerator()
  77. {
  78. return new Enumerator<TValue>(m_Array, m_StartIndex, m_Length);
  79. }
  80. IEnumerator IEnumerable.GetEnumerator()
  81. {
  82. return GetEnumerator();
  83. }
  84. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "`ToXXX` message only really makes sense as static, which is not recommended for generic types.")]
  85. public static implicit operator ReadOnlyArray<TValue>(TValue[] array)
  86. {
  87. return new ReadOnlyArray<TValue>(array);
  88. }
  89. /// <summary>
  90. /// Number of elements in the array.
  91. /// </summary>
  92. public int Count => m_Length;
  93. /// <summary>
  94. /// Return the element at the given index.
  95. /// </summary>
  96. /// <param name="index">Index into the array.</param>
  97. /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/>.</exception>
  98. /// <exception cref="InvalidOperationException"></exception>
  99. public TValue this[int index]
  100. {
  101. get
  102. {
  103. if (index < 0 || index >= m_Length)
  104. throw new ArgumentOutOfRangeException(nameof(index));
  105. // We allow array to be null as we are patching up ReadOnlyArrays in a separate
  106. // path in several places.
  107. if (m_Array == null)
  108. throw new InvalidOperationException();
  109. return m_Array[m_StartIndex + index];
  110. }
  111. }
  112. internal class Enumerator<T> : IEnumerator<T>
  113. {
  114. private readonly T[] m_Array;
  115. private readonly int m_IndexStart;
  116. private readonly int m_IndexEnd;
  117. private int m_Index;
  118. public Enumerator(T[] array, int index, int length)
  119. {
  120. m_Array = array;
  121. m_IndexStart = index - 1; // First call to MoveNext() moves us to first valid index.
  122. m_IndexEnd = index + length;
  123. m_Index = m_IndexStart;
  124. }
  125. public void Dispose()
  126. {
  127. }
  128. public bool MoveNext()
  129. {
  130. if (m_Index < m_IndexEnd)
  131. ++m_Index;
  132. return (m_Index != m_IndexEnd);
  133. }
  134. public void Reset()
  135. {
  136. m_Index = m_IndexStart;
  137. }
  138. public T Current
  139. {
  140. get
  141. {
  142. if (m_Index == m_IndexEnd)
  143. throw new InvalidOperationException("Iterated beyond end");
  144. return m_Array[m_Index];
  145. }
  146. }
  147. object IEnumerator.Current => Current;
  148. }
  149. }
  150. /// <summary>
  151. /// Extension methods to help with <see cref="ReadOnlyArrayExtensions"/> contents.
  152. /// </summary>
  153. public static class ReadOnlyArrayExtensions
  154. {
  155. public static bool Contains<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  156. where TValue : IComparable<TValue>
  157. {
  158. for (var i = 0; i < array.m_Length; ++i)
  159. if (array.m_Array[array.m_StartIndex + i].CompareTo(value) == 0)
  160. return true;
  161. return false;
  162. }
  163. public static bool ContainsReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  164. where TValue : class
  165. {
  166. return IndexOfReference(array, value) != -1;
  167. }
  168. public static int IndexOfReference<TValue>(this ReadOnlyArray<TValue> array, TValue value)
  169. where TValue : class
  170. {
  171. for (var i = 0; i < array.m_Length; ++i)
  172. if (ReferenceEquals(array.m_Array[array.m_StartIndex + i], value))
  173. return i;
  174. return -1;
  175. }
  176. }
  177. }