// // System.Collections.Generic.List // // Authors: // Ben Maurer (bmaurer@ximian.com) // Martin Baulig (martin@ximian.com) // Carlos Alberto Cortez (calberto.cortez@gmail.com) // David Waite (mass@akuma.org) // // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com) // Copyright (C) 2005 David Waite // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace Spine { [DebuggerDisplay("Count={Count}")] public class ExposedList : IEnumerable { public T[] Items; public int Count; private const int DefaultCapacity = 4; private static readonly T[] EmptyArray = new T[0]; private int version; public ExposedList () { Items = EmptyArray; } public ExposedList (IEnumerable collection) { CheckCollection(collection); // initialize to needed size (if determinable) ICollection c = collection as ICollection; if (c == null) { Items = EmptyArray; AddEnumerable(collection); } else { Items = new T[c.Count]; AddCollection(c); } } public ExposedList (int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); Items = new T[capacity]; } internal ExposedList (T[] data, int size) { Items = data; Count = size; } public void Add (T item) { // If we check to see if we need to grow before trying to grow // we can speed things up by 25% if (Count == Items.Length) GrowIfNeeded(1); Items[Count++] = item; version++; } public void GrowIfNeeded (int addedCount) { int minimumSize = Count + addedCount; if (minimumSize > Items.Length) Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); } public ExposedList Resize (int newSize) { int itemsLength = Items.Length; var oldItems = Items; if (newSize > itemsLength) { Array.Resize(ref Items, newSize); // var newItems = new T[newSize]; // Array.Copy(oldItems, newItems, Count); // Items = newItems; } else if (newSize < itemsLength) { // Allow nulling of T reference type to allow GC. for (int i = newSize; i < itemsLength; i++) oldItems[i] = default(T); } Count = newSize; return this; } public void EnsureCapacity (int min) { if (Items.Length < min) { int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } } private void CheckRange (int index, int count) { if (index < 0) throw new ArgumentOutOfRangeException("index"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if ((uint)index + (uint)count > (uint)Count) throw new ArgumentException("index and count exceed length of list"); } private void AddCollection (ICollection collection) { int collectionCount = collection.Count; if (collectionCount == 0) return; GrowIfNeeded(collectionCount); collection.CopyTo(Items, Count); Count += collectionCount; } private void AddEnumerable (IEnumerable enumerable) { foreach (T t in enumerable) { Add(t); } } // Additional overload provided because ExposedList only implements IEnumerable, // leading to sub-optimal behavior: It grows multiple times as it assumes not // to know the final size ahead of insertion. public void AddRange (ExposedList list) { CheckCollection(list); int collectionCount = list.Count; if (collectionCount == 0) return; GrowIfNeeded(collectionCount); list.CopyTo(Items, Count); Count += collectionCount; version++; } public void AddRange (IEnumerable collection) { CheckCollection(collection); ICollection c = collection as ICollection; if (c != null) AddCollection(c); else AddEnumerable(collection); version++; } public int BinarySearch (T item) { return Array.BinarySearch(Items, 0, Count, item); } public int BinarySearch (T item, IComparer comparer) { return Array.BinarySearch(Items, 0, Count, item, comparer); } public int BinarySearch (int index, int count, T item, IComparer comparer) { CheckRange(index, count); return Array.BinarySearch(Items, index, count, item, comparer); } public void Clear (bool clearArray = true) { if (clearArray) Array.Clear(Items, 0, Items.Length); Count = 0; version++; } public bool Contains (T item) { return Array.IndexOf(Items, item, 0, Count) != -1; } public ExposedList ConvertAll (Converter converter) { if (converter == null) throw new ArgumentNullException("converter"); ExposedList u = new ExposedList(Count); for (int i = 0; i < Count; i++) u.Items[i] = converter(Items[i]); u.Count = Count; return u; } public void CopyTo (T[] array) { Array.Copy(Items, 0, array, 0, Count); } public void CopyTo (T[] array, int arrayIndex) { Array.Copy(Items, 0, array, arrayIndex, Count); } public void CopyTo (int index, T[] array, int arrayIndex, int count) { CheckRange(index, count); Array.Copy(Items, index, array, arrayIndex, count); } public bool Exists (Predicate match) { CheckMatch(match); return GetIndex(0, Count, match) != -1; } public T Find (Predicate match) { CheckMatch(match); int i = GetIndex(0, Count, match); return (i != -1) ? Items[i] : default(T); } private static void CheckMatch (Predicate match) { if (match == null) throw new ArgumentNullException("match"); } public ExposedList FindAll (Predicate match) { CheckMatch(match); return FindAllList(match); } private ExposedList FindAllList (Predicate match) { ExposedList results = new ExposedList(); for (int i = 0; i < Count; i++) if (match(Items[i])) results.Add(Items[i]); return results; } public int FindIndex (Predicate match) { CheckMatch(match); return GetIndex(0, Count, match); } public int FindIndex (int startIndex, Predicate match) { CheckMatch(match); CheckIndex(startIndex); return GetIndex(startIndex, Count - startIndex, match); } public int FindIndex (int startIndex, int count, Predicate match) { CheckMatch(match); CheckRange(startIndex, count); return GetIndex(startIndex, count, match); } private int GetIndex (int startIndex, int count, Predicate match) { int end = startIndex + count; for (int i = startIndex; i < end; i++) if (match(Items[i])) return i; return -1; } public T FindLast (Predicate match) { CheckMatch(match); int i = GetLastIndex(0, Count, match); return i == -1 ? default(T) : Items[i]; } public int FindLastIndex (Predicate match) { CheckMatch(match); return GetLastIndex(0, Count, match); } public int FindLastIndex (int startIndex, Predicate match) { CheckMatch(match); CheckIndex(startIndex); return GetLastIndex(0, startIndex + 1, match); } public int FindLastIndex (int startIndex, int count, Predicate match) { CheckMatch(match); int start = startIndex - count + 1; CheckRange(start, count); return GetLastIndex(start, count, match); } private int GetLastIndex (int startIndex, int count, Predicate match) { // unlike FindLastIndex, takes regular params for search range for (int i = startIndex + count; i != startIndex; ) if (match(Items[--i])) return i; return -1; } public void ForEach (Action action) { if (action == null) throw new ArgumentNullException("action"); for (int i = 0; i < Count; i++) action(Items[i]); } public Enumerator GetEnumerator () { return new Enumerator(this); } public ExposedList GetRange (int index, int count) { CheckRange(index, count); T[] tmpArray = new T[count]; Array.Copy(Items, index, tmpArray, 0, count); return new ExposedList(tmpArray, count); } public int IndexOf (T item) { return Array.IndexOf(Items, item, 0, Count); } public int IndexOf (T item, int index) { CheckIndex(index); return Array.IndexOf(Items, item, index, Count - index); } public int IndexOf (T item, int index, int count) { if (index < 0) throw new ArgumentOutOfRangeException("index"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if ((uint)index + (uint)count > (uint)Count) throw new ArgumentOutOfRangeException("index and count exceed length of list"); return Array.IndexOf(Items, item, index, count); } private void Shift (int start, int delta) { if (delta < 0) start -= delta; if (start < Count) Array.Copy(Items, start, Items, start + delta, Count - start); Count += delta; if (delta < 0) Array.Clear(Items, Count, -delta); } private void CheckIndex (int index) { if (index < 0 || (uint)index > (uint)Count) throw new ArgumentOutOfRangeException("index"); } public void Insert (int index, T item) { CheckIndex(index); if (Count == Items.Length) GrowIfNeeded(1); Shift(index, 1); Items[index] = item; version++; } private void CheckCollection (IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); } public void InsertRange (int index, IEnumerable collection) { CheckCollection(collection); CheckIndex(index); if (collection == this) { T[] buffer = new T[Count]; CopyTo(buffer, 0); GrowIfNeeded(Count); Shift(index, buffer.Length); Array.Copy(buffer, 0, Items, index, buffer.Length); } else { ICollection c = collection as ICollection; if (c != null) InsertCollection(index, c); else InsertEnumeration(index, collection); } version++; } private void InsertCollection (int index, ICollection collection) { int collectionCount = collection.Count; GrowIfNeeded(collectionCount); Shift(index, collectionCount); collection.CopyTo(Items, index); } private void InsertEnumeration (int index, IEnumerable enumerable) { foreach (T t in enumerable) Insert(index++, t); } public int LastIndexOf (T item) { return Array.LastIndexOf(Items, item, Count - 1, Count); } public int LastIndexOf (T item, int index) { CheckIndex(index); return Array.LastIndexOf(Items, item, index, index + 1); } public int LastIndexOf (T item, int index, int count) { if (index < 0) throw new ArgumentOutOfRangeException("index", index, "index is negative"); if (count < 0) throw new ArgumentOutOfRangeException("count", count, "count is negative"); if (index - count + 1 < 0) throw new ArgumentOutOfRangeException("count", count, "count is too large"); return Array.LastIndexOf(Items, item, index, count); } public bool Remove (T item) { int loc = IndexOf(item); if (loc != -1) RemoveAt(loc); return loc != -1; } public int RemoveAll (Predicate match) { CheckMatch(match); int i = 0; int j = 0; // Find the first item to remove for (i = 0; i < Count; i++) if (match(Items[i])) break; if (i == Count) return 0; version++; // Remove any additional items for (j = i + 1; j < Count; j++) { if (!match(Items[j])) Items[i++] = Items[j]; } if (j - i > 0) Array.Clear(Items, i, j - i); Count = i; return (j - i); } public void RemoveAt (int index) { if (index < 0 || (uint)index >= (uint)Count) throw new ArgumentOutOfRangeException("index"); Shift(index, -1); Array.Clear(Items, Count, 1); version++; } // Spine Added Method // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. public T Pop () { if (Count == 0) throw new InvalidOperationException("List is empty. Nothing to pop."); int i = Count - 1; T item = Items[i]; Items[i] = default(T); Count--; version++; return item; } public void RemoveRange (int index, int count) { CheckRange(index, count); if (count > 0) { Shift(index, -count); Array.Clear(Items, Count, count); version++; } } public void Reverse () { Array.Reverse(Items, 0, Count); version++; } public void Reverse (int index, int count) { CheckRange(index, count); Array.Reverse(Items, index, count); version++; } public void Sort () { Array.Sort(Items, 0, Count, Comparer.Default); version++; } public void Sort (IComparer comparer) { Array.Sort(Items, 0, Count, comparer); version++; } public void Sort (Comparison comparison) { Array.Sort(Items, comparison); version++; } public void Sort (int index, int count, IComparer comparer) { CheckRange(index, count); Array.Sort(Items, index, count, comparer); version++; } public T[] ToArray () { T[] t = new T[Count]; Array.Copy(Items, t, Count); return t; } public void TrimExcess () { Capacity = Count; } public bool TrueForAll (Predicate match) { CheckMatch(match); for (int i = 0; i < Count; i++) if (!match(Items[i])) return false; return true; } public int Capacity { get { return Items.Length; } set { if ((uint)value < (uint)Count) throw new ArgumentOutOfRangeException(); Array.Resize(ref Items, value); } } #region Interface implementations. IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } #endregion public struct Enumerator : IEnumerator, IDisposable { private ExposedList l; private int next; private int ver; private T current; internal Enumerator (ExposedList l) : this() { this.l = l; ver = l.version; } public void Dispose () { l = null; } private void VerifyState () { if (l == null) throw new ObjectDisposedException(GetType().FullName); if (ver != l.version) throw new InvalidOperationException( "Collection was modified; enumeration operation may not execute."); } public bool MoveNext () { VerifyState(); if (next < 0) return false; if (next < l.Count) { current = l.Items[next++]; return true; } next = -1; return false; } public T Current { get { return current; } } void IEnumerator.Reset () { VerifyState(); next = 0; } object IEnumerator.Current { get { VerifyState(); if (next <= 0) throw new InvalidOperationException(); return current; } } } } }