diff options
Diffstat (limited to 'src/mscorlib/src/System/Collections')
53 files changed, 21062 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Collections/ArrayList.cs b/src/mscorlib/src/System/Collections/ArrayList.cs new file mode 100644 index 0000000000..94f4dc74e8 --- /dev/null +++ b/src/mscorlib/src/System/Collections/ArrayList.cs @@ -0,0 +1,2635 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Implements a dynamically sized List as an array, +** and provides many convenience methods for treating +** an array as an IList. +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Runtime; + using System.Security; + using System.Security.Permissions; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Runtime.Serialization; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + // Implements a variable-size List that uses an array of objects to store the + // elements. A ArrayList has a capacity, which is the allocated length + // of the internal array. As elements are added to a ArrayList, the capacity + // of the ArrayList is automatically increased as required by reallocating the + // internal array. + // +#if FEATURE_CORECLR + [FriendAccessAllowed] +#endif + [DebuggerTypeProxy(typeof(System.Collections.ArrayList.ArrayListDebugView))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class ArrayList : IList, ICloneable + { + private Object[] _items; + [ContractPublicPropertyName("Count")] + private int _size; + private int _version; + [NonSerialized] + private Object _syncRoot; + + private const int _defaultCapacity = 4; + private static readonly Object[] emptyArray = EmptyArray<Object>.Value; + + // Note: this constructor is a bogus constructor that does nothing + // and is for use only with SyncArrayList. + internal ArrayList( bool trash ) + { + } + + // Constructs a ArrayList. The list is initially empty and has a capacity + // of zero. Upon adding the first element to the list the capacity is + // increased to _defaultCapacity, and then increased in multiples of two as required. + public ArrayList() { + _items = emptyArray; + } + + // Constructs a ArrayList with a given initial capacity. The list is + // initially empty, but will have room for the given number of elements + // before any reallocations are required. + // + public ArrayList(int capacity) { + if (capacity < 0) throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_MustBeNonNegNum", "capacity")); + Contract.EndContractBlock(); + + if (capacity == 0) + _items = emptyArray; + else + _items = new Object[capacity]; + } + + // Constructs a ArrayList, copying the contents of the given collection. The + // size and capacity of the new list will both be equal to the size of the + // given collection. + // + public ArrayList(ICollection c) { + if (c==null) + throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection")); + Contract.EndContractBlock(); + + int count = c.Count; + if (count == 0) + { + _items = emptyArray; + } + else { + _items = new Object[count]; + AddRange(c); + } + } + + // Gets and sets the capacity of this list. The capacity is the size of + // the internal array used to hold items. When set, the internal + // array of the list is reallocated to the given capacity. + // + public virtual int Capacity { + get { + Contract.Ensures(Contract.Result<int>() >= Count); + return _items.Length; + } + set { + if (value < _size) { + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity")); + } + Contract.Ensures(Capacity >= 0); + Contract.EndContractBlock(); + // We don't want to update the version number when we change the capacity. + // Some existing applications have dependency on this. + if (value != _items.Length) { + if (value > 0) { + Object[] newItems = new Object[value]; + if (_size > 0) { + Array.Copy(_items, 0, newItems, 0, _size); + } + _items = newItems; + } + else { + _items = new Object[_defaultCapacity]; + } + } + } + } + + // Read-only property describing how many elements are in the List. + public virtual int Count { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + return _size; + } + } + + public virtual bool IsFixedSize { + get { return false; } + } + + + // Is this ArrayList read-only? + public virtual bool IsReadOnly { + get { return false; } + } + + // Is this ArrayList synchronized (thread-safe)? + public virtual bool IsSynchronized { + get { return false; } + } + + // Synchronization root for this object. + public virtual Object SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + // Sets or Gets the element at the given index. + // + public virtual Object this[int index] { + get { + if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + return _items[index]; + } + set { + if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + _items[index] = value; + _version++; + } + } + + // Creates a ArrayList wrapper for a particular IList. This does not + // copy the contents of the IList, but only wraps the ILIst. So any + // changes to the underlying list will affect the ArrayList. This would + // be useful if you want to Reverse a subrange of an IList, or want to + // use a generic BinarySearch or Sort method without implementing one yourself. + // However, since these methods are generic, the performance may not be + // nearly as good for some operations as they would be on the IList itself. + // + public static ArrayList Adapter(IList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + return new IListWrapper(list); + } + + // Adds the given object to the end of this list. The size of the list is + // increased by one. If required, the capacity of the list is doubled + // before adding the new element. + // + public virtual int Add(Object value) { + Contract.Ensures(Contract.Result<int>() >= 0); + if (_size == _items.Length) EnsureCapacity(_size + 1); + _items[_size] = value; + _version++; + return _size++; + } + + // Adds the elements of the given collection to the end of this list. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. + // + public virtual void AddRange(ICollection c) { + InsertRange(_size, c); + } + + // Searches a section of the list for a given element using a binary search + // algorithm. Elements of the list are compared to the search value using + // the given IComparer interface. If comparer is null, elements of + // the list are compared to the search value using the IComparable + // interface, which in that case must be implemented by all elements of the + // list and the given search value. This method assumes that the given + // section of the list is already sorted; if this is not the case, the + // result will be incorrect. + // + // The method returns the index of the given value in the list. If the + // list does not contain the given value, the method returns a negative + // integer. The bitwise complement operator (~) can be applied to a + // negative result to produce the index of the first element (if any) that + // is larger than the given search value. This is also the index at which + // the search value should be inserted into the list in order for the list + // to remain sorted. + // + // The method uses the Array.BinarySearch method to perform the + // search. + // + public virtual int BinarySearch(int index, int count, Object value, IComparer comparer) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.Ensures(Contract.Result<int>() < index + count); + Contract.EndContractBlock(); + + return Array.BinarySearch((Array)_items, index, count, value, comparer); + } + + public virtual int BinarySearch(Object value) + { + Contract.Ensures(Contract.Result<int>() < Count); + return BinarySearch(0, Count, value, null); + } + + public virtual int BinarySearch(Object value, IComparer comparer) + { + Contract.Ensures(Contract.Result<int>() < Count); + return BinarySearch(0, Count, value, comparer); + } + + + // Clears the contents of ArrayList. + public virtual void Clear() { + if (_size > 0) + { + Array.Clear(_items, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references. + _size = 0; + } + _version++; + } + + // Clones this ArrayList, doing a shallow copy. (A copy is made of all + // Object references in the ArrayList, but the Objects pointed to + // are not cloned). + public virtual Object Clone() + { + Contract.Ensures(Contract.Result<Object>() != null); + ArrayList la = new ArrayList(_size); + la._size = _size; + la._version = _version; + Array.Copy(_items, 0, la._items, 0, _size); + return la; + } + + + // Contains returns true if the specified element is in the ArrayList. + // It does a linear, O(n) search. Equality is determined by calling + // item.Equals(). + // + public virtual bool Contains(Object item) { + if (item==null) { + for(int i=0; i<_size; i++) + if (_items[i]==null) + return true; + return false; + } + else { + for(int i=0; i<_size; i++) + if ( (_items[i] != null) && (_items[i].Equals(item)) ) + return true; + return false; + } + } + + // Copies this ArrayList into array, which must be of a + // compatible array type. + // + public virtual void CopyTo(Array array) { + CopyTo(array, 0); + } + + // Copies this ArrayList into array, which must be of a + // compatible array type. + // + public virtual void CopyTo(Array array, int arrayIndex) { + if ((array != null) && (array.Rank != 1)) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + Contract.EndContractBlock(); + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, 0, array, arrayIndex, _size); + } + + // Copies a section of this list to the given array at the given index. + // + // The method uses the Array.Copy method to copy the elements. + // + public virtual void CopyTo(int index, Array array, int arrayIndex, int count) { + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + if ((array != null) && (array.Rank != 1)) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + Contract.EndContractBlock(); + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, index, array, arrayIndex, count); + } + + // Ensures that the capacity of this list is at least the given minimum + // value. If the currect capacity of the list is less than min, the + // capacity is increased to twice the current capacity or to min, + // whichever is larger. + private void EnsureCapacity(int min) { + if (_items.Length < min) { + int newCapacity = _items.Length == 0? _defaultCapacity: _items.Length * 2; + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + // Returns a list wrapper that is fixed at the current size. Operations + // that add or remove items will fail, however, replacing items is allowed. + // + public static IList FixedSize(IList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<IList>() != null); + Contract.EndContractBlock(); + return new FixedSizeList(list); + } + + // Returns a list wrapper that is fixed at the current size. Operations + // that add or remove items will fail, however, replacing items is allowed. + // + public static ArrayList FixedSize(ArrayList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + return new FixedSizeArrayList(list); + } + + // Returns an enumerator for this list with the given + // permission for removal of elements. If modifications made to the list + // while an enumeration is in progress, the MoveNext and + // GetObject methods of the enumerator will throw an exception. + // + public virtual IEnumerator GetEnumerator() { + Contract.Ensures(Contract.Result<IEnumerator>() != null); + return new ArrayListEnumeratorSimple(this); + } + + // Returns an enumerator for a section of this list with the given + // permission for removal of elements. If modifications made to the list + // while an enumeration is in progress, the MoveNext and + // GetObject methods of the enumerator will throw an exception. + // + public virtual IEnumerator GetEnumerator(int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Contract.Result<IEnumerator>() != null); + Contract.EndContractBlock(); + + return new ArrayListEnumerator(this, index, count); + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards from beginning to end. + // The elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public virtual int IndexOf(Object value) { + Contract.Ensures(Contract.Result<int>() < Count); + return Array.IndexOf((Array)_items, value, 0, _size); + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // startIndex and ending at count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public virtual int IndexOf(Object value, int startIndex) { + if (startIndex > _size) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + return Array.IndexOf((Array)_items, value, startIndex, _size - startIndex); + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // startIndex and upto count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public virtual int IndexOf(Object value, int startIndex, int count) { + if (startIndex > _size) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if (count <0 || startIndex > _size - count) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + return Array.IndexOf((Array)_items, value, startIndex, count); + } + + // Inserts an element into this list at a given index. The size of the list + // is increased by one. If required, the capacity of the list is doubled + // before inserting the new element. + // + public virtual void Insert(int index, Object value) { + // Note that insertions at the end are legal. + if (index < 0 || index > _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_ArrayListInsert")); + //Contract.Ensures(Count == Contract.OldValue(Count) + 1); + Contract.EndContractBlock(); + + if (_size == _items.Length) EnsureCapacity(_size + 1); + if (index < _size) { + Array.Copy(_items, index, _items, index + 1, _size - index); + } + _items[index] = value; + _size++; + _version++; + } + + // Inserts the elements of the given collection at a given index. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. Ranges may be added + // to the end of the list by setting index to the ArrayList's size. + // + public virtual void InsertRange(int index, ICollection c) { + if (c==null) + throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection")); + if (index < 0 || index > _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + //Contract.Ensures(Count == Contract.OldValue(Count) + c.Count); + Contract.EndContractBlock(); + + int count = c.Count; + if (count > 0) { + EnsureCapacity(_size + count); + // shift existing items + if (index < _size) { + Array.Copy(_items, index, _items, index + count, _size - index); + } + + Object[] itemsToInsert = new Object[count]; + c.CopyTo(itemsToInsert, 0); + itemsToInsert.CopyTo(_items, index); + _size += count; + _version++; + } + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at the end + // and ending at the first element in the list. The elements of the list + // are compared to the given value using the Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public virtual int LastIndexOf(Object value) + { + Contract.Ensures(Contract.Result<int>() < _size); + return LastIndexOf(value, _size - 1, _size); + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // startIndex and ending at the first element in the list. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public virtual int LastIndexOf(Object value, int startIndex) + { + if (startIndex >= _size) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + return LastIndexOf(value, startIndex, startIndex + 1); + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // startIndex and upto count elements. The elements of + // the list are compared to the given value using the Object.Equals + // method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public virtual int LastIndexOf(Object value, int startIndex, int count) { + if (Count != 0 && (startIndex < 0 || count < 0)) + throw new ArgumentOutOfRangeException((startIndex<0 ? "startIndex" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + + if (_size == 0) // Special case for an empty list + return -1; + + if (startIndex >= _size || count > startIndex + 1) + throw new ArgumentOutOfRangeException((startIndex>=_size ? "startIndex" : "count"), Environment.GetResourceString("ArgumentOutOfRange_BiggerThanCollection")); + + return Array.LastIndexOf((Array)_items, value, startIndex, count); + } + + // Returns a read-only IList wrapper for the given IList. + // +#if FEATURE_CORECLR + [FriendAccessAllowed] +#endif + public static IList ReadOnly(IList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<IList>() != null); + Contract.EndContractBlock(); + return new ReadOnlyList(list); + } + + // Returns a read-only ArrayList wrapper for the given ArrayList. + // + public static ArrayList ReadOnly(ArrayList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + return new ReadOnlyArrayList(list); + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + // + public virtual void Remove(Object obj) { + Contract.Ensures(Count >= 0); + + int index = IndexOf(obj); + BCLDebug.Correctness(index >= 0 || !(obj is Int32), "You passed an Int32 to Remove that wasn't in the ArrayList." + Environment.NewLine + "Did you mean RemoveAt? int: "+obj+" Count: "+Count); + if (index >=0) + RemoveAt(index); + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + // + public virtual void RemoveAt(int index) { + if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.Ensures(Count >= 0); + //Contract.Ensures(Count == Contract.OldValue(Count) - 1); + Contract.EndContractBlock(); + + _size--; + if (index < _size) { + Array.Copy(_items, index + 1, _items, index, _size - index); + } + _items[_size] = null; + _version++; + } + + // Removes a range of elements from this list. + // + public virtual void RemoveRange(int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Count >= 0); + //Contract.Ensures(Count == Contract.OldValue(Count) - count); + Contract.EndContractBlock(); + + if (count > 0) { + int i = _size; + _size -= count; + if (index < _size) { + Array.Copy(_items, index + count, _items, index, _size - index); + } + while (i > _size) _items[--i] = null; + _version++; + } + } + + // Returns an IList that contains count copies of value. + // + public static ArrayList Repeat(Object value, int count) { + if (count < 0) + throw new ArgumentOutOfRangeException("count",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + + ArrayList list = new ArrayList((count>_defaultCapacity)?count:_defaultCapacity); + for(int i=0; i<count; i++) + list.Add(value); + return list; + } + + // Reverses the elements in this list. + public virtual void Reverse() { + Reverse(0, Count); + } + + // Reverses the elements in a range of this list. Following a call to this + // method, an element in the range given by index and count + // which was previously located at index i will now be located at + // index index + (index + count - i - 1). + // + // This method uses the Array.Reverse method to reverse the + // elements. + // + public virtual void Reverse(int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + Array.Reverse(_items, index, count); + _version++; + } + + // Sets the elements starting at the given index to the elements of the + // given collection. + // + public virtual void SetRange(int index, ICollection c) { + if (c==null) throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection")); + Contract.EndContractBlock(); + int count = c.Count; + if (index < 0 || index > _size - count) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + + if (count > 0) { + c.CopyTo(_items, index); + _version++; + } + } + + public virtual ArrayList GetRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + return new Range(this,index, count); + } + + // Sorts the elements in this list. Uses the default comparer and + // Array.Sort. + public virtual void Sort() + { + Sort(0, Count, Comparer.Default); + } + + // Sorts the elements in this list. Uses Array.Sort with the + // provided comparer. + public virtual void Sort(IComparer comparer) + { + Sort(0, Count, comparer); + } + + // Sorts the elements in a section of this list. The sort compares the + // elements to each other using the given IComparer interface. If + // comparer is null, the elements are compared to each other using + // the IComparable interface, which in that case must be implemented by all + // elements of the list. + // + // This method uses the Array.Sort method to sort the elements. + // + public virtual void Sort(int index, int count, IComparer comparer) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_size - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + Array.Sort(_items, index, count, comparer); + _version++; + } + + // Returns a thread-safe wrapper around an IList. + // + [HostProtection(Synchronization=true)] + public static IList Synchronized(IList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<IList>() != null); + Contract.EndContractBlock(); + return new SyncIList(list); + } + + // Returns a thread-safe wrapper around a ArrayList. + // + [HostProtection(Synchronization=true)] + public static ArrayList Synchronized(ArrayList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.Ensures(Contract.Result<ArrayList>() != null); + Contract.EndContractBlock(); + return new SyncArrayList(list); + } + + // ToArray returns a new Object array containing the contents of the ArrayList. + // This requires copying the ArrayList, which is an O(n) operation. + public virtual Object[] ToArray() { + Contract.Ensures(Contract.Result<Object[]>() != null); + + Object[] array = new Object[_size]; + Array.Copy(_items, 0, array, 0, _size); + return array; + } + + // ToArray returns a new array of a particular type containing the contents + // of the ArrayList. This requires copying the ArrayList and potentially + // downcasting all elements. This copy may fail and is an O(n) operation. + // Internally, this implementation calls Array.Copy. + // + [SecuritySafeCritical] + public virtual Array ToArray(Type type) { + if (type==null) + throw new ArgumentNullException("type"); + Contract.Ensures(Contract.Result<Array>() != null); + Contract.EndContractBlock(); + Array array = Array.UnsafeCreateInstance(type, _size); + Array.Copy(_items, 0, array, 0, _size); + return array; + } + + // Sets the capacity of this list to the size of the list. This method can + // be used to minimize a list's memory overhead once it is known that no + // new elements will be added to the list. To completely clear a list and + // release all memory referenced by the list, execute the following + // statements: + // + // list.Clear(); + // list.TrimToSize(); + // + public virtual void TrimToSize() { + Capacity = _size; + } + + + // This class wraps an IList, exposing it as a ArrayList + // Note this requires reimplementing half of ArrayList... + [Serializable] + private class IListWrapper : ArrayList + { + private IList _list; + + internal IListWrapper(IList list) { + _list = list; + _version = 0; // list doesn't not contain a version number + } + + public override int Capacity { + get { return _list.Count; } + set { + if (value < Count) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity")); + Contract.EndContractBlock(); + } + } + + public override int Count { + get { return _list.Count; } + } + + public override bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return _list.IsFixedSize; } + } + + + public override bool IsSynchronized { + get { return _list.IsSynchronized; } + } + + public override Object this[int index] { + get { + return _list[index]; + } + set { + _list[index] = value; + _version++; + } + } + + public override Object SyncRoot { + get { return _list.SyncRoot; } + } + + public override int Add(Object obj) { + int i = _list.Add(obj); + _version++; + return i; + } + + public override void AddRange(ICollection c) { + InsertRange(Count, c); + } + + // Other overloads with automatically work + public override int BinarySearch(int index, int count, Object value, IComparer comparer) + { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (this.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + if (comparer == null) + comparer = Comparer.Default; + + int lo = index; + int hi = index + count - 1; + int mid; + while (lo <= hi) { + mid = (lo+hi)/2; + int r = comparer.Compare(value, _list[mid]); + if (r == 0) + return mid; + if (r < 0) + hi = mid-1; + else + lo = mid+1; + } + // return bitwise complement of the first element greater than value. + // Since hi is less than lo now, ~lo is the correct item. + return ~lo; + } + + public override void Clear() { + // If _list is an array, it will support Clear method. + // We shouldn't allow clear operation on a FixedSized ArrayList + if(_list.IsFixedSize) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + _list.Clear(); + _version++; + } + + public override Object Clone() { + // This does not do a shallow copy of _list into a ArrayList! + // This clones the IListWrapper, creating another wrapper class! + return new IListWrapper(_list); + } + + public override bool Contains(Object obj) { + return _list.Contains(obj); + } + + public override void CopyTo(Array array, int index) { + _list.CopyTo(array, index); + } + + public override void CopyTo(int index, Array array, int arrayIndex, int count) { + if (array==null) + throw new ArgumentNullException("array"); + if (index < 0 || arrayIndex < 0) + throw new ArgumentOutOfRangeException((index < 0) ? "index" : "arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if( count < 0) + throw new ArgumentOutOfRangeException( "count" , Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - arrayIndex < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + Contract.EndContractBlock(); + + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + for(int i=index; i<index+count; i++) + array.SetValue(_list[i], arrayIndex++); + } + + public override IEnumerator GetEnumerator() { + return _list.GetEnumerator(); + } + + public override IEnumerator GetEnumerator(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + return new IListWrapperEnumWrapper(this, index, count); + } + + public override int IndexOf(Object value) { + return _list.IndexOf(value); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex) { + return IndexOf(value, startIndex, _list.Count - startIndex); + } + + public override int IndexOf(Object value, int startIndex, int count) { + if (startIndex < 0 || startIndex > this.Count) throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if (count < 0 || startIndex > this.Count - count) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + Contract.EndContractBlock(); + + int endIndex = startIndex + count; + if (value == null) { + for(int i=startIndex; i<endIndex; i++) + if (_list[i] == null) + return i; + return -1; + } else { + for(int i=startIndex; i<endIndex; i++) + if (_list[i] != null && _list[i].Equals(value)) + return i; + return -1; + } + } + + public override void Insert(int index, Object obj) { + _list.Insert(index, obj); + _version++; + } + + public override void InsertRange(int index, ICollection c) { + if (c==null) + throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection")); + if (index < 0 || index > this.Count) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + + if( c.Count > 0) { + ArrayList al = _list as ArrayList; + if( al != null) { + // We need to special case ArrayList. + // When c is a range of _list, we need to handle this in a special way. + // See ArrayList.InsertRange for details. + al.InsertRange(index, c); + } + else { + IEnumerator en = c.GetEnumerator(); + while(en.MoveNext()) { + _list.Insert(index++, en.Current); + } + } + _version++; + } + } + + public override int LastIndexOf(Object value) { + return LastIndexOf(value,_list.Count - 1, _list.Count); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex) { + return LastIndexOf(value, startIndex, startIndex + 1); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex, int count) { + if (_list.Count == 0) + return -1; + + if (startIndex < 0 || startIndex >= _list.Count) throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if (count < 0 || count > startIndex + 1) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + + int endIndex = startIndex - count + 1; + if (value == null) { + for(int i=startIndex; i >= endIndex; i--) + if (_list[i] == null) + return i; + return -1; + } else { + for(int i=startIndex; i >= endIndex; i--) + if (_list[i] != null && _list[i].Equals(value)) + return i; + return -1; + } + } + + public override void Remove(Object value) { + int index = IndexOf(value); + if (index >=0) + RemoveAt(index); + } + + public override void RemoveAt(int index) { + _list.RemoveAt(index); + _version++; + } + + public override void RemoveRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + if( count > 0) // be consistent with ArrayList + _version++; + + while(count > 0) { + _list.RemoveAt(index); + count--; + } + } + + public override void Reverse(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + int i = index; + int j = index + count - 1; + while (i < j) + { + Object tmp = _list[i]; + _list[i++] = _list[j]; + _list[j--] = tmp; + } + _version++; + } + + public override void SetRange(int index, ICollection c) { + if (c==null) { + throw new ArgumentNullException("c", Environment.GetResourceString("ArgumentNull_Collection")); + } + Contract.EndContractBlock(); + + if (index < 0 || index > _list.Count - c.Count) { + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + } + + if( c.Count > 0) { + IEnumerator en = c.GetEnumerator(); + while(en.MoveNext()) { + _list[index++] = en.Current; + } + _version++; + } + } + + public override ArrayList GetRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + return new Range(this,index, count); + } + + public override void Sort(int index, int count, IComparer comparer) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_list.Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + Object [] array = new Object[count]; + CopyTo(index, array, 0, count); + Array.Sort(array, 0, count, comparer); + for(int i=0; i<count; i++) + _list[i+index] = array[i]; + + _version++; + } + + + public override Object[] ToArray() { + Object[] array = new Object[Count]; + _list.CopyTo(array, 0); + return array; + } + + [SecuritySafeCritical] + public override Array ToArray(Type type) + { + if (type==null) + throw new ArgumentNullException("type"); + Contract.EndContractBlock(); + Array array = Array.UnsafeCreateInstance(type, _list.Count); + _list.CopyTo(array, 0); + return array; + } + + public override void TrimToSize() + { + // Can't really do much here... + } + + // This is the enumerator for an IList that's been wrapped in another + // class that implements all of ArrayList's methods. + [Serializable] + private sealed class IListWrapperEnumWrapper : IEnumerator, ICloneable + { + private IEnumerator _en; + private int _remaining; + private int _initialStartIndex; // for reset + private int _initialCount; // for reset + private bool _firstCall; // firstCall to MoveNext + + private IListWrapperEnumWrapper() + { + } + + internal IListWrapperEnumWrapper(IListWrapper listWrapper, int startIndex, int count) + { + _en = listWrapper.GetEnumerator(); + _initialStartIndex = startIndex; + _initialCount = count; + while(startIndex-- > 0 && _en.MoveNext()); + _remaining = count; + _firstCall = true; + } + + public Object Clone() { + // We must clone the underlying enumerator, I think. + IListWrapperEnumWrapper clone = new IListWrapperEnumWrapper(); + clone._en = (IEnumerator) ((ICloneable)_en).Clone(); + clone._initialStartIndex = _initialStartIndex; + clone._initialCount = _initialCount; + clone._remaining = _remaining; + clone._firstCall = _firstCall; + return clone; + } + + public bool MoveNext() { + if (_firstCall) { + _firstCall = false; + return _remaining-- > 0 && _en.MoveNext(); + } + if (_remaining < 0) + return false; + bool r = _en.MoveNext(); + return r && _remaining-- > 0; + } + + public Object Current { + get { + if (_firstCall) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (_remaining < 0) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + return _en.Current; + } + } + + public void Reset() { + _en.Reset(); + int startIndex = _initialStartIndex; + while(startIndex-- > 0 && _en.MoveNext()); + _remaining = _initialCount; + _firstCall = true; + } + } + } + + + [Serializable] + private class SyncArrayList : ArrayList + { + private ArrayList _list; + private Object _root; + + internal SyncArrayList(ArrayList list) + : base( false ) + { + _list = list; + _root = list.SyncRoot; + } + + public override int Capacity { + get { + lock(_root) { + return _list.Capacity; + } + } + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + set { + lock(_root) { + _list.Capacity = value; + } + } + } + + public override int Count { + get { lock(_root) { return _list.Count; } } + } + + public override bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return _list.IsFixedSize; } + } + + + public override bool IsSynchronized { + get { return true; } + } + + public override Object this[int index] { + get { + lock(_root) { + return _list[index]; + } + } + set { + lock(_root) { + _list[index] = value; + } + } + } + + public override Object SyncRoot { + get { return _root; } + } + + public override int Add(Object value) { + lock(_root) { + return _list.Add(value); + } + } + + public override void AddRange(ICollection c) { + lock(_root) { + _list.AddRange(c); + } + } + + public override int BinarySearch(Object value) { + lock(_root) { + return _list.BinarySearch(value); + } + } + + public override int BinarySearch(Object value, IComparer comparer) { + lock(_root) { + return _list.BinarySearch(value, comparer); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int BinarySearch(int index, int count, Object value, IComparer comparer) { + lock(_root) { + return _list.BinarySearch(index, count, value, comparer); + } + } + + public override void Clear() { + lock(_root) { + _list.Clear(); + } + } + + public override Object Clone() { + lock(_root) { + return new SyncArrayList((ArrayList)_list.Clone()); + } + } + + public override bool Contains(Object item) { + lock(_root) { + return _list.Contains(item); + } + } + + public override void CopyTo(Array array) { + lock(_root) { + _list.CopyTo(array); + } + } + + public override void CopyTo(Array array, int index) { + lock(_root) { + _list.CopyTo(array, index); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void CopyTo(int index, Array array, int arrayIndex, int count) { + lock(_root) { + _list.CopyTo(index, array, arrayIndex, count); + } + } + + public override IEnumerator GetEnumerator() { + lock(_root) { + return _list.GetEnumerator(); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override IEnumerator GetEnumerator(int index, int count) { + lock(_root) { + return _list.GetEnumerator(index, count); + } + } + + public override int IndexOf(Object value) { + lock(_root) { + return _list.IndexOf(value); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex) { + lock(_root) { + return _list.IndexOf(value, startIndex); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex, int count) { + lock(_root) { + return _list.IndexOf(value, startIndex, count); + } + } + + public override void Insert(int index, Object value) { + lock(_root) { + _list.Insert(index, value); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void InsertRange(int index, ICollection c) { + lock(_root) { + _list.InsertRange(index, c); + } + } + + public override int LastIndexOf(Object value) { + lock(_root) { + return _list.LastIndexOf(value); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex) { + lock(_root) { + return _list.LastIndexOf(value, startIndex); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex, int count) { + lock(_root) { + return _list.LastIndexOf(value, startIndex, count); + } + } + + public override void Remove(Object value) { + lock(_root) { + _list.Remove(value); + } + } + + public override void RemoveAt(int index) { + lock(_root) { + _list.RemoveAt(index); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void RemoveRange(int index, int count) { + lock(_root) { + _list.RemoveRange(index, count); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Reverse(int index, int count) { + lock(_root) { + _list.Reverse(index, count); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void SetRange(int index, ICollection c) { + lock(_root) { + _list.SetRange(index, c); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override ArrayList GetRange(int index, int count) { + lock(_root) { + return _list.GetRange(index, count); + } + } + + public override void Sort() { + lock(_root) { + _list.Sort(); + } + } + + public override void Sort(IComparer comparer) { + lock(_root) { + _list.Sort(comparer); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Sort(int index, int count, IComparer comparer) { + lock(_root) { + _list.Sort(index, count, comparer); + } + } + + public override Object[] ToArray() { + lock(_root) { + return _list.ToArray(); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override Array ToArray(Type type) { + lock(_root) { + return _list.ToArray(type); + } + } + + public override void TrimToSize() { + lock(_root) { + _list.TrimToSize(); + } + } + } + + + [Serializable] + private class SyncIList : IList + { + private IList _list; + private Object _root; + + internal SyncIList(IList list) { + _list = list; + _root = list.SyncRoot; + } + + public virtual int Count { + get { lock(_root) { return _list.Count; } } + } + + public virtual bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public virtual bool IsFixedSize { + get { return _list.IsFixedSize; } + } + + + public virtual bool IsSynchronized { + get { return true; } + } + + public virtual Object this[int index] { + get { + lock(_root) { + return _list[index]; + } + } + set { + lock(_root) { + _list[index] = value; + } + } + } + + public virtual Object SyncRoot { + get { return _root; } + } + + public virtual int Add(Object value) { + lock(_root) { + return _list.Add(value); + } + } + + + public virtual void Clear() { + lock(_root) { + _list.Clear(); + } + } + + public virtual bool Contains(Object item) { + lock(_root) { + return _list.Contains(item); + } + } + + public virtual void CopyTo(Array array, int index) { + lock(_root) { + _list.CopyTo(array, index); + } + } + + public virtual IEnumerator GetEnumerator() { + lock(_root) { + return _list.GetEnumerator(); + } + } + + public virtual int IndexOf(Object value) { + lock(_root) { + return _list.IndexOf(value); + } + } + + public virtual void Insert(int index, Object value) { + lock(_root) { + _list.Insert(index, value); + } + } + + public virtual void Remove(Object value) { + lock(_root) { + _list.Remove(value); + } + } + + public virtual void RemoveAt(int index) { + lock(_root) { + _list.RemoveAt(index); + } + } + } + + [Serializable] + private class FixedSizeList : IList + { + private IList _list; + + internal FixedSizeList(IList l) { + _list = l; + } + + public virtual int Count { + get { return _list.Count; } + } + + public virtual bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public virtual bool IsFixedSize { + get { return true; } + } + + public virtual bool IsSynchronized { + get { return _list.IsSynchronized; } + } + + public virtual Object this[int index] { + get { + return _list[index]; + } + set { + _list[index] = value; + } + } + + public virtual Object SyncRoot { + get { return _list.SyncRoot; } + } + + public virtual int Add(Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public virtual void Clear() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public virtual bool Contains(Object obj) { + return _list.Contains(obj); + } + + public virtual void CopyTo(Array array, int index) { + _list.CopyTo(array, index); + } + + public virtual IEnumerator GetEnumerator() { + return _list.GetEnumerator(); + } + + public virtual int IndexOf(Object value) { + return _list.IndexOf(value); + } + + public virtual void Insert(int index, Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public virtual void Remove(Object value) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public virtual void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + } + + [Serializable] + private class FixedSizeArrayList : ArrayList + { + private ArrayList _list; + + internal FixedSizeArrayList(ArrayList l) { + _list = l; + _version = _list._version; + } + + public override int Count { + get { return _list.Count; } + } + + public override bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return true; } + } + + public override bool IsSynchronized { + get { return _list.IsSynchronized; } + } + + public override Object this[int index] { + get { + return _list[index]; + } + set { + _list[index] = value; + _version = _list._version; + } + } + + public override Object SyncRoot { + get { return _list.SyncRoot; } + } + + public override int Add(Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public override void AddRange(ICollection c) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int BinarySearch(int index, int count, Object value, IComparer comparer) { + return _list.BinarySearch(index, count, value, comparer); + } + + public override int Capacity { + get { return _list.Capacity; } + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + set { throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); } + } + + public override void Clear() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public override Object Clone() { + FixedSizeArrayList arrayList = new FixedSizeArrayList(_list); + arrayList._list = (ArrayList)_list.Clone(); + return arrayList; + } + + public override bool Contains(Object obj) { + return _list.Contains(obj); + } + + public override void CopyTo(Array array, int index) { + _list.CopyTo(array, index); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void CopyTo(int index, Array array, int arrayIndex, int count) { + _list.CopyTo(index, array, arrayIndex, count); + } + + public override IEnumerator GetEnumerator() { + return _list.GetEnumerator(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override IEnumerator GetEnumerator(int index, int count) { + return _list.GetEnumerator(index, count); + } + + public override int IndexOf(Object value) { + return _list.IndexOf(value); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex) { + return _list.IndexOf(value, startIndex); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex, int count) { + return _list.IndexOf(value, startIndex, count); + } + + public override void Insert(int index, Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void InsertRange(int index, ICollection c) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public override int LastIndexOf(Object value) { + return _list.LastIndexOf(value); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex) { + return _list.LastIndexOf(value, startIndex); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex, int count) { + return _list.LastIndexOf(value, startIndex, count); + } + + public override void Remove(Object value) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + public override void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void RemoveRange(int index, int count) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void SetRange(int index, ICollection c) { + _list.SetRange(index, c); + _version = _list._version; + } + + public override ArrayList GetRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + return new Range(this,index, count); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Reverse(int index, int count) { + _list.Reverse(index, count); + _version = _list._version; + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Sort(int index, int count, IComparer comparer) { + _list.Sort(index, count, comparer); + _version = _list._version; + } + + public override Object[] ToArray() { + return _list.ToArray(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override Array ToArray(Type type) { + return _list.ToArray(type); + } + + public override void TrimToSize() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FixedSizeCollection")); + } + } + + [Serializable] + private class ReadOnlyList : IList + { + private IList _list; + + internal ReadOnlyList(IList l) { + _list = l; + } + + public virtual int Count { + get { return _list.Count; } + } + + public virtual bool IsReadOnly { + get { return true; } + } + + public virtual bool IsFixedSize { + get { return true; } + } + + public virtual bool IsSynchronized { + get { return _list.IsSynchronized; } + } + + public virtual Object this[int index] { + get { + return _list[index]; + } + set { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + } + + public virtual Object SyncRoot { + get { return _list.SyncRoot; } + } + + public virtual int Add(Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public virtual void Clear() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public virtual bool Contains(Object obj) { + return _list.Contains(obj); + } + + public virtual void CopyTo(Array array, int index) { + _list.CopyTo(array, index); + } + + public virtual IEnumerator GetEnumerator() { + return _list.GetEnumerator(); + } + + public virtual int IndexOf(Object value) { + return _list.IndexOf(value); + } + + public virtual void Insert(int index, Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public virtual void Remove(Object value) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public virtual void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + } + + [Serializable] + private class ReadOnlyArrayList : ArrayList + { + private ArrayList _list; + + internal ReadOnlyArrayList(ArrayList l) { + _list = l; + } + + public override int Count { + get { return _list.Count; } + } + + public override bool IsReadOnly { + get { return true; } + } + + public override bool IsFixedSize { + get { return true; } + } + + public override bool IsSynchronized { + get { return _list.IsSynchronized; } + } + + public override Object this[int index] { + get { + return _list[index]; + } + set { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + } + + public override Object SyncRoot { + get { return _list.SyncRoot; } + } + + public override int Add(Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override void AddRange(ICollection c) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int BinarySearch(int index, int count, Object value, IComparer comparer) { + return _list.BinarySearch(index, count, value, comparer); + } + + + public override int Capacity { + get { return _list.Capacity; } + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + set { throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); } + } + + public override void Clear() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override Object Clone() { + ReadOnlyArrayList arrayList = new ReadOnlyArrayList(_list); + arrayList._list = (ArrayList)_list.Clone(); + return arrayList; + } + + public override bool Contains(Object obj) { + return _list.Contains(obj); + } + + public override void CopyTo(Array array, int index) { + _list.CopyTo(array, index); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void CopyTo(int index, Array array, int arrayIndex, int count) { + _list.CopyTo(index, array, arrayIndex, count); + } + + public override IEnumerator GetEnumerator() { + return _list.GetEnumerator(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override IEnumerator GetEnumerator(int index, int count) { + return _list.GetEnumerator(index, count); + } + + public override int IndexOf(Object value) { + return _list.IndexOf(value); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex) { + return _list.IndexOf(value, startIndex); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOf(Object value, int startIndex, int count) { + return _list.IndexOf(value, startIndex, count); + } + + public override void Insert(int index, Object obj) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void InsertRange(int index, ICollection c) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override int LastIndexOf(Object value) { + return _list.LastIndexOf(value); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex) { + return _list.LastIndexOf(value, startIndex); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex, int count) { + return _list.LastIndexOf(value, startIndex, count); + } + + public override void Remove(Object value) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void RemoveRange(int index, int count) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void SetRange(int index, ICollection c) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override ArrayList GetRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (Count - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + return new Range(this,index, count); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Reverse(int index, int count) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void Sort(int index, int count, IComparer comparer) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + + public override Object[] ToArray() { + return _list.ToArray(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override Array ToArray(Type type) { + return _list.ToArray(type); + } + + public override void TrimToSize() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ReadOnlyCollection")); + } + } + + + // Implements an enumerator for a ArrayList. The enumerator uses the + // internal version number of the list to ensure that no modifications are + // made to the list while an enumeration is in progress. + [Serializable] + private sealed class ArrayListEnumerator : IEnumerator, ICloneable + { + private ArrayList list; + private int index; + private int endIndex; // Where to stop. + private int version; + private Object currentElement; + private int startIndex; // Save this for Reset. + + internal ArrayListEnumerator(ArrayList list, int index, int count) { + this.list = list; + startIndex = index; + this.index = index - 1; + endIndex = this.index + count; // last valid index + version = list._version; + currentElement = null; + } + + public Object Clone() { + return MemberwiseClone(); + } + + public bool MoveNext() { + if (version != list._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + if (index < endIndex) { + currentElement = list[++index]; + return true; + } + else { + index = endIndex + 1; + } + + return false; + } + + public Object Current { + get { + if (index < startIndex) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + else if (index > endIndex) { + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + } + return currentElement; + } + } + + public void Reset() { + if (version != list._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + index = startIndex - 1; + } + } + + // Implementation of a generic list subrange. An instance of this class + // is returned by the default implementation of List.GetRange. + [Serializable] + private class Range: ArrayList + { + private ArrayList _baseList; + private int _baseIndex; + [ContractPublicPropertyName("Count")] + private int _baseSize; + private int _baseVersion; + + internal Range(ArrayList list, int index, int count) : base(false) { + _baseList = list; + _baseIndex = index; + _baseSize = count; + _baseVersion = list._version; + // we also need to update _version field to make Range of Range work + _version = list._version; + } + + private void InternalUpdateRange() + { + if (_baseVersion != _baseList._version) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_UnderlyingArrayListChanged")); + } + + private void InternalUpdateVersion() { + _baseVersion++; + _version++; + } + + public override int Add(Object value) { + InternalUpdateRange(); + _baseList.Insert(_baseIndex + _baseSize, value); + InternalUpdateVersion(); + return _baseSize++; + } + + public override void AddRange(ICollection c) { + if( c == null ) { + throw new ArgumentNullException("c"); + } + Contract.EndContractBlock(); + + InternalUpdateRange(); + int count = c.Count; + if( count > 0) { + _baseList.InsertRange(_baseIndex + _baseSize, c); + InternalUpdateVersion(); + _baseSize += count; + } + } + + // Other overloads with automatically work + public override int BinarySearch(int index, int count, Object value, IComparer comparer) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + InternalUpdateRange(); + + int i = _baseList.BinarySearch(_baseIndex + index, count, value, comparer); + if (i >= 0) return i - _baseIndex; + return i + _baseIndex; + } + + public override int Capacity { + get { + return _baseList.Capacity; + } + + set { + if (value < Count) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity")); + Contract.EndContractBlock(); + } + } + + + public override void Clear() { + InternalUpdateRange(); + if (_baseSize != 0) + { + _baseList.RemoveRange(_baseIndex, _baseSize); + InternalUpdateVersion(); + _baseSize = 0; + } + } + + public override Object Clone() { + InternalUpdateRange(); + Range arrayList = new Range(_baseList,_baseIndex,_baseSize); + arrayList._baseList = (ArrayList)_baseList.Clone(); + return arrayList; + } + + public override bool Contains(Object item) { + InternalUpdateRange(); + if (item==null) { + for(int i=0; i<_baseSize; i++) + if (_baseList[_baseIndex + i]==null) + return true; + return false; + } + else { + for(int i=0; i<_baseSize; i++) + if (_baseList[_baseIndex + i] != null && _baseList[_baseIndex + i].Equals(item)) + return true; + return false; + } + } + + public override void CopyTo(Array array, int index) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - index < _baseSize) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.CopyTo(_baseIndex, array, index, _baseSize); + } + + public override void CopyTo(int index, Array array, int arrayIndex, int count) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - arrayIndex < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.CopyTo(_baseIndex + index, array, arrayIndex, count); + } + + public override int Count { + get { + InternalUpdateRange(); + return _baseSize; + } + } + + public override bool IsReadOnly { + get { return _baseList.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return _baseList.IsFixedSize; } + } + + public override bool IsSynchronized { + get { return _baseList.IsSynchronized; } + } + + public override IEnumerator GetEnumerator() { + return GetEnumerator(0,_baseSize); + } + + public override IEnumerator GetEnumerator(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + return _baseList.GetEnumerator(_baseIndex + index, count); + } + + public override ArrayList GetRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + return new Range(this, index, count); + } + + public override Object SyncRoot { + get { + return _baseList.SyncRoot; + } + } + + + public override int IndexOf(Object value) { + InternalUpdateRange(); + int i = _baseList.IndexOf(value, _baseIndex, _baseSize); + if (i >= 0) return i - _baseIndex; + return -1; + } + + public override int IndexOf(Object value, int startIndex) { + if (startIndex < 0) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (startIndex > _baseSize) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + int i = _baseList.IndexOf(value, _baseIndex + startIndex, _baseSize - startIndex); + if (i >= 0) return i - _baseIndex; + return -1; + } + + public override int IndexOf(Object value, int startIndex, int count) { + if (startIndex < 0 || startIndex > _baseSize) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + + if (count < 0 || (startIndex > _baseSize - count)) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + int i = _baseList.IndexOf(value, _baseIndex + startIndex, count); + if (i >= 0) return i - _baseIndex; + return -1; + } + + public override void Insert(int index, Object value) { + if (index < 0 || index > _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.Insert(_baseIndex + index, value); + InternalUpdateVersion(); + _baseSize++; + } + + public override void InsertRange(int index, ICollection c) { + if (index < 0 || index > _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if( c == null) { + throw new ArgumentNullException("c"); + } + Contract.EndContractBlock(); + + InternalUpdateRange(); + int count = c.Count; + if( count > 0) { + _baseList.InsertRange(_baseIndex + index, c); + _baseSize += count; + InternalUpdateVersion(); + } + } + + public override int LastIndexOf(Object value) { + InternalUpdateRange(); + int i = _baseList.LastIndexOf(value, _baseIndex + _baseSize - 1, _baseSize); + if (i >= 0) return i - _baseIndex; + return -1; + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex) { + return LastIndexOf(value, startIndex, startIndex + 1); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int LastIndexOf(Object value, int startIndex, int count) { + InternalUpdateRange(); + if (_baseSize == 0) + return -1; + + if (startIndex >= _baseSize) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if (startIndex < 0) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + + int i = _baseList.LastIndexOf(value, _baseIndex + startIndex, count); + if (i >= 0) return i - _baseIndex; + return -1; + } + + // Don't need to override Remove + + public override void RemoveAt(int index) { + if (index < 0 || index >= _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.RemoveAt(_baseIndex + index); + InternalUpdateVersion(); + _baseSize--; + } + + public override void RemoveRange(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + // No need to call _bastList.RemoveRange if count is 0. + // In addition, _baseList won't change the vresion number if count is 0. + if( count > 0) { + _baseList.RemoveRange(_baseIndex + index, count); + InternalUpdateVersion(); + _baseSize -= count; + } + } + + public override void Reverse(int index, int count) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.Reverse(_baseIndex + index, count); + InternalUpdateVersion(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void SetRange(int index, ICollection c) { + InternalUpdateRange(); + if (index < 0 || index >= _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + _baseList.SetRange(_baseIndex + index, c); + if( c.Count > 0) { + InternalUpdateVersion(); + } + } + + public override void Sort(int index, int count, IComparer comparer) { + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index<0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (_baseSize - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + InternalUpdateRange(); + _baseList.Sort(_baseIndex + index, count, comparer); + InternalUpdateVersion(); + } + + public override Object this[int index] { + get { + InternalUpdateRange(); + if (index < 0 || index >= _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + return _baseList[_baseIndex + index]; + } + set { + InternalUpdateRange(); + if (index < 0 || index >= _baseSize) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + _baseList[_baseIndex + index] = value; + InternalUpdateVersion(); + } + } + + public override Object[] ToArray() { + InternalUpdateRange(); + Object[] array = new Object[_baseSize]; + Array.Copy(_baseList._items, _baseIndex, array, 0, _baseSize); + return array; + } + + [SecuritySafeCritical] + public override Array ToArray(Type type) { + if (type==null) + throw new ArgumentNullException("type"); + Contract.EndContractBlock(); + + InternalUpdateRange(); + Array array = Array.UnsafeCreateInstance(type, _baseSize); + _baseList.CopyTo(_baseIndex, array, 0, _baseSize); + return array; + } + + public override void TrimToSize() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_RangeCollection")); + } + } + + [Serializable] + private sealed class ArrayListEnumeratorSimple : IEnumerator, ICloneable { + private ArrayList list; + private int index; + private int version; + private Object currentElement; + [NonSerialized] + private bool isArrayList; + // this object is used to indicate enumeration has not started or has terminated + static Object dummyObject = new Object(); + + internal ArrayListEnumeratorSimple(ArrayList list) { + this.list = list; + this.index = -1; + version = list._version; + isArrayList = (list.GetType() == typeof(ArrayList)); + currentElement = dummyObject; + } + + public Object Clone() { + return MemberwiseClone(); + } + + public bool MoveNext() { + if (version != list._version) { + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + } + + if( isArrayList) { // avoid calling virtual methods if we are operating on ArrayList to improve performance + if (index < list._size - 1) { + currentElement = list._items[++index]; + return true; + } + else { + currentElement = dummyObject; + index =list._size; + return false; + } + } + else { + if (index < list.Count - 1) { + currentElement = list[++index]; + return true; + } + else { + index = list.Count; + currentElement = dummyObject; + return false; + } + } + } + + public Object Current { + get { + object temp = currentElement; + if(dummyObject == temp) { // check if enumeration has not started or has terminated + if (index == -1) { + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + } + else { + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + } + } + + return temp; + } + } + + public void Reset() { + if (version != list._version) { + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + } + + currentElement = dummyObject; + index = -1; + } + } + + internal class ArrayListDebugView { + private ArrayList arrayList; + + public ArrayListDebugView( ArrayList arrayList) { + if( arrayList == null) + throw new ArgumentNullException("arrayList"); + + this.arrayList = arrayList; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public Object[] Items { + get { + return arrayList.ToArray(); + } + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/BitArray.cs b/src/mscorlib/src/System/Collections/BitArray.cs new file mode 100644 index 0000000000..2f565f83af --- /dev/null +++ b/src/mscorlib/src/System/Collections/BitArray.cs @@ -0,0 +1,524 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** +** +** +** +** Purpose: The BitArray class manages a compact array of bit values. +** +** +=============================================================================*/ +namespace System.Collections { + + using System; + using System.Security.Permissions; + using System.Diagnostics.Contracts; + // A vector of bits. Use this to store bits efficiently, without having to do bit + // shifting yourself. +[System.Runtime.InteropServices.ComVisible(true)] + [Serializable()] public sealed class BitArray : ICollection, ICloneable { + private BitArray() { + } + + /*========================================================================= + ** Allocates space to hold length bit values. All of the values in the bit + ** array are set to false. + ** + ** Exceptions: ArgumentException if length < 0. + =========================================================================*/ + public BitArray(int length) + : this(length, false) { + } + + /*========================================================================= + ** Allocates space to hold length bit values. All of the values in the bit + ** array are set to defaultValue. + ** + ** Exceptions: ArgumentOutOfRangeException if length < 0. + =========================================================================*/ + public BitArray(int length, bool defaultValue) { + if (length < 0) { + throw new ArgumentOutOfRangeException(nameof(length), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + + m_array = new int[GetArrayLength(length, BitsPerInt32)]; + m_length = length; + + int fillValue = defaultValue ? unchecked(((int)0xffffffff)) : 0; + for (int i = 0; i < m_array.Length; i++) { + m_array[i] = fillValue; + } + + _version = 0; + } + + /*========================================================================= + ** Allocates space to hold the bit values in bytes. bytes[0] represents + ** bits 0 - 7, bytes[1] represents bits 8 - 15, etc. The LSB of each byte + ** represents the lowest index value; bytes[0] & 1 represents bit 0, + ** bytes[0] & 2 represents bit 1, bytes[0] & 4 represents bit 2, etc. + ** + ** Exceptions: ArgumentException if bytes == null. + =========================================================================*/ + public BitArray(byte[] bytes) { + if (bytes == null) { + throw new ArgumentNullException(nameof(bytes)); + } + Contract.EndContractBlock(); + // this value is chosen to prevent overflow when computing m_length. + // m_length is of type int32 and is exposed as a property, so + // type of m_length can't be changed to accommodate. + if (bytes.Length > Int32.MaxValue / BitsPerByte) { + throw new ArgumentException(Environment.GetResourceString("Argument_ArrayTooLarge", BitsPerByte), nameof(bytes)); + } + + m_array = new int[GetArrayLength(bytes.Length, BytesPerInt32)]; + m_length = bytes.Length * BitsPerByte; + + int i = 0; + int j = 0; + while (bytes.Length - j >= 4) { + m_array[i++] = (bytes[j] & 0xff) | + ((bytes[j + 1] & 0xff) << 8) | + ((bytes[j + 2] & 0xff) << 16) | + ((bytes[j + 3] & 0xff) << 24); + j += 4; + } + + Contract.Assert(bytes.Length - j >= 0, "BitArray byteLength problem"); + Contract.Assert(bytes.Length - j < 4, "BitArray byteLength problem #2"); + + switch (bytes.Length - j) { + case 3: + m_array[i] = ((bytes[j + 2] & 0xff) << 16); + goto case 2; + // fall through + case 2: + m_array[i] |= ((bytes[j + 1] & 0xff) << 8); + goto case 1; + // fall through + case 1: + m_array[i] |= (bytes[j] & 0xff); + break; + } + + _version = 0; + } + + public BitArray(bool[] values) { + if (values == null) { + throw new ArgumentNullException(nameof(values)); + } + Contract.EndContractBlock(); + + m_array = new int[GetArrayLength(values.Length, BitsPerInt32)]; + m_length = values.Length; + + for (int i = 0;i<values.Length;i++) { + if (values[i]) + m_array[i/32] |= (1 << (i%32)); + } + + _version = 0; + + } + + /*========================================================================= + ** Allocates space to hold the bit values in values. values[0] represents + ** bits 0 - 31, values[1] represents bits 32 - 63, etc. The LSB of each + ** integer represents the lowest index value; values[0] & 1 represents bit + ** 0, values[0] & 2 represents bit 1, values[0] & 4 represents bit 2, etc. + ** + ** Exceptions: ArgumentException if values == null. + =========================================================================*/ + public BitArray(int[] values) { + if (values == null) { + throw new ArgumentNullException(nameof(values)); + } + Contract.EndContractBlock(); + // this value is chosen to prevent overflow when computing m_length + if (values.Length > Int32.MaxValue / BitsPerInt32) { + throw new ArgumentException(Environment.GetResourceString("Argument_ArrayTooLarge", BitsPerInt32), nameof(values)); + } + + m_array = new int[values.Length]; + m_length = values.Length * BitsPerInt32; + + Array.Copy(values, m_array, values.Length); + + _version = 0; + } + + /*========================================================================= + ** Allocates a new BitArray with the same length and bit values as bits. + ** + ** Exceptions: ArgumentException if bits == null. + =========================================================================*/ + public BitArray(BitArray bits) { + if (bits == null) { + throw new ArgumentNullException(nameof(bits)); + } + Contract.EndContractBlock(); + + int arrayLength = GetArrayLength(bits.m_length, BitsPerInt32); + m_array = new int[arrayLength]; + m_length = bits.m_length; + + Array.Copy(bits.m_array, m_array, arrayLength); + + _version = bits._version; + } + + public bool this[int index] { + get { + return Get(index); + } + set { + Set(index,value); + } + } + + /*========================================================================= + ** Returns the bit value at position index. + ** + ** Exceptions: ArgumentOutOfRangeException if index < 0 or + ** index >= GetLength(). + =========================================================================*/ + public bool Get(int index) { + if (index < 0 || index >= Length) { + throw new ArgumentOutOfRangeException(nameof(index), Environment.GetResourceString("ArgumentOutOfRange_Index")); + } + Contract.EndContractBlock(); + + return (m_array[index / 32] & (1 << (index % 32))) != 0; + } + + /*========================================================================= + ** Sets the bit value at position index to value. + ** + ** Exceptions: ArgumentOutOfRangeException if index < 0 or + ** index >= GetLength(). + =========================================================================*/ + public void Set(int index, bool value) { + if (index < 0 || index >= Length) { + throw new ArgumentOutOfRangeException(nameof(index), Environment.GetResourceString("ArgumentOutOfRange_Index")); + } + Contract.EndContractBlock(); + + if (value) { + m_array[index / 32] |= (1 << (index % 32)); + } else { + m_array[index / 32] &= ~(1 << (index % 32)); + } + + _version++; + } + + /*========================================================================= + ** Sets all the bit values to value. + =========================================================================*/ + public void SetAll(bool value) { + int fillValue = value ? unchecked(((int)0xffffffff)) : 0; + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) { + m_array[i] = fillValue; + } + + _version++; + } + + /*========================================================================= + ** Returns a reference to the current instance ANDed with value. + ** + ** Exceptions: ArgumentException if value == null or + ** value.Length != this.Length. + =========================================================================*/ + public BitArray And(BitArray value) { + if (value==null) + throw new ArgumentNullException(nameof(value)); + if (Length != value.Length) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer")); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) { + m_array[i] &= value.m_array[i]; + } + + _version++; + return this; + } + + /*========================================================================= + ** Returns a reference to the current instance ORed with value. + ** + ** Exceptions: ArgumentException if value == null or + ** value.Length != this.Length. + =========================================================================*/ + public BitArray Or(BitArray value) { + if (value==null) + throw new ArgumentNullException(nameof(value)); + if (Length != value.Length) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer")); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) { + m_array[i] |= value.m_array[i]; + } + + _version++; + return this; + } + + /*========================================================================= + ** Returns a reference to the current instance XORed with value. + ** + ** Exceptions: ArgumentException if value == null or + ** value.Length != this.Length. + =========================================================================*/ + public BitArray Xor(BitArray value) { + if (value==null) + throw new ArgumentNullException(nameof(value)); + if (Length != value.Length) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer")); + Contract.EndContractBlock(); + + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) { + m_array[i] ^= value.m_array[i]; + } + + _version++; + return this; + } + + /*========================================================================= + ** Inverts all the bit values. On/true bit values are converted to + ** off/false. Off/false bit values are turned on/true. The current instance + ** is updated and returned. + =========================================================================*/ + public BitArray Not() { + int ints = GetArrayLength(m_length, BitsPerInt32); + for (int i = 0; i < ints; i++) { + m_array[i] = ~m_array[i]; + } + + _version++; + return this; + } + + public int Length { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + return m_length; + } + set { + if (value < 0) { + throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + + int newints = GetArrayLength(value, BitsPerInt32); + if (newints > m_array.Length || newints + _ShrinkThreshold < m_array.Length) { + // grow or shrink (if wasting more than _ShrinkThreshold ints) + int[] newarray = new int[newints]; + Array.Copy(m_array, newarray, newints > m_array.Length ? m_array.Length : newints); + m_array = newarray; + } + + if (value > m_length) { + // clear high bit values in the last int + int last = GetArrayLength(m_length, BitsPerInt32) - 1; + int bits = m_length % 32; + if (bits > 0) { + m_array[last] &= (1 << bits) - 1; + } + + // clear remaining int values + Array.Clear(m_array, last + 1, newints - last - 1); + } + + m_length = value; + _version++; + } + } + + // ICollection implementation + public void CopyTo(Array array, int index) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"), nameof(array)); + + Contract.EndContractBlock(); + + if (array is int[]) + { + Array.Copy(m_array, 0, array, index, GetArrayLength(m_length, BitsPerInt32)); + } + else if (array is byte[]) + { + int arrayLength = GetArrayLength(m_length, BitsPerByte); + if ((array.Length - index) < arrayLength) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + byte [] b = (byte[])array; + for (int i = 0; i < arrayLength; i++) + b[index + i] = (byte)((m_array[i/4] >> ((i%4)*8)) & 0x000000FF); // Shift to bring the required byte to LSB, then mask + } + else if (array is bool[]) + { + if (array.Length - index < m_length) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + bool [] b = (bool[])array; + for (int i = 0;i<m_length;i++) + b[index + i] = ((m_array[i/32] >> (i%32)) & 0x00000001) != 0; + } + else + throw new ArgumentException(Environment.GetResourceString("Arg_BitArrayTypeUnsupported"), nameof(array)); + } + + public int Count + { + get + { + Contract.Ensures(Contract.Result<int>() >= 0); + + return m_length; + } + } + + public Object Clone() + { + Contract.Ensures(Contract.Result<Object>() != null); + Contract.Ensures(((BitArray)Contract.Result<Object>()).Length == this.Length); + + return new BitArray(this); + } + + public Object SyncRoot + { + get + { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public bool IsSynchronized + { + get + { + return false; + } + } + + public IEnumerator GetEnumerator() + { + return new BitArrayEnumeratorSimple(this); + } + + // XPerY=n means that n Xs can be stored in 1 Y. + private const int BitsPerInt32 = 32; + private const int BytesPerInt32 = 4; + private const int BitsPerByte = 8; + + /// <summary> + /// Used for conversion between different representations of bit array. + /// Returns (n+(div-1))/div, rearranged to avoid arithmetic overflow. + /// For example, in the bit to int case, the straightforward calc would + /// be (n+31)/32, but that would cause overflow. So instead it's + /// rearranged to ((n-1)/32) + 1, with special casing for 0. + /// + /// Usage: + /// GetArrayLength(77, BitsPerInt32): returns how many ints must be + /// allocated to store 77 bits. + /// </summary> + /// <param name="n"></param> + /// <param name="div">use a conversion constant, e.g. BytesPerInt32 to get + /// how many ints are required to store n bytes</param> + /// <returns></returns> + private static int GetArrayLength(int n, int div) { + Contract.Assert(div > 0, "GetArrayLength: div arg must be greater than 0"); + return n > 0 ? (((n - 1) / div) + 1) : 0; + } + + [Serializable] + private class BitArrayEnumeratorSimple : IEnumerator, ICloneable + { + private BitArray bitarray; + private int index; + private int version; + private bool currentElement; + + internal BitArrayEnumeratorSimple(BitArray bitarray) { + this.bitarray = bitarray; + this.index = -1; + version = bitarray._version; + } + + public Object Clone() { + return MemberwiseClone(); + } + + public virtual bool MoveNext() { + if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + if (index < (bitarray.Count-1)) { + index++; + currentElement = bitarray.Get(index); + return true; + } + else + index = bitarray.Count; + + return false; + } + + public virtual Object Current { + get { + if (index == -1) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (index >= bitarray.Count) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + return currentElement; + } + } + + public void Reset() { + if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + index = -1; + } + } + + private int[] m_array; + private int m_length; + private int _version; + [NonSerialized] + private Object _syncRoot; + + private const int _ShrinkThreshold = 256; + } + +} diff --git a/src/mscorlib/src/System/Collections/CollectionBase.cs b/src/mscorlib/src/System/Collections/CollectionBase.cs new file mode 100644 index 0000000000..1bb08af27a --- /dev/null +++ b/src/mscorlib/src/System/Collections/CollectionBase.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +// + +namespace System.Collections { + using System; + using System.Diagnostics.Contracts; + + // Useful base class for typed read/write collections where items derive from object + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public abstract class CollectionBase : IList { + ArrayList list; + + protected CollectionBase() { + list = new ArrayList(); + } + + protected CollectionBase(int capacity) { + list = new ArrayList(capacity); + } + + + protected ArrayList InnerList { + get { + if (list == null) + list = new ArrayList(); + return list; + } + } + + protected IList List { + get { return (IList)this; } + } + + [System.Runtime.InteropServices.ComVisible(false)] + public int Capacity { + get { + return InnerList.Capacity; + } + set { + InnerList.Capacity = value; + } + } + + + public int Count { + get { + return list == null ? 0 : list.Count; + } + } + + public void Clear() { + OnClear(); + InnerList.Clear(); + OnClearComplete(); + } + + public void RemoveAt(int index) { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + Object temp = InnerList[index]; + OnValidate(temp); + OnRemove(index, temp); + InnerList.RemoveAt(index); + try { + OnRemoveComplete(index, temp); + } + catch { + InnerList.Insert(index, temp); + throw; + } + + } + + bool IList.IsReadOnly { + get { return InnerList.IsReadOnly; } + } + + bool IList.IsFixedSize { + get { return InnerList.IsFixedSize; } + } + + bool ICollection.IsSynchronized { + get { return InnerList.IsSynchronized; } + } + + Object ICollection.SyncRoot { + get { return InnerList.SyncRoot; } + } + + void ICollection.CopyTo(Array array, int index) { + InnerList.CopyTo(array, index); + } + + Object IList.this[int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + return InnerList[index]; + } + set { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + OnValidate(value); + Object temp = InnerList[index]; + OnSet(index, temp, value); + InnerList[index] = value; + try { + OnSetComplete(index, temp, value); + } + catch { + InnerList[index] = temp; + throw; + } + } + } + + bool IList.Contains(Object value) { + return InnerList.Contains(value); + } + + int IList.Add(Object value) { + OnValidate(value); + OnInsert(InnerList.Count, value); + int index = InnerList.Add(value); + try { + OnInsertComplete(index, value); + } + catch { + InnerList.RemoveAt(index); + throw; + } + return index; + } + + + void IList.Remove(Object value) { + OnValidate(value); + int index = InnerList.IndexOf(value); + if (index < 0) throw new ArgumentException(Environment.GetResourceString("Arg_RemoveArgNotFound")); + OnRemove(index, value); + InnerList.RemoveAt(index); + try{ + OnRemoveComplete(index, value); + } + catch { + InnerList.Insert(index, value); + throw; + } + } + + int IList.IndexOf(Object value) { + return InnerList.IndexOf(value); + } + + void IList.Insert(int index, Object value) { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + OnValidate(value); + OnInsert(index, value); + InnerList.Insert(index, value); + try { + OnInsertComplete(index, value); + } + catch { + InnerList.RemoveAt(index); + throw; + } + } + + public IEnumerator GetEnumerator() { + return InnerList.GetEnumerator(); + } + + protected virtual void OnSet(int index, Object oldValue, Object newValue) { + } + + protected virtual void OnInsert(int index, Object value) { + } + + protected virtual void OnClear() { + } + + protected virtual void OnRemove(int index, Object value) { + } + + protected virtual void OnValidate(Object value) { + if (value == null) throw new ArgumentNullException("value"); + Contract.EndContractBlock(); + } + + protected virtual void OnSetComplete(int index, Object oldValue, Object newValue) { + } + + protected virtual void OnInsertComplete(int index, Object value) { + } + + protected virtual void OnClearComplete() { + } + + protected virtual void OnRemoveComplete(int index, Object value) { + } + + } + +} diff --git a/src/mscorlib/src/System/Collections/Comparer.cs b/src/mscorlib/src/System/Collections/Comparer.cs new file mode 100644 index 0000000000..11e26252a8 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Comparer.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Default IComparer implementation. +** +** +===========================================================*/ +namespace System.Collections { + + using System; + using System.Globalization; + using System.Runtime.Serialization; + using System.Security.Permissions; + using System.Diagnostics.Contracts; + + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public sealed class Comparer : IComparer , ISerializable + { + private CompareInfo m_compareInfo; + public static readonly Comparer Default = new Comparer(CultureInfo.CurrentCulture); + public static readonly Comparer DefaultInvariant = new Comparer(CultureInfo.InvariantCulture); + + private const String CompareInfoName = "CompareInfo"; + + private Comparer() { + m_compareInfo = null; + } + + public Comparer(CultureInfo culture) { + if (culture==null) { + throw new ArgumentNullException("culture"); + } + Contract.EndContractBlock(); + m_compareInfo = culture.CompareInfo; + } + + private Comparer(SerializationInfo info, StreamingContext context) { + m_compareInfo = null; + SerializationInfoEnumerator enumerator = info.GetEnumerator(); + while( enumerator.MoveNext()) { + switch( enumerator.Name) { + case CompareInfoName: + m_compareInfo = (CompareInfo) info.GetValue(CompareInfoName, typeof(CompareInfo)); + break; + } + } + } + + // Compares two Objects by calling CompareTo. If a == + // b,0 is returned. If a implements + // IComparable, a.CompareTo(b) is returned. If a + // doesn't implement IComparable and b does, + // -(b.CompareTo(a)) is returned, otherwise an + // exception is thrown. + // + public int Compare(Object a, Object b) { + if (a == b) return 0; + if (a == null) return -1; + if (b == null) return 1; + if (m_compareInfo != null) { + String sa = a as String; + String sb = b as String; + if (sa != null && sb != null) + return m_compareInfo.Compare(sa, sb); + } + + IComparable ia = a as IComparable; + if (ia != null) + return ia.CompareTo(b); + + IComparable ib = b as IComparable; + if (ib != null) + return -ib.CompareTo(a); + + throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable")); + } + + [System.Security.SecurityCritical] // auto-generated_required + public void GetObjectData(SerializationInfo info, StreamingContext context) { + if (info==null) { + throw new ArgumentNullException("info"); + } + Contract.EndContractBlock(); + + if( m_compareInfo != null) { + info.AddValue(CompareInfoName, m_compareInfo); + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/CompatibleComparer.cs b/src/mscorlib/src/System/Collections/CompatibleComparer.cs new file mode 100644 index 0000000000..85e6c3f0f3 --- /dev/null +++ b/src/mscorlib/src/System/Collections/CompatibleComparer.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +using System.Diagnostics.Contracts; + +namespace System.Collections { + + [Serializable] + internal class CompatibleComparer: IEqualityComparer { + IComparer _comparer; +#pragma warning disable 618 + IHashCodeProvider _hcp; + + internal CompatibleComparer(IComparer comparer, IHashCodeProvider hashCodeProvider) { + _comparer = comparer; + _hcp = hashCodeProvider; + } +#pragma warning restore 618 + + public int Compare(Object a, Object b) { + if (a == b) return 0; + if (a == null) return -1; + if (b == null) return 1; + if (_comparer != null) + return _comparer.Compare(a,b); + IComparable ia = a as IComparable; + if (ia != null) + return ia.CompareTo(b); + + throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable")); + } + + public new bool Equals(Object a, Object b) { + return Compare(a, b) == 0; + } + + public int GetHashCode(Object obj) { + if( obj == null) { + throw new ArgumentNullException("obj"); + } + Contract.EndContractBlock(); + + if (_hcp != null) + return _hcp.GetHashCode(obj); + return obj.GetHashCode(); + } + + // These are helpers for the Hashtable to query the IKeyComparer infrastructure. + internal IComparer Comparer { + get { + return _comparer; + } + } + + // These are helpers for the Hashtable to query the IKeyComparer infrastructure. +#pragma warning disable 618 + internal IHashCodeProvider HashCodeProvider { + get { + return _hcp; + } + } +#pragma warning restore 618 + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs new file mode 100644 index 0000000000..d805dc8be7 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs @@ -0,0 +1,2095 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================ +** +** +** +** Purpose: A scalable dictionary for concurrent access +** +** +===========================================================*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using System.Security; +using System.Security.Permissions; + +namespace System.Collections.Concurrent +{ + + /// <summary> + /// Represents a thread-safe collection of keys and values. + /// </summary> + /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam> + /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam> + /// <remarks> + /// All public and protected members of <see cref="ConcurrentDictionary{TKey,TValue}"/> are thread-safe and may be used + /// concurrently from multiple threads. + /// </remarks> +#if !FEATURE_CORECLR + [Serializable] +#endif + [ComVisible(false)] + [DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue> + { + /// <summary> + /// Tables that hold the internal state of the ConcurrentDictionary + /// + /// Wrapping the three tables in a single object allows us to atomically + /// replace all tables at once. + /// </summary> + private class Tables + { + internal readonly Node[] m_buckets; // A singly-linked list for each bucket. + internal readonly object[] m_locks; // A set of locks, each guarding a section of the table. + internal volatile int[] m_countPerLock; // The number of elements guarded by each lock. + internal readonly IEqualityComparer<TKey> m_comparer; // Key equality comparer + + internal Tables(Node[] buckets, object[] locks, int[] countPerLock, IEqualityComparer<TKey> comparer) + { + m_buckets = buckets; + m_locks = locks; + m_countPerLock = countPerLock; + m_comparer = comparer; + } + } +#if !FEATURE_CORECLR + [NonSerialized] +#endif + private volatile Tables m_tables; // Internal tables of the dictionary + // NOTE: this is only used for compat reasons to serialize the comparer. + // This should not be accessed from anywhere else outside of the serialization methods. + internal IEqualityComparer<TKey> m_comparer; +#if !FEATURE_CORECLR + [NonSerialized] +#endif + private readonly bool m_growLockArray; // Whether to dynamically increase the size of the striped lock + + // How many times we resized becaused of collisions. + // This is used to make sure we don't resize the dictionary because of multi-threaded Add() calls + // that generate collisions. Whenever a GrowTable() should be the only place that changes this +#if !FEATURE_CORECLR + // The field should be have been marked as NonSerialized but because we shipped it without that attribute in 4.5.1. + // we can't add it back without breaking compat. To maximize compat we are going to keep the OptionalField attribute + // This will prevent cases where the field was not serialized. + [OptionalField] +#endif + private int m_keyRehashCount; + +#if !FEATURE_CORECLR + [NonSerialized] +#endif + private int m_budget; // The maximum number of elements per lock before a resize operation is triggered + +#if !FEATURE_CORECLR // These fields are not used in CoreCLR + private KeyValuePair<TKey, TValue>[] m_serializationArray; // Used for custom serialization + + private int m_serializationConcurrencyLevel; // used to save the concurrency level in serialization + + private int m_serializationCapacity; // used to save the capacity in serialization +#endif + // The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the + // DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference + // and blocking, but also the more expensive operations that require all locks become (e.g. table + // resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good + // compromise. + private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4; + + // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making + // a trade-off between the size of a very small dictionary, and the number of resizes when + // constructing a large dictionary. Also, the capacity should not be divisible by a small prime. + private const int DEFAULT_CAPACITY = 31; + + // The maximum size of the striped lock that will not be exceeded when locks are automatically + // added as the dictionary grows. However, the user is allowed to exceed this limit by passing + // a concurrency level larger than MAX_LOCK_NUMBER into the constructor. + private const int MAX_LOCK_NUMBER = 1024; + + // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) + private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); + + + /// <summary> + /// Determines whether type TValue can be written atomically + /// </summary> + private static bool IsValueWriteAtomic() + { + Type valueType = typeof(TValue); + + // + // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without + // the risk of tearing. + // + // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf + // + bool isAtomic = + (valueType.IsClass) + || valueType == typeof(Boolean) + || valueType == typeof(Char) + || valueType == typeof(Byte) + || valueType == typeof(SByte) + || valueType == typeof(Int16) + || valueType == typeof(UInt16) + || valueType == typeof(Int32) + || valueType == typeof(UInt32) + || valueType == typeof(Single); + + if (!isAtomic && IntPtr.Size == 8) + { + isAtomic |= valueType == typeof(Double) || valueType == typeof(Int64); + } + + return isAtomic; + } + + /// <summary> + /// Initializes a new instance of the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the key type. + /// </summary> + public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer<TKey>.Default) { } + + /// <summary> + /// Initializes a new instance of the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that is empty, has the specified concurrency level and capacity, and uses the default + /// comparer for the key type. + /// </summary> + /// <param name="concurrencyLevel">The estimated number of threads that will update the + /// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param> + /// <param name="capacity">The initial number of elements that the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// can contain.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is + /// less than 1.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than + /// 0.</exception> + public ConcurrentDictionary(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, false, EqualityComparer<TKey>.Default) { } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that contains elements copied from the specified <see + /// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/>, has the default concurrency + /// level, has the default initial capacity, and uses the default comparer for the key type. + /// </summary> + /// <param name="collection">The <see + /// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to + /// the new + /// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentException"><paramref name="collection"/> contains one or more + /// duplicate keys.</exception> + public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, EqualityComparer<TKey>.Default) { } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that is empty, has the specified concurrency level and capacity, and uses the specified + /// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>. + /// </summary> + /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/> + /// implementation to use when comparing keys.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference + /// (Nothing in Visual Basic).</exception> + public ConcurrentDictionary(IEqualityComparer<TKey> comparer) : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, comparer) { } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that contains elements copied from the specified <see + /// cref="T:System.Collections.IEnumerable"/>, has the default concurrency level, has the default + /// initial capacity, and uses the specified + /// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>. + /// </summary> + /// <param name="collection">The <see + /// cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to + /// the new + /// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param> + /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/> + /// implementation to use when comparing keys.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference + /// (Nothing in Visual Basic). -or- + /// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic). + /// </exception> + public ConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) + : this(comparer) + { + if (collection == null) throw new ArgumentNullException("collection"); + + InitializeFromCollection(collection); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable"/>, + /// has the specified concurrency level, has the specified initial capacity, and uses the specified + /// <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>. + /// </summary> + /// <param name="concurrencyLevel">The estimated number of threads that will update the + /// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param> + /// <param name="collection">The <see cref="T:System.Collections.IEnumerable{KeyValuePair{TKey,TValue}}"/> whose elements are copied to the new + /// <see cref="ConcurrentDictionary{TKey,TValue}"/>.</param> + /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/> implementation to use + /// when comparing keys.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="collection"/> is a null reference (Nothing in Visual Basic). + /// -or- + /// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic). + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="concurrencyLevel"/> is less than 1. + /// </exception> + /// <exception cref="T:System.ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception> + public ConcurrentDictionary( + int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) + : this(concurrencyLevel, DEFAULT_CAPACITY, false, comparer) + { + if (collection == null) throw new ArgumentNullException("collection"); + if (comparer == null) throw new ArgumentNullException("comparer"); + + InitializeFromCollection(collection); + } + + private void InitializeFromCollection(IEnumerable<KeyValuePair<TKey, TValue>> collection) + { + TValue dummy; + foreach (KeyValuePair<TKey, TValue> pair in collection) + { + if (pair.Key == null) throw new ArgumentNullException("key"); + + if (!TryAddInternal(pair.Key, pair.Value, false, false, out dummy)) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_SourceContainsDuplicateKeys")); + } + } + + if (m_budget == 0) + { + m_budget = m_tables.m_buckets.Length / m_tables.m_locks.Length; + } + + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// class that is empty, has the specified concurrency level, has the specified initial capacity, and + /// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/>. + /// </summary> + /// <param name="concurrencyLevel">The estimated number of threads that will update the + /// <see cref="ConcurrentDictionary{TKey,TValue}"/> concurrently.</param> + /// <param name="capacity">The initial number of elements that the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// can contain.</param> + /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{TKey}"/> + /// implementation to use when comparing keys.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="concurrencyLevel"/> is less than 1. -or- + /// <paramref name="capacity"/> is less than 0. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference + /// (Nothing in Visual Basic).</exception> + public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer) + : this(concurrencyLevel, capacity, false, comparer) + { + } + + internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<TKey> comparer) + { + if (concurrencyLevel < 1) + { + throw new ArgumentOutOfRangeException("concurrencyLevel", GetResource("ConcurrentDictionary_ConcurrencyLevelMustBePositive")); + } + if (capacity < 0) + { + throw new ArgumentOutOfRangeException("capacity", GetResource("ConcurrentDictionary_CapacityMustNotBeNegative")); + } + if (comparer == null) throw new ArgumentNullException("comparer"); + + // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard + // any buckets. + if (capacity < concurrencyLevel) + { + capacity = concurrencyLevel; + } + + object[] locks = new object[concurrencyLevel]; + for (int i = 0; i < locks.Length; i++) + { + locks[i] = new object(); + } + + int[] countPerLock = new int[locks.Length]; + Node[] buckets = new Node[capacity]; + m_tables = new Tables(buckets, locks, countPerLock, comparer); + + m_growLockArray = growLockArray; + m_budget = buckets.Length / locks.Length; + } + + + /// <summary> + /// Attempts to add the specified key and value to the <see cref="ConcurrentDictionary{TKey, + /// TValue}"/>. + /// </summary> + /// <param name="key">The key of the element to add.</param> + /// <param name="value">The value of the element to add. The value can be a null reference (Nothing + /// in Visual Basic) for reference types.</param> + /// <returns>true if the key/value pair was added to the <see cref="ConcurrentDictionary{TKey, + /// TValue}"/> + /// successfully; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The <see cref="ConcurrentDictionary{TKey, TValue}"/> + /// contains too many elements.</exception> + public bool TryAdd(TKey key, TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + TValue dummy; + return TryAddInternal(key, value, false, true, out dummy); + } + + /// <summary> + /// Determines whether the <see cref="ConcurrentDictionary{TKey, TValue}"/> contains the specified + /// key. + /// </summary> + /// <param name="key">The key to locate in the <see cref="ConcurrentDictionary{TKey, + /// TValue}"/>.</param> + /// <returns>true if the <see cref="ConcurrentDictionary{TKey, TValue}"/> contains an element with + /// the specified key; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + public bool ContainsKey(TKey key) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue throwAwayValue; + return TryGetValue(key, out throwAwayValue); + } + + /// <summary> + /// Attempts to remove and return the the value with the specified key from the + /// <see cref="ConcurrentDictionary{TKey, TValue}"/>. + /// </summary> + /// <param name="key">The key of the element to remove and return.</param> + /// <param name="value">When this method returns, <paramref name="value"/> contains the object removed from the + /// <see cref="ConcurrentDictionary{TKey,TValue}"/> or the default value of <typeparamref + /// name="TValue"/> + /// if the operation failed.</param> + /// <returns>true if an object was removed successfully; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + public bool TryRemove(TKey key, out TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + return TryRemoveInternal(key, out value, false, default(TValue)); + } + + /// <summary> + /// Removes the specified key from the dictionary if it exists and returns its associated value. + /// If matchValue flag is set, the key will be removed only if is associated with a particular + /// value. + /// </summary> + /// <param name="key">The key to search for and remove if it exists.</param> + /// <param name="value">The variable into which the removed value, if found, is stored.</param> + /// <param name="matchValue">Whether removal of the key is conditional on its value.</param> + /// <param name="oldValue">The conditional value to compare against if <paramref name="matchValue"/> is true</param> + /// <returns></returns> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue) + { + while (true) + { + Tables tables = m_tables; + + IEqualityComparer<TKey> comparer = tables.m_comparer; + + int bucketNo, lockNo; + GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); + + lock (tables.m_locks[lockNo]) + { + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != m_tables) + { + continue; + } + + Node prev = null; + for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next) + { + Assert((prev == null && curr == tables.m_buckets[bucketNo]) || prev.m_next == curr); + + if (comparer.Equals(curr.m_key, key)) + { + if (matchValue) + { + bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr.m_value); + if (!valuesMatch) + { + value = default(TValue); + return false; + } + } + + if (prev == null) + { + Volatile.Write<Node>(ref tables.m_buckets[bucketNo], curr.m_next); + } + else + { + prev.m_next = curr.m_next; + } + + value = curr.m_value; + tables.m_countPerLock[lockNo]--; + return true; + } + prev = curr; + } + } + + value = default(TValue); + return false; + } + } + + /// <summary> + /// Attempts to get the value associated with the specified key from the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>. + /// </summary> + /// <param name="key">The key of the value to get.</param> + /// <param name="value">When this method returns, <paramref name="value"/> contains the object from + /// the + /// <see cref="ConcurrentDictionary{TKey,TValue}"/> with the specified key or the default value of + /// <typeparamref name="TValue"/>, if the operation failed.</param> + /// <returns>true if the key was found in the <see cref="ConcurrentDictionary{TKey,TValue}"/>; + /// otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + public bool TryGetValue(TKey key, out TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + int bucketNo, lockNoUnused; + + // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize. + Tables tables = m_tables; + IEqualityComparer<TKey> comparer = tables.m_comparer; + GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables.m_buckets.Length, tables.m_locks.Length); + + // We can get away w/out a lock here. + // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. + Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]); + + while (n != null) + { + if (comparer.Equals(n.m_key, key)) + { + value = n.m_value; + return true; + } + n = n.m_next; + } + + value = default(TValue); + return false; + } + + /// <summary> + /// Compares the existing value for the specified key with a specified value, and if they're equal, + /// updates the key with a third value. + /// </summary> + /// <param name="key">The key whose value is compared with <paramref name="comparisonValue"/> and + /// possibly replaced.</param> + /// <param name="newValue">The value that replaces the value of the element with <paramref + /// name="key"/> if the comparison results in equality.</param> + /// <param name="comparisonValue">The value that is compared to the value of the element with + /// <paramref name="key"/>.</param> + /// <returns>true if the value with <paramref name="key"/> was equal to <paramref + /// name="comparisonValue"/> and replaced with <paramref name="newValue"/>; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null + /// reference.</exception> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) + { + if (key == null) throw new ArgumentNullException("key"); + + IEqualityComparer<TValue> valueComparer = EqualityComparer<TValue>.Default; + + while (true) + { + int bucketNo; + int lockNo; + int hashcode; + + Tables tables = m_tables; + IEqualityComparer<TKey> comparer = tables.m_comparer; + + hashcode = comparer.GetHashCode(key); + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); + + lock (tables.m_locks[lockNo]) + { + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != m_tables) + { + continue; + } + + // Try to find this key in the bucket + Node prev = null; + for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next) + { + Assert((prev == null && node == tables.m_buckets[bucketNo]) || prev.m_next == node); + if (comparer.Equals(node.m_key, key)) + { + if (valueComparer.Equals(node.m_value, comparisonValue)) + { + if (s_isValueWriteAtomic) + { + node.m_value = newValue; + } + else + { + Node newNode = new Node(node.m_key, newValue, hashcode, node.m_next); + + if (prev == null) + { + tables.m_buckets[bucketNo] = newNode; + } + else + { + prev.m_next = newNode; + } + } + + return true; + } + + return false; + } + + prev = node; + } + + //didn't find the key + return false; + } + } + } + + /// <summary> + /// Removes all keys and values from the <see cref="ConcurrentDictionary{TKey,TValue}"/>. + /// </summary> + public void Clear() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + Tables newTables = new Tables(new Node[DEFAULT_CAPACITY], m_tables.m_locks, new int[m_tables.m_countPerLock.Length], m_tables.m_comparer); + m_tables = newTables; + m_budget = Math.Max(1, newTables.m_buckets.Length / newTables.m_locks.Length); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection"/> to an array of + /// type <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/>, starting at the + /// specified array index. + /// </summary> + /// <param name="array">The one-dimensional array of type <see + /// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> + /// that is the destination of the <see + /// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> elements copied from the <see + /// cref="T:System.Collections.ICollection"/>. The array must have zero-based indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// 0.</exception> + /// <exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than + /// the length of the <paramref name="array"/>. -or- The number of elements in the source <see + /// cref="T:System.Collections.ICollection"/> + /// is greater than the available space from <paramref name="index"/> to the end of the destination + /// <paramref name="array"/>.</exception> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index) + { + if (array == null) throw new ArgumentNullException("array"); + if (index < 0) throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative")); + + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + int count = 0; + + for (int i = 0; i < m_tables.m_locks.Length && count >= 0; i++) + { + count += m_tables.m_countPerLock[i]; + } + + if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow + { + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough")); + } + + CopyToPairs(array, index); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Copies the key and value pairs stored in the <see cref="ConcurrentDictionary{TKey,TValue}"/> to a + /// new array. + /// </summary> + /// <returns>A new array containing a snapshot of key and value pairs copied from the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</returns> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + public KeyValuePair<TKey, TValue>[] ToArray() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + int count = 0; + checked + { + for (int i = 0; i < m_tables.m_locks.Length; i++) + { + count += m_tables.m_countPerLock[i]; + } + } + + KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count]; + + CopyToPairs(array, 0); + return array; + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToPairs. + /// </summary> + private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index) + { + Node[] buckets = m_tables.m_buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); + index++; //this should never flow, CopyToPairs is only called when there's no overflow risk + } + } + } + + /// <summary> + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToEntries. + /// </summary> + private void CopyToEntries(DictionaryEntry[] array, int index) + { + Node[] buckets = m_tables.m_buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new DictionaryEntry(current.m_key, current.m_value); + index++; //this should never flow, CopyToEntries is only called when there's no overflow risk + } + } + } + + /// <summary> + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToObjects. + /// </summary> + private void CopyToObjects(object[] array, int index) + { + Node[] buckets = m_tables.m_buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); + index++; //this should never flow, CopyToObjects is only called when there's no overflow risk + } + } + } + + /// <summary>Returns an enumerator that iterates through the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</summary> + /// <returns>An enumerator for the <see cref="ConcurrentDictionary{TKey,TValue}"/>.</returns> + /// <remarks> + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after <see cref="GetEnumerator"/> was called. + /// </remarks> + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + Node[] buckets = m_tables.m_buckets; + + for (int i = 0; i < buckets.Length; i++) + { + // The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i]. + Node current = Volatile.Read<Node>(ref buckets[i]); + + while (current != null) + { + yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value); + current = current.m_next; + } + } + } + + /// <summary> + /// Shared internal implementation for inserts and updates. + /// If key exists, we always return false; and if updateIfExists == true we force update with value; + /// If key doesn't exist, we always add value and return true; + /// </summary> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) + { + while (true) + { + int bucketNo, lockNo; + int hashcode; + + Tables tables = m_tables; + IEqualityComparer<TKey> comparer = tables.m_comparer; + hashcode = comparer.GetHashCode(key); + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length); + + bool resizeDesired = false; + bool lockTaken = false; +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + bool resizeDueToCollisions = false; +#endif // !FEATURE_CORECLR +#endif + + try + { + if (acquireLock) + Monitor.Enter(tables.m_locks[lockNo], ref lockTaken); + + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != m_tables) + { + continue; + } + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + int collisionCount = 0; +#endif // !FEATURE_CORECLR +#endif + + // Try to find this key in the bucket + Node prev = null; + for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next) + { + Assert((prev == null && node == tables.m_buckets[bucketNo]) || prev.m_next == node); + if (comparer.Equals(node.m_key, key)) + { + // The key was found in the dictionary. If updates are allowed, update the value for that key. + // We need to create a new node for the update, in order to support TValue types that cannot + // be written atomically, since lock-free reads may be happening concurrently. + if (updateIfExists) + { + if (s_isValueWriteAtomic) + { + node.m_value = value; + } + else + { + Node newNode = new Node(node.m_key, value, hashcode, node.m_next); + if (prev == null) + { + tables.m_buckets[bucketNo] = newNode; + } + else + { + prev.m_next = newNode; + } + } + resultingValue = value; + } + else + { + resultingValue = node.m_value; + } + return false; + } + prev = node; + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + collisionCount++; +#endif // !FEATURE_CORECLR +#endif + } + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer)) + { + resizeDesired = true; + resizeDueToCollisions = true; + } +#endif // !FEATURE_CORECLR +#endif + + // The key was not found in the bucket. Insert the key-value pair. + Volatile.Write<Node>(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo])); + checked + { + tables.m_countPerLock[lockNo]++; + } + + // + // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table. + // It is also possible that GrowTable will increase the budget but won't resize the bucket table. + // That happens if the bucket table is found to be poorly utilized due to a bad hash function. + // + if (tables.m_countPerLock[lockNo] > m_budget) + { + resizeDesired = true; + } + } + finally + { + if (lockTaken) + Monitor.Exit(tables.m_locks[lockNo]); + } + + // + // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table. + // + // Concurrency notes: + // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks. + // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0 + // and then verify that the table we passed to it as the argument is still the current table. + // + if (resizeDesired) + { +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + if (resizeDueToCollisions) + { + GrowTable(tables, (IEqualityComparer<TKey>)HashHelpers.GetRandomizedEqualityComparer(comparer), true, m_keyRehashCount); + } + else +#endif // !FEATURE_CORECLR + { + GrowTable(tables, tables.m_comparer, false, m_keyRehashCount); + } +#else + GrowTable(tables, tables.m_comparer, false, m_keyRehashCount); +#endif + } + + resultingValue = value; + return true; + } + } + + /// <summary> + /// Gets or sets the value associated with the specified key. + /// </summary> + /// <param name="key">The key of the value to get or set.</param> + /// <value>The value associated with the specified key. If the specified key is not found, a get + /// operation throws a + /// <see cref="T:Sytem.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new + /// element with the specified key.</value> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">The property is retrieved and + /// <paramref name="key"/> + /// does not exist in the collection.</exception> + public TValue this[TKey key] + { + get + { + TValue value; + if (!TryGetValue(key, out value)) + { + throw new KeyNotFoundException(); + } + return value; + } + set + { + if (key == null) throw new ArgumentNullException("key"); + TValue dummy; + TryAddInternal(key, value, true, true, out dummy); + } + } + + /// <summary> + /// Gets the number of key/value pairs contained in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>. + /// </summary> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <value>The number of key/value paris contained in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</value> + /// <remarks>Count has snapshot semantics and represents the number of items in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/> + /// at the moment when Count was accessed.</remarks> + public int Count + { + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + get + { + int count = 0; + + int acquiredLocks = 0; + try + { + // Acquire all locks + AcquireAllLocks(ref acquiredLocks); + + // Compute the count, we allow overflow + for (int i = 0; i < m_tables.m_countPerLock.Length; i++) + { + count += m_tables.m_countPerLock[i]; + } + + } + finally + { + // Release locks that have been acquired earlier + ReleaseLocks(0, acquiredLocks); + } + + return count; + } + } + + /// <summary> + /// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// if the key does not already exist. + /// </summary> + /// <param name="key">The key of the element to add.</param> + /// <param name="valueFactory">The function used to generate a value for the key</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentNullException"><paramref name="valueFactory"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <returns>The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value for the key as returned by valueFactory + /// if the key was not in the dictionary.</returns> + public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (valueFactory == null) throw new ArgumentNullException("valueFactory"); + + TValue resultingValue; + if (TryGetValue(key, out resultingValue)) + { + return resultingValue; + } + TryAddInternal(key, valueFactory(key), false, true, out resultingValue); + return resultingValue; + } + + /// <summary> + /// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> + /// if the key does not already exist. + /// </summary> + /// <param name="key">The key of the element to add.</param> + /// <param name="value">the value to be added, if the key does not already exist</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <returns>The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value if the key was not in the dictionary.</returns> + public TValue GetOrAdd(TKey key, TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue resultingValue; + TryAddInternal(key, value, false, true, out resultingValue); + return resultingValue; + } + + /// <summary> + /// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key does not already + /// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key + /// already exists. + /// </summary> + /// <param name="key">The key to be added or whose value should be updated</param> + /// <param name="addValueFactory">The function used to generate a value for an absent key</param> + /// <param name="updateValueFactory">The function used to generate a new value for an existing key + /// based on the key's existing value</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentNullException"><paramref name="addValueFactory"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <returns>The new value for the key. This will be either be the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present).</returns> + public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (addValueFactory == null) throw new ArgumentNullException("addValueFactory"); + if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); + + TValue newValue, resultingValue; + while (true) + { + TValue oldValue; + if (TryGetValue(key, out oldValue)) + //key exists, try to update + { + newValue = updateValueFactory(key, oldValue); + if (TryUpdate(key, newValue, oldValue)) + { + return newValue; + } + } + else //try add + { + newValue = addValueFactory(key); + if (TryAddInternal(key, newValue, false, true, out resultingValue)) + { + return resultingValue; + } + } + } + } + + /// <summary> + /// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key does not already + /// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary{TKey,TValue}"/> if the key + /// already exists. + /// </summary> + /// <param name="key">The key to be added or whose value should be updated</param> + /// <param name="addValue">The value to be added for an absent key</param> + /// <param name="updateValueFactory">The function used to generate a new value for an existing key based on + /// the key's existing value</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <returns>The new value for the key. This will be either be the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present).</returns> + public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); + TValue newValue, resultingValue; + while (true) + { + TValue oldValue; + if (TryGetValue(key, out oldValue)) + //key exists, try to update + { + newValue = updateValueFactory(key, oldValue); + if (TryUpdate(key, newValue, oldValue)) + { + return newValue; + } + } + else //try add + { + if (TryAddInternal(key, addValue, false, true, out resultingValue)) + { + return resultingValue; + } + } + } + } + + + + /// <summary> + /// Gets a value that indicates whether the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty. + /// </summary> + /// <value>true if the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty; otherwise, + /// false.</value> + public bool IsEmpty + { + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + get + { + int acquiredLocks = 0; + try + { + // Acquire all locks + AcquireAllLocks(ref acquiredLocks); + + for (int i = 0; i < m_tables.m_countPerLock.Length; i++) + { + if (m_tables.m_countPerLock[i] != 0) + { + return false; + } + } + } + finally + { + // Release locks that have been acquired earlier + ReleaseLocks(0, acquiredLocks); + } + + return true; + } + } + + #region IDictionary<TKey,TValue> members + + /// <summary> + /// Adds the specified key and value to the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>. + /// </summary> + /// <param name="key">The object to use as the key of the element to add.</param> + /// <param name="value">The object to use as the value of the element to add.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <exception cref="T:System.ArgumentException"> + /// An element with the same key already exists in the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</exception> + void IDictionary<TKey, TValue>.Add(TKey key, TValue value) + { + if (!TryAdd(key, value)) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_KeyAlreadyExisted")); + } + } + + /// <summary> + /// Removes the element with the specified key from the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>. + /// </summary> + /// <param name="key">The key of the element to remove.</param> + /// <returns>true if the element is successfully remove; otherwise false. This method also returns + /// false if + /// <paramref name="key"/> was not found in the original <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + bool IDictionary<TKey, TValue>.Remove(TKey key) + { + TValue throwAwayValue; + return TryRemove(key, out throwAwayValue); + } + + /// <summary> + /// Gets a collection containing the keys in the <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.Generic.ICollection{TKey}"/> containing the keys in the + /// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</value> + public ICollection<TKey> Keys + { + get { return GetKeys(); } + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.Generic.IEnumerable{TKey}"/> containing the keys of + /// the <see cref="T:System.Collections.Generic.IReadOnlyDictionary{TKey,TValue}"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.Generic.IEnumerable{TKey}"/> containing the keys of + /// the <see cref="T:System.Collections.Generic.IReadOnlyDictionary{TKey,TValue}"/>.</value> + IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys + { + get { return GetKeys(); } + } + + /// <summary> + /// Gets a collection containing the values in the <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.Generic.ICollection{TValue}"/> containing the values in + /// the + /// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</value> + public ICollection<TValue> Values + { + get { return GetValues(); } + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.Generic.IEnumerable{TValue}"/> containing the values + /// in the <see cref="T:System.Collections.Generic.IReadOnlyDictionary{TKey,TValue}"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.Generic.IEnumerable{TValue}"/> containing the + /// values in the <see cref="T:System.Collections.Generic.IReadOnlyDictionary{TKey,TValue}"/>.</value> + IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values + { + get { return GetValues(); } + } + #endregion + + #region ICollection<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Adds the specified value to the <see cref="T:System.Collections.Generic.ICollection{TValue}"/> + /// with the specified key. + /// </summary> + /// <param name="keyValuePair">The <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> + /// structure representing the key and value to add to the <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="keyValuePair"/> of <paramref + /// name="keyValuePair"/> is null.</exception> + /// <exception cref="T:System.OverflowException">The <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/> + /// contains too many elements.</exception> + /// <exception cref="T:System.ArgumentException">An element with the same key already exists in the + /// <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/></exception> + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair) + { + ((IDictionary<TKey, TValue>)this).Add(keyValuePair.Key, keyValuePair.Value); + } + + /// <summary> + /// Determines whether the <see cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/> + /// contains a specific key and value. + /// </summary> + /// <param name="keyValuePair">The <see cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> + /// structure to locate in the <see + /// cref="T:System.Collections.Generic.ICollection{TValue}"/>.</param> + /// <returns>true if the <paramref name="keyValuePair"/> is found in the <see + /// cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/>; otherwise, false.</returns> + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair) + { + TValue value; + if (!TryGetValue(keyValuePair.Key, out value)) + { + return false; + } + return EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value); + } + + /// <summary> + /// Gets a value indicating whether the dictionary is read-only. + /// </summary> + /// <value>true if the <see cref="T:System.Collections.Generic.ICollection{TKey,TValue}"/> is + /// read-only; otherwise, false. For <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>, this property always returns + /// false.</value> + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Removes a key and value from the dictionary. + /// </summary> + /// <param name="keyValuePair">The <see + /// cref="T:System.Collections.Generic.KeyValuePair{TKey,TValue}"/> + /// structure representing the key and value to remove from the <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>.</param> + /// <returns>true if the key and value represented by <paramref name="keyValuePair"/> is successfully + /// found and removed; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException">The Key property of <paramref + /// name="keyValuePair"/> is a null reference (Nothing in Visual Basic).</exception> + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair) + { + if (keyValuePair.Key == null) throw new ArgumentNullException(GetResource("ConcurrentDictionary_ItemKeyIsNull")); + + TValue throwAwayValue; + return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value); + } + + #endregion + + #region IEnumerable Members + + /// <summary>Returns an enumerator that iterates through the <see + /// cref="ConcurrentDictionary{TKey,TValue}"/>.</summary> + /// <returns>An enumerator for the <see cref="ConcurrentDictionary{TKey,TValue}"/>.</returns> + /// <remarks> + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after <see cref="GetEnumerator"/> was called. + /// </remarks> + IEnumerator IEnumerable.GetEnumerator() + { + return ((ConcurrentDictionary<TKey, TValue>)this).GetEnumerator(); + } + + #endregion + + #region IDictionary Members + + /// <summary> + /// Adds the specified key and value to the dictionary. + /// </summary> + /// <param name="key">The object to use as the key.</param> + /// <param name="value">The object to use as the value.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.OverflowException">The dictionary contains too many + /// elements.</exception> + /// <exception cref="T:System.ArgumentException"> + /// <paramref name="key"/> is of a type that is not assignable to the key type <typeparamref + /// name="TKey"/> of the <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. -or- + /// <paramref name="value"/> is of a type that is not assignable to <typeparamref name="TValue"/>, + /// the type of values in the <see cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. + /// -or- A value with the same key already exists in the <see + /// cref="T:System.Collections.Generic.Dictionary{TKey,TValue}"/>. + /// </exception> + void IDictionary.Add(object key, object value) + { + if (key == null) throw new ArgumentNullException("key"); + if (!(key is TKey)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect")); + + TValue typedValue; + try + { + typedValue = (TValue)value; + } + catch (InvalidCastException) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect")); + } + + ((IDictionary<TKey, TValue>)this).Add((TKey)key, typedValue); + } + + /// <summary> + /// Gets whether the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> contains an + /// element with the specified key. + /// </summary> + /// <param name="key">The key to locate in the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</param> + /// <returns>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> contains + /// an element with the specified key; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentNullException"> <paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + bool IDictionary.Contains(object key) + { + if (key == null) throw new ArgumentNullException("key"); + + return (key is TKey) && ((ConcurrentDictionary<TKey, TValue>)this).ContainsKey((TKey)key); + } + + /// <summary>Provides an <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the + /// <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</summary> + /// <returns>An <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</returns> + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return new DictionaryEnumerator(this); + } + + /// <summary> + /// Gets a value indicating whether the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> has a fixed size. + /// </summary> + /// <value>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> has a + /// fixed size; otherwise, false. For <see + /// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>, this property always + /// returns false.</value> + bool IDictionary.IsFixedSize + { + get { return false; } + } + + /// <summary> + /// Gets a value indicating whether the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> is read-only. + /// </summary> + /// <value>true if the <see cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/> is + /// read-only; otherwise, false. For <see + /// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>, this property always + /// returns false.</value> + bool IDictionary.IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.ICollection"/> containing the keys of the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.ICollection"/> containing the keys of the <see + /// cref="T:System.Collections.Generic.IDictionary{TKey,TValue}"/>.</value> + ICollection IDictionary.Keys + { + get { return GetKeys(); } + } + + /// <summary> + /// Removes the element with the specified key from the <see + /// cref="T:System.Collections.IDictionary"/>. + /// </summary> + /// <param name="key">The key of the element to remove.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + void IDictionary.Remove(object key) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue throwAwayValue; + if (key is TKey) + { + this.TryRemove((TKey)key, out throwAwayValue); + } + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.ICollection"/> containing the values in the <see + /// cref="T:System.Collections.IDictionary"/>. + /// </summary> + /// <value>An <see cref="T:System.Collections.ICollection"/> containing the values in the <see + /// cref="T:System.Collections.IDictionary"/>.</value> + ICollection IDictionary.Values + { + get { return GetValues(); } + } + + /// <summary> + /// Gets or sets the value associated with the specified key. + /// </summary> + /// <param name="key">The key of the value to get or set.</param> + /// <value>The value associated with the specified key, or a null reference (Nothing in Visual Basic) + /// if <paramref name="key"/> is not in the dictionary or <paramref name="key"/> is of a type that is + /// not assignable to the key type <typeparamref name="TKey"/> of the <see + /// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>.</value> + /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentException"> + /// A value is being assigned, and <paramref name="key"/> is of a type that is not assignable to the + /// key type <typeparamref name="TKey"/> of the <see + /// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/>. -or- A value is being + /// assigned, and <paramref name="key"/> is of a type that is not assignable to the value type + /// <typeparamref name="TValue"/> of the <see + /// cref="T:System.Collections.Generic.ConcurrentDictionary{TKey,TValue}"/> + /// </exception> + object IDictionary.this[object key] + { + get + { + if (key == null) throw new ArgumentNullException("key"); + + TValue value; + if (key is TKey && this.TryGetValue((TKey)key, out value)) + { + return value; + } + + return null; + } + set + { + if (key == null) throw new ArgumentNullException("key"); + + if (!(key is TKey)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect")); + if (!(value is TValue)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect")); + + ((ConcurrentDictionary<TKey, TValue>)this)[(TKey)key] = (TValue)value; + } + } + + #endregion + + #region ICollection Members + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an array, starting + /// at the specified array index. + /// </summary> + /// <param name="array">The one-dimensional array that is the destination of the elements copied from + /// the <see cref="T:System.Collections.ICollection"/>. The array must have zero-based + /// indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// 0.</exception> + /// <exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than + /// the length of the <paramref name="array"/>. -or- The number of elements in the source <see + /// cref="T:System.Collections.ICollection"/> + /// is greater than the available space from <paramref name="index"/> to the end of the destination + /// <paramref name="array"/>.</exception> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + void ICollection.CopyTo(Array array, int index) + { + if (array == null) throw new ArgumentNullException("array"); + if (index < 0) throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative")); + + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + Tables tables = m_tables; + + int count = 0; + + for (int i = 0; i < tables.m_locks.Length && count >= 0; i++) + { + count += tables.m_countPerLock[i]; + } + + if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow + { + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough")); + } + + // To be consistent with the behavior of ICollection.CopyTo() in Dictionary<TKey,TValue>, + // we recognize three types of target arrays: + // - an array of KeyValuePair<TKey, TValue> structs + // - an array of DictionaryEntry structs + // - an array of objects + + KeyValuePair<TKey, TValue>[] pairs = array as KeyValuePair<TKey, TValue>[]; + if (pairs != null) + { + CopyToPairs(pairs, index); + return; + } + + DictionaryEntry[] entries = array as DictionaryEntry[]; + if (entries != null) + { + CopyToEntries(entries, index); + return; + } + + object[] objects = array as object[]; + if (objects != null) + { + CopyToObjects(objects, index); + return; + } + + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayIncorrectType"), "array"); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is + /// synchronized with the SyncRoot. + /// </summary> + /// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized + /// (thread safe); otherwise, false. For <see + /// cref="T:System.Collections.Concurrent.ConcurrentDictionary{TKey,TValue}"/>, this property always + /// returns false.</value> + bool ICollection.IsSynchronized + { + get { return false; } + } + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see + /// cref="T:System.Collections.ICollection"/>. This property is not supported. + /// </summary> + /// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception> + object ICollection.SyncRoot + { + get + { + throw new NotSupportedException(Environment.GetResourceString("ConcurrentCollection_SyncRoot_NotSupported")); + } + } + + #endregion + + /// <summary> + /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the + /// table as a result of a race condition, the Tables instance that holds the table of buckets deemed too + /// small is passed in as an argument to GrowTable(). GrowTable() obtains a lock, and then checks + /// the Tables instance has been replaced in the meantime or not. + /// The <paramref name="rehashCount"/> will be used to ensure that we don't do two subsequent resizes + /// because of a collision + /// </summary> + private void GrowTable(Tables tables, IEqualityComparer<TKey> newComparer, bool regenerateHashKeys, int rehashCount) + { + int locksAcquired = 0; + try + { + // The thread that first obtains m_locks[0] will be the one doing the resize operation + AcquireLocks(0, 1, ref locksAcquired); + + if (regenerateHashKeys && rehashCount == m_keyRehashCount) + { + // This method is called with regenerateHashKeys==true when we detected + // more than HashHelpers.HashCollisionThreshold collisions when adding a new element. + // In that case we are in the process of switching to another (randomized) comparer + // and we have to re-hash all the keys in the table. + // We are only going to do this if we did not just rehash the entire table while waiting for the lock + tables = m_tables; + } + else + { + // If we don't require a regeneration of hash keys we want to make sure we don't do work when + // we don't have to + if (tables != m_tables) + { + // We assume that since the table reference is different, it was already resized (or the budget + // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, + // we will have to revisit this logic. + return; + } + + // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. + long approxCount = 0; + for (int i = 0; i < tables.m_countPerLock.Length; i++) + { + approxCount += tables.m_countPerLock[i]; + } + + // + // If the bucket array is too empty, double the budget instead of resizing the table + // + if (approxCount < tables.m_buckets.Length / 4) + { + m_budget = 2 * m_budget; + if (m_budget < 0) + { + m_budget = int.MaxValue; + } + + return; + } + } + // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by + // 2,3,5 or 7. We can consider a different table-sizing policy in the future. + int newLength = 0; + bool maximizeTableSize = false; + try + { + checked + { + // Double the size of the buckets table and add one, so that we have an odd integer. + newLength = tables.m_buckets.Length * 2 + 1; + + // Now, we only need to check odd integers, and find the first that is not divisible + // by 3, 5 or 7. + while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) + { + newLength += 2; + } + + Assert(newLength % 2 != 0); + + if (newLength > Array.MaxArrayLength) + { + maximizeTableSize = true; + } + } + } + catch (OverflowException) + { + maximizeTableSize = true; + } + + if (maximizeTableSize) + { + newLength = Array.MaxArrayLength; + + // We want to make sure that GrowTable will not be called again, since table is at the maximum size. + // To achieve that, we set the budget to int.MaxValue. + // + // (There is one special case that would allow GrowTable() to be called in the future: + // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.) + m_budget = int.MaxValue; + } + + // Now acquire all other locks for the table + AcquireLocks(1, tables.m_locks.Length, ref locksAcquired); + + object[] newLocks = tables.m_locks; + + // Add more locks + if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER) + { + newLocks = new object[tables.m_locks.Length * 2]; + Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length); + + for (int i = tables.m_locks.Length; i < newLocks.Length; i++) + { + newLocks[i] = new object(); + } + } + + Node[] newBuckets = new Node[newLength]; + int[] newCountPerLock = new int[newLocks.Length]; + + // Copy all data into a new table, creating new nodes for all elements + for (int i = 0; i < tables.m_buckets.Length; i++) + { + Node current = tables.m_buckets[i]; + while (current != null) + { + Node next = current.m_next; + int newBucketNo, newLockNo; + int nodeHashCode = current.m_hashcode; + + if (regenerateHashKeys) + { + // Recompute the hash from the key + nodeHashCode = newComparer.GetHashCode(current.m_key); + } + + GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); + + newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, newBuckets[newBucketNo]); + + checked + { + newCountPerLock[newLockNo]++; + } + + current = next; + } + } + + // If this resize regenerated the hashkeys, increment the count + if (regenerateHashKeys) + { + // We use unchecked here because we don't want to throw an exception if + // an overflow happens + unchecked + { + m_keyRehashCount++; + } + } + + // Adjust the budget + m_budget = Math.Max(1, newBuckets.Length / newLocks.Length); + + // Replace tables with the new versions + m_tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer); + } + finally + { + // Release all locks that we took earlier + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Computes the bucket and lock number for a particular key. + /// </summary> + private void GetBucketAndLockNo( + int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; + + Assert(bucketNo >= 0 && bucketNo < bucketCount); + Assert(lockNo >= 0 && lockNo < lockCount); + } + + /// <summary> + /// The number of concurrent writes for which to optimize by default. + /// </summary> + private static int DefaultConcurrencyLevel + { + + get { return DEFAULT_CONCURRENCY_MULTIPLIER * PlatformHelper.ProcessorCount; } + } + + /// <summary> + /// Acquires all locks for this hash table, and increments locksAcquired by the number + /// of locks that were successfully acquired. The locks are acquired in an increasing + /// order. + /// </summary> + private void AcquireAllLocks(ref int locksAcquired) + { +#if !FEATURE_CORECLR + if (CDSCollectionETWBCLProvider.Log.IsEnabled()) + { + CDSCollectionETWBCLProvider.Log.ConcurrentDictionary_AcquiringAllLocks(m_tables.m_buckets.Length); + } +#endif //!FEATURE_CORECLR + + // First, acquire lock 0 + AcquireLocks(0, 1, ref locksAcquired); + + // Now that we have lock 0, the m_locks array will not change (i.e., grow), + // and so we can safely read m_locks.Length. + AcquireLocks(1, m_tables.m_locks.Length, ref locksAcquired); + Assert(locksAcquired == m_tables.m_locks.Length); + } + + /// <summary> + /// Acquires a contiguous range of locks for this hash table, and increments locksAcquired + /// by the number of locks that were successfully acquired. The locks are acquired in an + /// increasing order. + /// </summary> + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) + { + Assert(fromInclusive <= toExclusive); + object[] locks = m_tables.m_locks; + + for (int i = fromInclusive; i < toExclusive; i++) + { + bool lockTaken = false; + try + { + Monitor.Enter(locks[i], ref lockTaken); + } + finally + { + if (lockTaken) + { + locksAcquired++; + } + } + } + } + + /// <summary> + /// Releases a contiguous range of locks. + /// </summary> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + private void ReleaseLocks(int fromInclusive, int toExclusive) + { + Assert(fromInclusive <= toExclusive); + + for (int i = fromInclusive; i < toExclusive; i++) + { + Monitor.Exit(m_tables.m_locks[i]); + } + } + + /// <summary> + /// Gets a collection containing the keys in the dictionary. + /// </summary> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + private ReadOnlyCollection<TKey> GetKeys() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + List<TKey> keys = new List<TKey>(); + + for (int i = 0; i < m_tables.m_buckets.Length; i++) + { + Node current = m_tables.m_buckets[i]; + while (current != null) + { + keys.Add(current.m_key); + current = current.m_next; + } + } + + return new ReadOnlyCollection<TKey>(keys); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// Gets a collection containing the values in the dictionary. + /// </summary> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")] + private ReadOnlyCollection<TValue> GetValues() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + List<TValue> values = new List<TValue>(); + + for (int i = 0; i < m_tables.m_buckets.Length; i++) + { + Node current = m_tables.m_buckets[i]; + while (current != null) + { + values.Add(current.m_value); + current = current.m_next; + } + } + + return new ReadOnlyCollection<TValue>(values); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// <summary> + /// A helper method for asserts. + /// </summary> + [Conditional("DEBUG")] + private void Assert(bool condition) + { + Contract.Assert(condition); + } + + /// <summary> + /// A helper function to obtain the string for a particular resource key. + /// </summary> + /// <param name="key"></param> + /// <returns></returns> + private string GetResource(string key) + { + Assert(key != null); + + return Environment.GetResourceString(key); + } + + /// <summary> + /// A node in a singly-linked list representing a particular hash table bucket. + /// </summary> + private class Node + { + internal TKey m_key; + internal TValue m_value; + internal volatile Node m_next; + internal int m_hashcode; + + internal Node(TKey key, TValue value, int hashcode, Node next) + { + m_key = key; + m_value = value; + m_next = next; + m_hashcode = hashcode; + } + } + + /// <summary> + /// A private class to represent enumeration over the dictionary that implements the + /// IDictionaryEnumerator interface. + /// </summary> + private class DictionaryEnumerator : IDictionaryEnumerator + { + IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; // Enumerator over the dictionary. + + internal DictionaryEnumerator(ConcurrentDictionary<TKey, TValue> dictionary) + { + m_enumerator = dictionary.GetEnumerator(); + } + + public DictionaryEntry Entry + { + get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); } + } + + public object Key + { + get { return m_enumerator.Current.Key; } + } + + public object Value + { + get { return m_enumerator.Current.Value; } + } + + public object Current + { + get { return this.Entry; } + } + + public bool MoveNext() + { + return m_enumerator.MoveNext(); + } + + public void Reset() + { + m_enumerator.Reset(); + } + } + +#if !FEATURE_CORECLR + /// <summary> + /// Get the data array to be serialized + /// </summary> + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + Tables tables = m_tables; + + // save the data into the serialization array to be saved + m_serializationArray = ToArray(); + m_serializationConcurrencyLevel = tables.m_locks.Length; + m_serializationCapacity = tables.m_buckets.Length; + m_comparer = (IEqualityComparer<TKey>)HashHelpers.GetEqualityComparerForSerialization(tables.m_comparer); + } + + /// <summary> + /// Construct the dictionary from a previously serialized one + /// </summary> + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + KeyValuePair<TKey, TValue>[] array = m_serializationArray; + + var buckets = new Node[m_serializationCapacity]; + var countPerLock = new int[m_serializationConcurrencyLevel]; + + var locks = new object[m_serializationConcurrencyLevel]; + for (int i = 0; i < locks.Length; i++) + { + locks[i] = new object(); + } + + m_tables = new Tables(buckets, locks, countPerLock, m_comparer); + + InitializeFromCollection(array); + m_serializationArray = null; + + } +#endif + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentQueue.cs b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentQueue.cs new file mode 100644 index 0000000000..9164eadad1 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentQueue.cs @@ -0,0 +1,960 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A lock-free, concurrent queue primitive, and its associated debugger view type. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; +using System.Security.Permissions; +using System.Threading; + +namespace System.Collections.Concurrent +{ + + /// <summary> + /// Represents a thread-safe first-in, first-out collection of objects. + /// </summary> + /// <typeparam name="T">Specifies the type of elements in the queue.</typeparam> + /// <remarks> + /// All public and protected members of <see cref="ConcurrentQueue{T}"/> are thread-safe and may be used + /// concurrently from multiple threads. + /// </remarks> + [ComVisible(false)] + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<>))] + [HostProtection(Synchronization = true, ExternalThreading = true)] + [Serializable] + public class ConcurrentQueue<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T> + { + //fields of ConcurrentQueue + [NonSerialized] + private volatile Segment m_head; + + [NonSerialized] + private volatile Segment m_tail; + + private T[] m_serializationArray; // Used for custom serialization. + + private const int SEGMENT_SIZE = 32; + + //number of snapshot takers, GetEnumerator(), ToList() and ToArray() operations take snapshot. + [NonSerialized] + internal volatile int m_numSnapshotTakers = 0; + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentQueue{T}"/> class. + /// </summary> + public ConcurrentQueue() + { + m_head = m_tail = new Segment(0, this); + } + + /// <summary> + /// Initializes the contents of the queue from an existing collection. + /// </summary> + /// <param name="collection">A collection from which to copy elements.</param> + private void InitializeFromCollection(IEnumerable<T> collection) + { + Segment localTail = new Segment(0, this);//use this local variable to avoid the extra volatile read/write. this is safe because it is only called from ctor + m_head = localTail; + + int index = 0; + foreach (T element in collection) + { + Contract.Assert(index >= 0 && index < SEGMENT_SIZE); + localTail.UnsafeAdd(element); + index++; + + if (index >= SEGMENT_SIZE) + { + localTail = localTail.UnsafeGrow(); + index = 0; + } + } + + m_tail = localTail; + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentQueue{T}"/> + /// class that contains elements copied from the specified collection + /// </summary> + /// <param name="collection">The collection whose elements are copied to the new <see + /// cref="ConcurrentQueue{T}"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="collection"/> argument is + /// null.</exception> + public ConcurrentQueue(IEnumerable<T> collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + InitializeFromCollection(collection); + } + + /// <summary> + /// Get the data array to be serialized + /// </summary> + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + // save the data into the serialization array to be saved + m_serializationArray = ToArray(); + } + + /// <summary> + /// Construct the queue from a previously seiralized one + /// </summary> + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + Contract.Assert(m_serializationArray != null); + InitializeFromCollection(m_serializationArray); + m_serializationArray = null; + } + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see + /// cref="T:System.Array"/>, starting at a particular + /// <see cref="T:System.Array"/> index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the + /// destination of the elements copied from the + /// <see cref="T:System.Collections.Concurrent.ConcurrentBag"/>. The <see + /// cref="T:System.Array">Array</see> must have zero-based indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in + /// Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// zero.</exception> + /// <exception cref="ArgumentException"> + /// <paramref name="array"/> is multidimensional. -or- + /// <paramref name="array"/> does not have zero-based indexing. -or- + /// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/> + /// -or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is + /// greater than the available space from <paramref name="index"/> to the end of the destination + /// <paramref name="array"/>. -or- The type of the source <see + /// cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the + /// destination <paramref name="array"/>. + /// </exception> + void ICollection.CopyTo(Array array, int index) + { + // Validate arguments. + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ((ICollection)ToList()).CopyTo(array, index); + } + + /// <summary> + /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is + /// synchronized with the SyncRoot. + /// </summary> + /// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized + /// with the SyncRoot; otherwise, false. For <see cref="ConcurrentQueue{T}"/>, this property always + /// returns false.</value> + bool ICollection.IsSynchronized + { + // Gets a value indicating whether access to this collection is synchronized. Always returns + // false. The reason is subtle. While access is in face thread safe, it's not the case that + // locking on the SyncRoot would have prevented concurrent pushes and pops, as this property + // would typically indicate; that's because we internally use CAS operations vs. true locks. + get { return false; } + } + + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see + /// cref="T:System.Collections.ICollection"/>. This property is not supported. + /// </summary> + /// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception> + object ICollection.SyncRoot + { + get + { + throw new NotSupportedException(Environment.GetResourceString("ConcurrentCollection_SyncRoot_NotSupported")); + } + } + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns>An <see cref="T:System.Collections.IEnumerator"/> that can be used to iterate through the collection.</returns> + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable<T>)this).GetEnumerator(); + } + + /// <summary> + /// Attempts to add an object to the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item">The object to add to the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. The value can be a null + /// reference (Nothing in Visual Basic) for reference types. + /// </param> + /// <returns>true if the object was added successfully; otherwise, false.</returns> + /// <remarks>For <see cref="ConcurrentQueue{T}"/>, this operation will always add the object to the + /// end of the <see cref="ConcurrentQueue{T}"/> + /// and return true.</remarks> + bool IProducerConsumerCollection<T>.TryAdd(T item) + { + Enqueue(item); + return true; + } + + /// <summary> + /// Attempts to remove and return an object from the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item"> + /// When this method returns, if the operation was successful, <paramref name="item"/> contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// </param> + /// <returns>true if an element was removed and returned succesfully; otherwise, false.</returns> + /// <remarks>For <see cref="ConcurrentQueue{T}"/>, this operation will attempt to remove the object + /// from the beginning of the <see cref="ConcurrentQueue{T}"/>. + /// </remarks> + bool IProducerConsumerCollection<T>.TryTake(out T item) + { + return TryDequeue(out item); + } + + /// <summary> + /// Gets a value that indicates whether the <see cref="ConcurrentQueue{T}"/> is empty. + /// </summary> + /// <value>true if the <see cref="ConcurrentQueue{T}"/> is empty; otherwise, false.</value> + /// <remarks> + /// For determining whether the collection contains any items, use of this property is recommended + /// rather than retrieving the number of items from the <see cref="Count"/> property and comparing it + /// to 0. However, as this collection is intended to be accessed concurrently, it may be the case + /// that another thread will modify the collection after <see cref="IsEmpty"/> returns, thus invalidating + /// the result. + /// </remarks> + public bool IsEmpty + { + get + { + Segment head = m_head; + if (!head.IsEmpty) + //fast route 1: + //if current head is not empty, then queue is not empty + return false; + else if (head.Next == null) + //fast route 2: + //if current head is empty and it's the last segment + //then queue is empty + return true; + else + //slow route: + //current head is empty and it is NOT the last segment, + //it means another thread is growing new segment + { + SpinWait spin = new SpinWait(); + while (head.IsEmpty) + { + if (head.Next == null) + return true; + + spin.SpinOnce(); + head = m_head; + } + return false; + } + } + } + + /// <summary> + /// Copies the elements stored in the <see cref="ConcurrentQueue{T}"/> to a new array. + /// </summary> + /// <returns>A new array containing a snapshot of elements copied from the <see + /// cref="ConcurrentQueue{T}"/>.</returns> + public T[] ToArray() + { + return ToList().ToArray(); + } + + /// <summary> + /// Copies the <see cref="ConcurrentQueue{T}"/> elements to a new <see + /// cref="T:System.Collections.Generic.List{T}"/>. + /// </summary> + /// <returns>A new <see cref="T:System.Collections.Generic.List{T}"/> containing a snapshot of + /// elements copied from the <see cref="ConcurrentQueue{T}"/>.</returns> + private List<T> ToList() + { + // Increments the number of active snapshot takers. This increment must happen before the snapshot is + // taken. At the same time, Decrement must happen after list copying is over. Only in this way, can it + // eliminate race condition when Segment.TryRemove() checks whether m_numSnapshotTakers == 0. + Interlocked.Increment(ref m_numSnapshotTakers); + + List<T> list = new List<T>(); + try + { + //store head and tail positions in buffer, + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + if (head == tail) + { + head.AddToList(list, headLow, tailHigh); + } + else + { + head.AddToList(list, headLow, SEGMENT_SIZE - 1); + Segment curr = head.Next; + while (curr != tail) + { + curr.AddToList(list, 0, SEGMENT_SIZE - 1); + curr = curr.Next; + } + //Add tail segment + tail.AddToList(list, 0, tailHigh); + } + } + finally + { + // This Decrement must happen after copying is over. + Interlocked.Decrement(ref m_numSnapshotTakers); + } + return list; + } + + /// <summary> + /// Store the position of the current head and tail positions. + /// </summary> + /// <param name="head">return the head segment</param> + /// <param name="tail">return the tail segment</param> + /// <param name="headLow">return the head offset, value range [0, SEGMENT_SIZE]</param> + /// <param name="tailHigh">return the tail offset, value range [-1, SEGMENT_SIZE-1]</param> + private void GetHeadTailPositions(out Segment head, out Segment tail, + out int headLow, out int tailHigh) + { + head = m_head; + tail = m_tail; + headLow = head.Low; + tailHigh = tail.High; + SpinWait spin = new SpinWait(); + + //we loop until the observed values are stable and sensible. + //This ensures that any update order by other methods can be tolerated. + while ( + //if head and tail changed, retry + head != m_head || tail != m_tail + //if low and high pointers, retry + || headLow != head.Low || tailHigh != tail.High + //if head jumps ahead of tail because of concurrent grow and dequeue, retry + || head.m_index > tail.m_index) + { + spin.SpinOnce(); + head = m_head; + tail = m_tail; + headLow = head.Low; + tailHigh = tail.High; + } + } + + + /// <summary> + /// Gets the number of elements contained in the <see cref="ConcurrentQueue{T}"/>. + /// </summary> + /// <value>The number of elements contained in the <see cref="ConcurrentQueue{T}"/>.</value> + /// <remarks> + /// For determining whether the collection contains any items, use of the <see cref="IsEmpty"/> + /// property is recommended rather than retrieving the number of items from the <see cref="Count"/> + /// property and comparing it to 0. + /// </remarks> + public int Count + { + get + { + //store head and tail positions in buffer, + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + if (head == tail) + { + return tailHigh - headLow + 1; + } + + //head segment + int count = SEGMENT_SIZE - headLow; + + //middle segment(s), if any, are full. + //We don't deal with overflow to be consistent with the behavior of generic types in CLR. + count += SEGMENT_SIZE * ((int)(tail.m_index - head.m_index - 1)); + + //tail segment + count += tailHigh + 1; + + return count; + } + } + + + /// <summary> + /// Copies the <see cref="ConcurrentQueue{T}"/> elements to an existing one-dimensional <see + /// cref="T:System.Array">Array</see>, starting at the specified array index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the + /// destination of the elements copied from the + /// <see cref="ConcurrentQueue{T}"/>. The <see cref="T:System.Array">Array</see> must have zero-based + /// indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in + /// Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// zero.</exception> + /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the + /// length of the <paramref name="array"/> + /// -or- The number of elements in the source <see cref="ConcurrentQueue{T}"/> is greater than the + /// available space from <paramref name="index"/> to the end of the destination <paramref + /// name="array"/>. + /// </exception> + public void CopyTo(T[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ToList().CopyTo(array, index); + } + + + /// <summary> + /// Returns an enumerator that iterates through the <see + /// cref="ConcurrentQueue{T}"/>. + /// </summary> + /// <returns>An enumerator for the contents of the <see + /// cref="ConcurrentQueue{T}"/>.</returns> + /// <remarks> + /// The enumeration represents a moment-in-time snapshot of the contents + /// of the queue. It does not reflect any updates to the collection after + /// <see cref="GetEnumerator"/> was called. The enumerator is safe to use + /// concurrently with reads from and writes to the queue. + /// </remarks> + public IEnumerator<T> GetEnumerator() + { + // Increments the number of active snapshot takers. This increment must happen before the snapshot is + // taken. At the same time, Decrement must happen after the enumeration is over. Only in this way, can it + // eliminate race condition when Segment.TryRemove() checks whether m_numSnapshotTakers == 0. + Interlocked.Increment(ref m_numSnapshotTakers); + + // Takes a snapshot of the queue. + // A design flaw here: if a Thread.Abort() happens, we cannot decrement m_numSnapshotTakers. But we cannot + // wrap the following with a try/finally block, otherwise the decrement will happen before the yield return + // statements in the GetEnumerator (head, tail, headLow, tailHigh) method. + Segment head, tail; + int headLow, tailHigh; + GetHeadTailPositions(out head, out tail, out headLow, out tailHigh); + + //If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of + // the queue is not taken when GetEnumerator is initialized but when MoveNext() is first called. + // This is inconsistent with existing generic collections. In order to prevent it, we capture the + // value of m_head in a buffer and call out to a helper method. + //The old way of doing this was to return the ToList().GetEnumerator(), but ToList() was an + // unnecessary perfomance hit. + return GetEnumerator(head, tail, headLow, tailHigh); + } + + /// <summary> + /// Helper method of GetEnumerator to seperate out yield return statement, and prevent lazy evaluation. + /// </summary> + private IEnumerator<T> GetEnumerator(Segment head, Segment tail, int headLow, int tailHigh) + { + try + { + SpinWait spin = new SpinWait(); + + if (head == tail) + { + for (int i = headLow; i <= tailHigh; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!head.m_state[i].m_value) + { + spin.SpinOnce(); + } + yield return head.m_array[i]; + } + } + else + { + //iterate on head segment + for (int i = headLow; i < SEGMENT_SIZE; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!head.m_state[i].m_value) + { + spin.SpinOnce(); + } + yield return head.m_array[i]; + } + //iterate on middle segments + Segment curr = head.Next; + while (curr != tail) + { + for (int i = 0; i < SEGMENT_SIZE; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!curr.m_state[i].m_value) + { + spin.SpinOnce(); + } + yield return curr.m_array[i]; + } + curr = curr.Next; + } + + //iterate on tail segment + for (int i = 0; i <= tailHigh; i++) + { + // If the position is reserved by an Enqueue operation, but the value is not written into, + // spin until the value is available. + spin.Reset(); + while (!tail.m_state[i].m_value) + { + spin.SpinOnce(); + } + yield return tail.m_array[i]; + } + } + } + finally + { + // This Decrement must happen after the enumeration is over. + Interlocked.Decrement(ref m_numSnapshotTakers); + } + } + + /// <summary> + /// Adds an object to the end of the <see cref="ConcurrentQueue{T}"/>. + /// </summary> + /// <param name="item">The object to add to the end of the <see + /// cref="ConcurrentQueue{T}"/>. The value can be a null reference + /// (Nothing in Visual Basic) for reference types. + /// </param> + public void Enqueue(T item) + { + SpinWait spin = new SpinWait(); + while (true) + { + Segment tail = m_tail; + if (tail.TryAppend(item)) + return; + spin.SpinOnce(); + } + } + + + /// <summary> + /// Attempts to remove and return the object at the beginning of the <see + /// cref="ConcurrentQueue{T}"/>. + /// </summary> + /// <param name="result"> + /// When this method returns, if the operation was successful, <paramref name="result"/> contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// </param> + /// <returns>true if an element was removed and returned from the beggining of the <see + /// cref="ConcurrentQueue{T}"/> + /// succesfully; otherwise, false.</returns> + public bool TryDequeue(out T result) + { + while (!IsEmpty) + { + Segment head = m_head; + if (head.TryRemove(out result)) + return true; + //since method IsEmpty spins, we don't need to spin in the while loop + } + result = default(T); + return false; + } + + /// <summary> + /// Attempts to return an object from the beginning of the <see cref="ConcurrentQueue{T}"/> + /// without removing it. + /// </summary> + /// <param name="result">When this method returns, <paramref name="result"/> contains an object from + /// the beginning of the <see cref="T:System.Collections.Concurrent.ConccurrentQueue{T}"/> or an + /// unspecified value if the operation failed.</param> + /// <returns>true if and object was returned successfully; otherwise, false.</returns> + public bool TryPeek(out T result) + { + Interlocked.Increment(ref m_numSnapshotTakers); + + while (!IsEmpty) + { + Segment head = m_head; + if (head.TryPeek(out result)) + { + Interlocked.Decrement(ref m_numSnapshotTakers); + return true; + } + //since method IsEmpty spins, we don't need to spin in the while loop + } + result = default(T); + Interlocked.Decrement(ref m_numSnapshotTakers); + return false; + } + + + /// <summary> + /// private class for ConcurrentQueue. + /// a queue is a linked list of small arrays, each node is called a segment. + /// A segment contains an array, a pointer to the next segment, and m_low, m_high indices recording + /// the first and last valid elements of the array. + /// </summary> + private class Segment + { + //we define two volatile arrays: m_array and m_state. Note that the accesses to the array items + //do not get volatile treatment. But we don't need to worry about loading adjacent elements or + //store/load on adjacent elements would suffer reordering. + // - Two stores: these are at risk, but CLRv2 memory model guarantees store-release hence we are safe. + // - Two loads: because one item from two volatile arrays are accessed, the loads of the array references + // are sufficient to prevent reordering of the loads of the elements. + internal volatile T[] m_array; + + // For each entry in m_array, the corresponding entry in m_state indicates whether this position contains + // a valid value. m_state is initially all false. + internal volatile VolatileBool[] m_state; + + //pointer to the next segment. null if the current segment is the last segment + private volatile Segment m_next; + + //We use this zero based index to track how many segments have been created for the queue, and + //to compute how many active segments are there currently. + // * The number of currently active segments is : m_tail.m_index - m_head.m_index + 1; + // * m_index is incremented with every Segment.Grow operation. We use Int64 type, and we can safely + // assume that it never overflows. To overflow, we need to do 2^63 increments, even at a rate of 4 + // billion (2^32) increments per second, it takes 2^31 seconds, which is about 64 years. + internal readonly long m_index; + + //indices of where the first and last valid values + // - m_low points to the position of the next element to pop from this segment, range [0, infinity) + // m_low >= SEGMENT_SIZE implies the segment is disposable + // - m_high points to the position of the latest pushed element, range [-1, infinity) + // m_high == -1 implies the segment is new and empty + // m_high >= SEGMENT_SIZE-1 means this segment is ready to grow. + // and the thread who sets m_high to SEGMENT_SIZE-1 is responsible to grow the segment + // - Math.Min(m_low, SEGMENT_SIZE) > Math.Min(m_high, SEGMENT_SIZE-1) implies segment is empty + // - initially m_low =0 and m_high=-1; + private volatile int m_low; + private volatile int m_high; + + private volatile ConcurrentQueue<T> m_source; + + /// <summary> + /// Create and initialize a segment with the specified index. + /// </summary> + internal Segment(long index, ConcurrentQueue<T> source) + { + m_array = new T[SEGMENT_SIZE]; + m_state = new VolatileBool[SEGMENT_SIZE]; //all initialized to false + m_high = -1; + Contract.Assert(index >= 0); + m_index = index; + m_source = source; + } + + /// <summary> + /// return the next segment + /// </summary> + internal Segment Next + { + get { return m_next; } + } + + + /// <summary> + /// return true if the current segment is empty (doesn't have any element available to dequeue, + /// false otherwise + /// </summary> + internal bool IsEmpty + { + get { return (Low > High); } + } + + /// <summary> + /// Add an element to the tail of the current segment + /// exclusively called by ConcurrentQueue.InitializedFromCollection + /// InitializeFromCollection is responsible to guaratee that there is no index overflow, + /// and there is no contention + /// </summary> + /// <param name="value"></param> + internal void UnsafeAdd(T value) + { + Contract.Assert(m_high < SEGMENT_SIZE - 1); + m_high++; + m_array[m_high] = value; + m_state[m_high].m_value = true; + } + + /// <summary> + /// Create a new segment and append to the current one + /// Does not update the m_tail pointer + /// exclusively called by ConcurrentQueue.InitializedFromCollection + /// InitializeFromCollection is responsible to guaratee that there is no index overflow, + /// and there is no contention + /// </summary> + /// <returns>the reference to the new Segment</returns> + internal Segment UnsafeGrow() + { + Contract.Assert(m_high >= SEGMENT_SIZE - 1); + Segment newSegment = new Segment(m_index + 1, m_source); //m_index is Int64, we don't need to worry about overflow + m_next = newSegment; + return newSegment; + } + + /// <summary> + /// Create a new segment and append to the current one + /// Update the m_tail pointer + /// This method is called when there is no contention + /// </summary> + internal void Grow() + { + //no CAS is needed, since there is no contention (other threads are blocked, busy waiting) + Segment newSegment = new Segment(m_index + 1, m_source); //m_index is Int64, we don't need to worry about overflow + m_next = newSegment; + Contract.Assert(m_source.m_tail == this); + m_source.m_tail = m_next; + } + + + /// <summary> + /// Try to append an element at the end of this segment. + /// </summary> + /// <param name="value">the element to append</param> + /// <param name="tail">The tail.</param> + /// <returns>true if the element is appended, false if the current segment is full</returns> + /// <remarks>if appending the specified element succeeds, and after which the segment is full, + /// then grow the segment</remarks> + internal bool TryAppend(T value) + { + //quickly check if m_high is already over the boundary, if so, bail out + if (m_high >= SEGMENT_SIZE - 1) + { + return false; + } + + //Now we will use a CAS to increment m_high, and store the result in newhigh. + //Depending on how many free spots left in this segment and how many threads are doing this Increment + //at this time, the returning "newhigh" can be + // 1) < SEGMENT_SIZE - 1 : we took a spot in this segment, and not the last one, just insert the value + // 2) == SEGMENT_SIZE - 1 : we took the last spot, insert the value AND grow the segment + // 3) > SEGMENT_SIZE - 1 : we failed to reserve a spot in this segment, we return false to + // Queue.Enqueue method, telling it to try again in the next segment. + + int newhigh = SEGMENT_SIZE; //initial value set to be over the boundary + + //We need do Interlocked.Increment and value/state update in a finally block to ensure that they run + //without interuption. This is to prevent anything from happening between them, and another dequeue + //thread maybe spinning forever to wait for m_state[] to be true; + try + { } + finally + { + newhigh = Interlocked.Increment(ref m_high); + if (newhigh <= SEGMENT_SIZE - 1) + { + m_array[newhigh] = value; + m_state[newhigh].m_value = true; + } + + //if this thread takes up the last slot in the segment, then this thread is responsible + //to grow a new segment. Calling Grow must be in the finally block too for reliability reason: + //if thread abort during Grow, other threads will be left busy spinning forever. + if (newhigh == SEGMENT_SIZE - 1) + { + Grow(); + } + } + + //if newhigh <= SEGMENT_SIZE-1, it means the current thread successfully takes up a spot + return newhigh <= SEGMENT_SIZE - 1; + } + + + /// <summary> + /// try to remove an element from the head of current segment + /// </summary> + /// <param name="result">The result.</param> + /// <param name="head">The head.</param> + /// <returns>return false only if the current segment is empty</returns> + internal bool TryRemove(out T result) + { + SpinWait spin = new SpinWait(); + int lowLocal = Low, highLocal = High; + while (lowLocal <= highLocal) + { + //try to update m_low + if (Interlocked.CompareExchange(ref m_low, lowLocal + 1, lowLocal) == lowLocal) + { + //if the specified value is not available (this spot is taken by a push operation, + // but the value is not written into yet), then spin + SpinWait spinLocal = new SpinWait(); + while (!m_state[lowLocal].m_value) + { + spinLocal.SpinOnce(); + } + result = m_array[lowLocal]; + + // If there is no other thread taking snapshot (GetEnumerator(), ToList(), etc), reset the deleted entry to null. + // It is ok if after this conditional check m_numSnapshotTakers becomes > 0, because new snapshots won't include + // the deleted entry at m_array[lowLocal]. + if (m_source.m_numSnapshotTakers <= 0) + { + m_array[lowLocal] = default(T); //release the reference to the object. + } + + //if the current thread sets m_low to SEGMENT_SIZE, which means the current segment becomes + //disposable, then this thread is responsible to dispose this segment, and reset m_head + if (lowLocal + 1 >= SEGMENT_SIZE) + { + // Invariant: we only dispose the current m_head, not any other segment + // In usual situation, disposing a segment is simply seting m_head to m_head.m_next + // But there is one special case, where m_head and m_tail points to the same and ONLY + //segment of the queue: Another thread A is doing Enqueue and finds that it needs to grow, + //while the *current* thread is doing *this* Dequeue operation, and finds that it needs to + //dispose the current (and ONLY) segment. Then we need to wait till thread A finishes its + //Grow operation, this is the reason of having the following while loop + spinLocal = new SpinWait(); + while (m_next == null) + { + spinLocal.SpinOnce(); + } + Contract.Assert(m_source.m_head == this); + m_source.m_head = m_next; + } + return true; + } + else + { + //CAS failed due to contention: spin briefly and retry + spin.SpinOnce(); + lowLocal = Low; highLocal = High; + } + }//end of while + result = default(T); + return false; + } + + /// <summary> + /// try to peek the current segment + /// </summary> + /// <param name="result">holds the return value of the element at the head position, + /// value set to default(T) if there is no such an element</param> + /// <returns>true if there are elements in the current segment, false otherwise</returns> + internal bool TryPeek(out T result) + { + result = default(T); + int lowLocal = Low; + if (lowLocal > High) + return false; + SpinWait spin = new SpinWait(); + while (!m_state[lowLocal].m_value) + { + spin.SpinOnce(); + } + result = m_array[lowLocal]; + return true; + } + + /// <summary> + /// Adds part or all of the current segment into a List. + /// </summary> + /// <param name="list">the list to which to add</param> + /// <param name="start">the start position</param> + /// <param name="end">the end position</param> + internal void AddToList(List<T> list, int start, int end) + { + for (int i = start; i <= end; i++) + { + SpinWait spin = new SpinWait(); + while (!m_state[i].m_value) + { + spin.SpinOnce(); + } + list.Add(m_array[i]); + } + } + + /// <summary> + /// return the position of the head of the current segment + /// Value range [0, SEGMENT_SIZE], if it's SEGMENT_SIZE, it means this segment is exhausted and thus empty + /// </summary> + internal int Low + { + get + { + return Math.Min(m_low, SEGMENT_SIZE); + } + } + + /// <summary> + /// return the logical position of the tail of the current segment + /// Value range [-1, SEGMENT_SIZE-1]. When it's -1, it means this is a new segment and has no elemnet yet + /// </summary> + internal int High + { + get + { + //if m_high > SEGMENT_SIZE, it means it's out of range, we should return + //SEGMENT_SIZE-1 as the logical position + return Math.Min(m_high, SEGMENT_SIZE - 1); + } + } + + } + }//end of class Segment + + /// <summary> + /// A wrapper struct for volatile bool, please note the copy of the struct it self will not be volatile + /// for example this statement will not include in volatilness operation volatileBool1 = volatileBool2 the jit will copy the struct and will ignore the volatile + /// </summary> + struct VolatileBool + { + public VolatileBool(bool value) + { + m_value = value; + } + public volatile bool m_value; + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentStack.cs b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentStack.cs new file mode 100644 index 0000000000..15d4176cff --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentStack.cs @@ -0,0 +1,840 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A lock-free, concurrent stack primitive, and its associated debugger view type. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Serialization; +using System.Security; +using System.Security.Permissions; +using System.Threading; + +namespace System.Collections.Concurrent +{ + // A stack that uses CAS operations internally to maintain thread-safety in a lock-free + // manner. Attempting to push or pop concurrently from the stack will not trigger waiting, + // although some optimistic concurrency and retry is used, possibly leading to lack of + // fairness and/or livelock. The stack uses spinning and backoff to add some randomization, + // in hopes of statistically decreasing the possibility of livelock. + // + // Note that we currently allocate a new node on every push. This avoids having to worry + // about potential ABA issues, since the CLR GC ensures that a memory address cannot be + // reused before all references to it have died. + + /// <summary> + /// Represents a thread-safe last-in, first-out collection of objects. + /// </summary> + /// <typeparam name="T">Specifies the type of elements in the stack.</typeparam> + /// <remarks> + /// All public and protected members of <see cref="ConcurrentStack{T}"/> are thread-safe and may be used + /// concurrently from multiple threads. + /// </remarks> + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<>))] + [HostProtection(Synchronization = true, ExternalThreading = true)] +#if !FEATURE_CORECLR + [Serializable] +#endif //!FEATURE_CORECLR + public class ConcurrentStack<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T> + { + /// <summary> + /// A simple (internal) node type used to store elements of concurrent stacks and queues. + /// </summary> + private class Node + { + internal readonly T m_value; // Value of the node. + internal Node m_next; // Next pointer. + + /// <summary> + /// Constructs a new node with the specified value and no next node. + /// </summary> + /// <param name="value">The value of the node.</param> + internal Node(T value) + { + m_value = value; + m_next = null; + } + } + +#if !FEATURE_CORECLR + [NonSerialized] +#endif //!FEATURE_CORECLR + private volatile Node m_head; // The stack is a singly linked list, and only remembers the head. + +#if !FEATURE_CORECLR + private T[] m_serializationArray; // Used for custom serialization. +#endif //!FEATURE_CORECLR + + private const int BACKOFF_MAX_YIELDS = 8; // Arbitrary number to cap backoff. + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentStack{T}"/> + /// class. + /// </summary> + public ConcurrentStack() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConcurrentStack{T}"/> + /// class that contains elements copied from the specified collection + /// </summary> + /// <param name="collection">The collection whose elements are copied to the new <see + /// cref="ConcurrentStack{T}"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="collection"/> argument is + /// null.</exception> + public ConcurrentStack(IEnumerable<T> collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + InitializeFromCollection(collection); + } + + /// <summary> + /// Initializes the contents of the stack from an existing collection. + /// </summary> + /// <param name="collection">A collection from which to copy elements.</param> + private void InitializeFromCollection(IEnumerable<T> collection) + { + // We just copy the contents of the collection to our stack. + Node lastNode = null; + foreach (T element in collection) + { + Node newNode = new Node(element); + newNode.m_next = lastNode; + lastNode = newNode; + } + + m_head = lastNode; + } + +#if !FEATURE_CORECLR + /// <summary> + /// Get the data array to be serialized + /// </summary> + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + // save the data into the serialization array to be saved + m_serializationArray = ToArray(); + } + + /// <summary> + /// Construct the stack from a previously seiralized one + /// </summary> + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + Contract.Assert(m_serializationArray != null); + // Add the elements to our stack. We need to add them from head-to-tail, to + // preserve the original ordering of the stack before serialization. + Node prevNode = null; + Node head = null; + for (int i = 0; i < m_serializationArray.Length; i++) + { + Node currNode = new Node(m_serializationArray[i]); + + if (prevNode == null) + { + head = currNode; + } + else + { + prevNode.m_next = currNode; + } + + prevNode = currNode; + } + + m_head = head; + m_serializationArray = null; + } +#endif //!FEATURE_CORECLR + + + /// <summary> + /// Gets a value that indicates whether the <see cref="ConcurrentStack{T}"/> is empty. + /// </summary> + /// <value>true if the <see cref="ConcurrentStack{T}"/> is empty; otherwise, false.</value> + /// <remarks> + /// For determining whether the collection contains any items, use of this property is recommended + /// rather than retrieving the number of items from the <see cref="Count"/> property and comparing it + /// to 0. However, as this collection is intended to be accessed concurrently, it may be the case + /// that another thread will modify the collection after <see cref="IsEmpty"/> returns, thus invalidating + /// the result. + /// </remarks> + public bool IsEmpty + { + // Checks whether the stack is empty. Clearly the answer may be out of date even prior to + // the function returning (i.e. if another thread concurrently adds to the stack). It does + // guarantee, however, that, if another thread does not mutate the stack, a subsequent call + // to TryPop will return true -- i.e. it will also read the stack as non-empty. + get { return m_head == null; } + } + + /// <summary> + /// Gets the number of elements contained in the <see cref="ConcurrentStack{T}"/>. + /// </summary> + /// <value>The number of elements contained in the <see cref="ConcurrentStack{T}"/>.</value> + /// <remarks> + /// For determining whether the collection contains any items, use of the <see cref="IsEmpty"/> + /// property is recommended rather than retrieving the number of items from the <see cref="Count"/> + /// property and comparing it to 0. + /// </remarks> + public int Count + { + // Counts the number of entries in the stack. This is an O(n) operation. The answer may be out + // of date before returning, but guarantees to return a count that was once valid. Conceptually, + // the implementation snaps a copy of the list and then counts the entries, though physically + // this is not what actually happens. + get + { + int count = 0; + + // Just whip through the list and tally up the number of nodes. We rely on the fact that + // node next pointers are immutable after being enqueued for the first time, even as + // they are being dequeued. If we ever changed this (e.g. to pool nodes somehow), + // we'd need to revisit this implementation. + + for (Node curr = m_head; curr != null; curr = curr.m_next) + { + count++; //we don't handle overflow, to be consistent with existing generic collection types in CLR + } + + return count; + } + } + + + /// <summary> + /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is + /// synchronized with the SyncRoot. + /// </summary> + /// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized + /// with the SyncRoot; otherwise, false. For <see cref="ConcurrentStack{T}"/>, this property always + /// returns false.</value> + bool ICollection.IsSynchronized + { + // Gets a value indicating whether access to this collection is synchronized. Always returns + // false. The reason is subtle. While access is in face thread safe, it's not the case that + // locking on the SyncRoot would have prevented concurrent pushes and pops, as this property + // would typically indicate; that's because we internally use CAS operations vs. true locks. + get { return false; } + } + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see + /// cref="T:System.Collections.ICollection"/>. This property is not supported. + /// </summary> + /// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported</exception> + object ICollection.SyncRoot + { + get + { + throw new NotSupportedException(Environment.GetResourceString("ConcurrentCollection_SyncRoot_NotSupported")); + } + } + + /// <summary> + /// Removes all objects from the <see cref="ConcurrentStack{T}"/>. + /// </summary> + public void Clear() + { + // Clear the list by setting the head to null. We don't need to use an atomic + // operation for this: anybody who is mutating the head by pushing or popping + // will need to use an atomic operation to guarantee they serialize and don't + // overwrite our setting of the head to null. + m_head = null; + } + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see + /// cref="T:System.Array"/>, starting at a particular + /// <see cref="T:System.Array"/> index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of + /// the elements copied from the + /// <see cref="ConcurrentStack{T}"/>. The <see cref="T:System.Array"/> must + /// have zero-based indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in + /// Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// zero.</exception> + /// <exception cref="ArgumentException"> + /// <paramref name="array"/> is multidimensional. -or- + /// <paramref name="array"/> does not have zero-based indexing. -or- + /// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/> + /// -or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is + /// greater than the available space from <paramref name="index"/> to the end of the destination + /// <paramref name="array"/>. -or- The type of the source <see + /// cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the + /// destination <paramref name="array"/>. + /// </exception> + void ICollection.CopyTo(Array array, int index) + { + // Validate arguments. + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ((ICollection)ToList()).CopyTo(array, index); + } + + /// <summary> + /// Copies the <see cref="ConcurrentStack{T}"/> elements to an existing one-dimensional <see + /// cref="T:System.Array"/>, starting at the specified array index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of + /// the elements copied from the + /// <see cref="ConcurrentStack{T}"/>. The <see cref="T:System.Array"/> must have zero-based + /// indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in + /// Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// zero.</exception> + /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the + /// length of the <paramref name="array"/> + /// -or- The number of elements in the source <see cref="ConcurrentStack{T}"/> is greater than the + /// available space from <paramref name="index"/> to the end of the destination <paramref + /// name="array"/>. + /// </exception> + public void CopyTo(T[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + // We must be careful not to corrupt the array, so we will first accumulate an + // internal list of elements that we will then copy to the array. This requires + // some extra allocation, but is necessary since we don't know up front whether + // the array is sufficiently large to hold the stack's contents. + ToList().CopyTo(array, index); + } + + + /// <summary> + /// Inserts an object at the top of the <see cref="ConcurrentStack{T}"/>. + /// </summary> + /// <param name="item">The object to push onto the <see cref="ConcurrentStack{T}"/>. The value can be + /// a null reference (Nothing in Visual Basic) for reference types. + /// </param> + public void Push(T item) + { + // Pushes a node onto the front of the stack thread-safely. Internally, this simply + // swaps the current head pointer using a (thread safe) CAS operation to accomplish + // lock freedom. If the CAS fails, we add some back off to statistically decrease + // contention at the head, and then go back around and retry. + + Node newNode = new Node(item); + newNode.m_next = m_head; + if (Interlocked.CompareExchange(ref m_head, newNode, newNode.m_next) == newNode.m_next) + { + return; + } + + // If we failed, go to the slow path and loop around until we succeed. + PushCore(newNode, newNode); + } + + /// <summary> + /// Inserts multiple objects at the top of the <see cref="ConcurrentStack{T}"/> atomically. + /// </summary> + /// <param name="items">The objects to push onto the <see cref="ConcurrentStack{T}"/>.</param> + /// <exception cref="ArgumentNullException"><paramref name="items"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <remarks> + /// When adding multiple items to the stack, using PushRange is a more efficient + /// mechanism than using <see cref="Push"/> one item at a time. Additionally, PushRange + /// guarantees that all of the elements will be added atomically, meaning that no other threads will + /// be able to inject elements between the elements being pushed. Items at lower indices in + /// the <paramref name="items"/> array will be pushed before items at higher indices. + /// </remarks> + public void PushRange(T[] items) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + PushRange(items, 0, items.Length); + } + + /// <summary> + /// Inserts multiple objects at the top of the <see cref="ConcurrentStack{T}"/> atomically. + /// </summary> + /// <param name="items">The objects to push onto the <see cref="ConcurrentStack{T}"/>.</param> + /// <param name="startIndex">The zero-based offset in <paramref name="items"/> at which to begin + /// inserting elements onto the top of the <see cref="ConcurrentStack{T}"/>.</param> + /// <param name="count">The number of elements to be inserted onto the top of the <see + /// cref="ConcurrentStack{T}"/>.</param> + /// <exception cref="ArgumentNullException"><paramref name="items"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref + /// name="count"/> is negative. Or <paramref name="startIndex"/> is greater than or equal to the length + /// of <paramref name="items"/>.</exception> + /// <exception cref="ArgumentException"><paramref name="startIndex"/> + <paramref name="count"/> is + /// greater than the length of <paramref name="items"/>.</exception> + /// <remarks> + /// When adding multiple items to the stack, using PushRange is a more efficient + /// mechanism than using <see cref="Push"/> one item at a time. Additionally, PushRange + /// guarantees that all of the elements will be added atomically, meaning that no other threads will + /// be able to inject elements between the elements being pushed. Items at lower indices in the + /// <paramref name="items"/> array will be pushed before items at higher indices. + /// </remarks> + public void PushRange(T[] items, int startIndex, int count) + { + ValidatePushPopRangeInput(items, startIndex, count); + + // No op if the count is zero + if (count == 0) + return; + + + Node head, tail; + head = tail = new Node(items[startIndex]); + for (int i = startIndex + 1; i < startIndex + count; i++) + { + Node node = new Node(items[i]); + node.m_next = head; + head = node; + } + + tail.m_next = m_head; + if (Interlocked.CompareExchange(ref m_head, head, tail.m_next) == tail.m_next) + { + return; + } + + // If we failed, go to the slow path and loop around until we succeed. + PushCore(head, tail); + + } + + + /// <summary> + /// Push one or many nodes into the stack, if head and tails are equal then push one node to the stack other wise push the list between head + /// and tail to the stack + /// </summary> + /// <param name="head">The head pointer to the new list</param> + /// <param name="tail">The tail pointer to the new list</param> + private void PushCore(Node head, Node tail) + { + SpinWait spin = new SpinWait(); + + // Keep trying to CAS the exising head with the new node until we succeed. + do + { + spin.SpinOnce(); + // Reread the head and link our new node. + tail.m_next = m_head; + } + while (Interlocked.CompareExchange( + ref m_head, head, tail.m_next) != tail.m_next); + +#if !FEATURE_CORECLR + if (CDSCollectionETWBCLProvider.Log.IsEnabled()) + { + CDSCollectionETWBCLProvider.Log.ConcurrentStack_FastPushFailed(spin.Count); + } +#endif // !FEATURE_CORECLR + } + + /// <summary> + /// Local helper function to validate the Pop Push range methods input + /// </summary> + private void ValidatePushPopRangeInput(T[] items, int startIndex, int count) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ConcurrentStack_PushPopRange_CountOutOfRange")); + } + int length = items.Length; + if (startIndex >= length || startIndex < 0) + { + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ConcurrentStack_PushPopRange_StartOutOfRange")); + } + if (length - count < startIndex) //instead of (startIndex + count > items.Length) to prevent overflow + { + throw new ArgumentException(Environment.GetResourceString("ConcurrentStack_PushPopRange_InvalidCount")); + } + } + + /// <summary> + /// Attempts to add an object to the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item">The object to add to the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. The value can be a null + /// reference (Nothing in Visual Basic) for reference types. + /// </param> + /// <returns>true if the object was added successfully; otherwise, false.</returns> + /// <remarks>For <see cref="ConcurrentStack{T}"/>, this operation + /// will always insert the object onto the top of the <see cref="ConcurrentStack{T}"/> + /// and return true.</remarks> + bool IProducerConsumerCollection<T>.TryAdd(T item) + { + Push(item); + return true; + } + + /// <summary> + /// Attempts to return an object from the top of the <see cref="ConcurrentStack{T}"/> + /// without removing it. + /// </summary> + /// <param name="result">When this method returns, <paramref name="result"/> contains an object from + /// the top of the <see cref="T:System.Collections.Concurrent.ConccurrentStack{T}"/> or an + /// unspecified value if the operation failed.</param> + /// <returns>true if and object was returned successfully; otherwise, false.</returns> + public bool TryPeek(out T result) + { + Node head = m_head; + + // If the stack is empty, return false; else return the element and true. + if (head == null) + { + result = default(T); + return false; + } + else + { + result = head.m_value; + return true; + } + } + + /// <summary> + /// Attempts to pop and return the object at the top of the <see cref="ConcurrentStack{T}"/>. + /// </summary> + /// <param name="result"> + /// When this method returns, if the operation was successful, <paramref name="result"/> contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// </param> + /// <returns>true if an element was removed and returned from the top of the <see + /// cref="ConcurrentStack{T}"/> + /// succesfully; otherwise, false.</returns> + public bool TryPop(out T result) + { + Node head = m_head; + //stack is empty + if (head == null) + { + result = default(T); + return false; + } + if (Interlocked.CompareExchange(ref m_head, head.m_next, head) == head) + { + result = head.m_value; + return true; + } + + // Fall through to the slow path. + return TryPopCore(out result); + } + + /// <summary> + /// Attempts to pop and return multiple objects from the top of the <see cref="ConcurrentStack{T}"/> + /// atomically. + /// </summary> + /// <param name="items"> + /// The <see cref="T:System.Array"/> to which objects popped from the top of the <see + /// cref="ConcurrentStack{T}"/> will be added. + /// </param> + /// <returns>The number of objects successfully popped from the top of the <see + /// cref="ConcurrentStack{T}"/> and inserted in + /// <paramref name="items"/>.</returns> + /// <exception cref="ArgumentNullException"><paramref name="items"/> is a null argument (Nothing + /// in Visual Basic).</exception> + /// <remarks> + /// When popping multiple items, if there is little contention on the stack, using + /// TryPopRange can be more efficient than using <see cref="TryPop"/> + /// once per item to be removed. Nodes fill the <paramref name="items"/> + /// with the first node to be popped at the startIndex, the second node to be popped + /// at startIndex + 1, and so on. + /// </remarks> + public int TryPopRange(T[] items) + { + if (items == null) + { + throw new ArgumentNullException("items"); + } + + return TryPopRange(items, 0, items.Length); + } + + /// <summary> + /// Attempts to pop and return multiple objects from the top of the <see cref="ConcurrentStack{T}"/> + /// atomically. + /// </summary> + /// <param name="items"> + /// The <see cref="T:System.Array"/> to which objects popped from the top of the <see + /// cref="ConcurrentStack{T}"/> will be added. + /// </param> + /// <param name="startIndex">The zero-based offset in <paramref name="items"/> at which to begin + /// inserting elements from the top of the <see cref="ConcurrentStack{T}"/>.</param> + /// <param name="count">The number of elements to be popped from top of the <see + /// cref="ConcurrentStack{T}"/> and inserted into <paramref name="items"/>.</param> + /// <returns>The number of objects successfully popped from the top of + /// the <see cref="ConcurrentStack{T}"/> and inserted in <paramref name="items"/>.</returns> + /// <exception cref="ArgumentNullException"><paramref name="items"/> is a null reference + /// (Nothing in Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref + /// name="count"/> is negative. Or <paramref name="startIndex"/> is greater than or equal to the length + /// of <paramref name="items"/>.</exception> + /// <exception cref="ArgumentException"><paramref name="startIndex"/> + <paramref name="count"/> is + /// greater than the length of <paramref name="items"/>.</exception> + /// <remarks> + /// When popping multiple items, if there is little contention on the stack, using + /// TryPopRange can be more efficient than using <see cref="TryPop"/> + /// once per item to be removed. Nodes fill the <paramref name="items"/> + /// with the first node to be popped at the startIndex, the second node to be popped + /// at startIndex + 1, and so on. + /// </remarks> + public int TryPopRange(T[] items, int startIndex, int count) + { + ValidatePushPopRangeInput(items, startIndex, count); + + // No op if the count is zero + if (count == 0) + return 0; + + Node poppedHead; + int nodesCount = TryPopCore(count, out poppedHead); + if (nodesCount > 0) + { + CopyRemovedItems(poppedHead, items, startIndex, nodesCount); + + } + return nodesCount; + + } + + /// <summary> + /// Local helper function to Pop an item from the stack, slow path + /// </summary> + /// <param name="result">The popped item</param> + /// <returns>True if succeeded, false otherwise</returns> + private bool TryPopCore(out T result) + { + Node poppedNode; + + if (TryPopCore(1, out poppedNode) == 1) + { + result = poppedNode.m_value; + return true; + } + + result = default(T); + return false; + + } + + /// <summary> + /// Slow path helper for TryPop. This method assumes an initial attempt to pop an element + /// has already occurred and failed, so it begins spinning right away. + /// </summary> + /// <param name="count">The number of items to pop.</param> + /// <param name="poppedHead"> + /// When this method returns, if the pop succeeded, contains the removed object. If no object was + /// available to be removed, the value is unspecified. This parameter is passed uninitialized. + /// </param> + /// <returns>True if an element was removed and returned; otherwise, false.</returns> + private int TryPopCore(int count, out Node poppedHead) + { + SpinWait spin = new SpinWait(); + + // Try to CAS the head with its current next. We stop when we succeed or + // when we notice that the stack is empty, whichever comes first. + Node head; + Node next; + int backoff = 1; + Random r = new Random(Environment.TickCount & Int32.MaxValue); // avoid the case where TickCount could return Int32.MinValue + while (true) + { + head = m_head; + // Is the stack empty? + if (head == null) + { +#if !FEATURE_CORECLR + if (count == 1 && CDSCollectionETWBCLProvider.Log.IsEnabled()) + { + CDSCollectionETWBCLProvider.Log.ConcurrentStack_FastPopFailed(spin.Count); + } +#endif //!FEATURE_CORECLR + poppedHead = null; + return 0; + } + next = head; + int nodesCount = 1; + for (; nodesCount < count && next.m_next != null; nodesCount++) + { + next = next.m_next; + } + + // Try to swap the new head. If we succeed, break out of the loop. + if (Interlocked.CompareExchange(ref m_head, next.m_next, head) == head) + { +#if !FEATURE_CORECLR + if (count == 1 && CDSCollectionETWBCLProvider.Log.IsEnabled()) + { + CDSCollectionETWBCLProvider.Log.ConcurrentStack_FastPopFailed(spin.Count); + } +#endif //!FEATURE_CORECLR + // Return the popped Node. + poppedHead = head; + return nodesCount; + } + + // We failed to CAS the new head. Spin briefly and retry. + for (int i = 0; i < backoff; i++) + { + spin.SpinOnce(); + } + + backoff = spin.NextSpinWillYield ? r.Next(1, BACKOFF_MAX_YIELDS) : backoff * 2; + } + } + + + /// <summary> + /// Local helper function to copy the poped elements into a given collection + /// </summary> + /// <param name="head">The head of the list to be copied</param> + /// <param name="collection">The collection to place the popped items in</param> + /// <param name="startIndex">the beginning of index of where to place the popped items</param> + /// <param name="nodesCount">The number of nodes.</param> + private void CopyRemovedItems(Node head, T[] collection, int startIndex, int nodesCount) + { + Node current = head; + for (int i = startIndex; i < startIndex + nodesCount; i++) + { + collection[i] = current.m_value; + current = current.m_next; + } + + } + + /// <summary> + /// Attempts to remove and return an object from the <see + /// cref="T:System.Collections.Concurrent.IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item"> + /// When this method returns, if the operation was successful, <paramref name="item"/> contains the + /// object removed. If no object was available to be removed, the value is unspecified. + /// </param> + /// <returns>true if an element was removed and returned succesfully; otherwise, false.</returns> + /// <remarks>For <see cref="ConcurrentStack{T}"/>, this operation will attempt to pope the object at + /// the top of the <see cref="ConcurrentStack{T}"/>. + /// </remarks> + bool IProducerConsumerCollection<T>.TryTake(out T item) + { + return TryPop(out item); + } + + /// <summary> + /// Copies the items stored in the <see cref="ConcurrentStack{T}"/> to a new array. + /// </summary> + /// <returns>A new array containing a snapshot of elements copied from the <see + /// cref="ConcurrentStack{T}"/>.</returns> + public T[] ToArray() + { + return ToList().ToArray(); + } + + /// <summary> + /// Returns an array containing a snapshot of the list's contents, using + /// the target list node as the head of a region in the list. + /// </summary> + /// <returns>An array of the list's contents.</returns> + private List<T> ToList() + { + List<T> list = new List<T>(); + + Node curr = m_head; + while (curr != null) + { + list.Add(curr.m_value); + curr = curr.m_next; + } + + return list; + } + + /// <summary> + /// Returns an enumerator that iterates through the <see cref="ConcurrentStack{T}"/>. + /// </summary> + /// <returns>An enumerator for the <see cref="ConcurrentStack{T}"/>.</returns> + /// <remarks> + /// The enumeration represents a moment-in-time snapshot of the contents + /// of the stack. It does not reflect any updates to the collection after + /// <see cref="GetEnumerator"/> was called. The enumerator is safe to use + /// concurrently with reads from and writes to the stack. + /// </remarks> + public IEnumerator<T> GetEnumerator() + { + // Returns an enumerator for the stack. This effectively takes a snapshot + // of the stack's contents at the time of the call, i.e. subsequent modifications + // (pushes or pops) will not be reflected in the enumerator's contents. + + //If we put yield-return here, the iterator will be lazily evaluated. As a result a snapshot of + //the stack is not taken when GetEnumerator is initialized but when MoveNext() is first called. + //This is inconsistent with existing generic collections. In order to prevent it, we capture the + //value of m_head in a buffer and call out to a helper method + return GetEnumerator(m_head); + } + + private IEnumerator<T> GetEnumerator(Node head) + { + Node current = head; + while (current != null) + { + yield return current.m_value; + current = current.m_next; + } + } + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns>An <see cref="T:System.Collections.IEnumerator"/> that can be used to iterate through + /// the collection.</returns> + /// <remarks> + /// The enumeration represents a moment-in-time snapshot of the contents of the stack. It does not + /// reflect any updates to the collection after + /// <see cref="GetEnumerator"/> was called. The enumerator is safe to use concurrently with reads + /// from and writes to the stack. + /// </remarks> + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable<T>)this).GetEnumerator(); + } + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/IProducerConsumerCollection.cs b/src/mscorlib/src/System/Collections/Concurrent/IProducerConsumerCollection.cs new file mode 100644 index 0000000000..a74f69069a --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/IProducerConsumerCollection.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A common interface for all concurrent collections. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Collections.Concurrent +{ + + /// <summary> + /// Defines methods to manipulate thread-safe collections intended for producer/consumer usage. + /// </summary> + /// <typeparam name="T">Specifies the type of elements in the collection.</typeparam> + /// <remarks> + /// All implementations of this interface must enable all members of this interface + /// to be used concurrently from multiple threads. + /// </remarks> + public interface IProducerConsumerCollection<T> : IEnumerable<T>, ICollection + { + + /// <summary> + /// Copies the elements of the <see cref="IProducerConsumerCollection{T}"/> to + /// an + /// <see cref="T:System.Array"/>, starting at a specified index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of + /// the elements copied from the <see cref="IProducerConsumerCollection{T}"/>. + /// The array must have zero-based indexing.</param> + /// <param name="index">The zero-based index in <paramref name="array"/> at which copying + /// begins.</param> + /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in + /// Visual Basic).</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than + /// zero.</exception> + /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the + /// length of the <paramref name="array"/> + /// -or- The number of elements in the source <see cref="ConcurrentQueue{T}"/> is greater than the + /// available space from <paramref name="index"/> to the end of the destination <paramref + /// name="array"/>. + /// </exception> + void CopyTo(T[] array, int index); + + /// <summary> + /// Attempts to add an object to the <see + /// cref="IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item">The object to add to the <see + /// cref="IProducerConsumerCollection{T}"/>.</param> + /// <returns>true if the object was added successfully; otherwise, false.</returns> + /// <exception cref="T:System.ArgumentException">The <paramref name="item"/> was invalid for this collection.</exception> + bool TryAdd(T item); + + /// <summary> + /// Attempts to remove and return an object from the <see cref="IProducerConsumerCollection{T}"/>. + /// </summary> + /// <param name="item"> + /// When this method returns, if the object was removed and returned successfully, <paramref + /// name="item"/> contains the removed object. If no object was available to be removed, the value is + /// unspecified. + /// </param> + /// <returns>true if an object was removed and returned successfully; otherwise, false.</returns> + bool TryTake(out T item); + + /// <summary> + /// Copies the elements contained in the <see cref="IProducerConsumerCollection{T}"/> to a new array. + /// </summary> + /// <returns>A new array containing the elements copied from the <see cref="IProducerConsumerCollection{T}"/>.</returns> + T[] ToArray(); + + } + + + /// <summary> + /// A debugger view of the IProducerConsumerCollection that makes it simple to browse the + /// collection's contents at a point in time. + /// </summary> + /// <typeparam name="T">The type of elements stored within.</typeparam> + internal sealed class SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView<T> + { + private IProducerConsumerCollection<T> m_collection; // The collection being viewed. + + /// <summary> + /// Constructs a new debugger view object for the provided collection object. + /// </summary> + /// <param name="collection">A collection to browse in the debugger.</param> + public SystemCollectionsConcurrent_ProducerConsumerCollectionDebugView(IProducerConsumerCollection<T> collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + m_collection = collection; + } + + /// <summary> + /// Returns a snapshot of the underlying collection's elements. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get { return m_collection.ToArray(); } + } + + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/OrderablePartitioner.cs b/src/mscorlib/src/System/Collections/Concurrent/OrderablePartitioner.cs new file mode 100644 index 0000000000..02263b7f97 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/OrderablePartitioner.cs @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Security.Permissions; +using System.Threading; + +namespace System.Collections.Concurrent +{ + + /// <summary> + /// Represents a particular manner of splitting an orderable data source into multiple partitions. + /// </summary> + /// <typeparam name="TSource">Type of the elements in the collection.</typeparam> + /// <remarks> + /// <para> + /// Each element in each partition has an integer index associated with it, which determines the relative + /// order of that element against elements in other partitions. + /// </para> + /// <para> + /// Inheritors of <see cref="OrderablePartitioner{TSource}"/> must adhere to the following rules: + /// <ol> + /// <li>All indices must be unique, such that there may not be duplicate indices. If all indices are not + /// unique, the output ordering may be scrambled.</li> + /// <li>All indices must be non-negative. If any indices are negative, consumers of the implementation + /// may throw exceptions.</li> + /// <li><see cref="GetPartitions"/> and <see cref="GetOrderablePartitions"/> should throw a + /// <see cref="T:System.ArgumentOutOfRangeException"/> if the requested partition count is less than or + /// equal to zero.</li> + /// <li><see cref="GetPartitions"/> and <see cref="GetOrderablePartitions"/> should always return a number + /// of enumerables equal to the requested partition count. If the partitioner runs out of data and cannot + /// create as many partitions as requested, an empty enumerator should be returned for each of the + /// remaining partitions. If this rule is not followed, consumers of the implementation may throw a <see + /// cref="T:System.InvalidOperationException"/>.</li> + /// <li><see cref="GetPartitions"/>, <see cref="GetOrderablePartitions"/>, + /// <see cref="GetDynamicPartitions"/>, and <see cref="GetOrderableDynamicPartitions"/> + /// should never return null. If null is returned, a consumer of the implementation may throw a + /// <see cref="T:System.InvalidOperationException"/>.</li> + /// <li><see cref="GetPartitions"/>, <see cref="GetOrderablePartitions"/>, + /// <see cref="GetDynamicPartitions"/>, and <see cref="GetOrderableDynamicPartitions"/> + /// should always return partitions that can fully and uniquely enumerate the input data source. All of + /// the data and only the data contained in the input source should be enumerated, with no duplication + /// that was not already in the input, unless specifically required by the particular partitioner's + /// design. If this is not followed, the output ordering may be scrambled.</li> + /// <li>If <see cref="KeysOrderedInEachPartition"/> returns true, each partition must return elements + /// with increasing key indices.</li> + /// <li>If <see cref="KeysOrderedAcrossPartitions"/> returns true, all the keys in partition numbered N + /// must be larger than all the keys in partition numbered N-1.</li> + /// <li>If <see cref="KeysNormalized"/> returns true, all indices must be monotonically increasing from + /// 0, though not necessarily within a single partition.</li> + /// </ol> + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public abstract class OrderablePartitioner<TSource> : Partitioner<TSource> + { + /// <summary> + /// Initializes a new instance of the <see cref="OrderablePartitioner{TSource}"/> class with the + /// specified constraints on the index keys. + /// </summary> + /// <param name="keysOrderedInEachPartition"> + /// Indicates whether the elements in each partition are yielded in the order of + /// increasing keys. + /// </param> + /// <param name="keysOrderedAcrossPartitions"> + /// Indicates whether elements in an earlier partition always come before + /// elements in a later partition. If true, each element in partition 0 has a smaller order key than + /// any element in partition 1, each element in partition 1 has a smaller order key than any element + /// in partition 2, and so on. + /// </param> + /// <param name="keysNormalized"> + /// Indicates whether keys are normalized. If true, all order keys are distinct + /// integers in the range [0 .. numberOfElements-1]. If false, order keys must still be dictinct, but + /// only their relative order is considered, not their absolute values. + /// </param> + protected OrderablePartitioner(bool keysOrderedInEachPartition, bool keysOrderedAcrossPartitions, bool keysNormalized) + { + KeysOrderedInEachPartition = keysOrderedInEachPartition; + KeysOrderedAcrossPartitions = keysOrderedAcrossPartitions; + KeysNormalized = keysNormalized; + } + + /// <summary> + /// Partitions the underlying collection into the specified number of orderable partitions. + /// </summary> + /// <remarks> + /// Each partition is represented as an enumerator over key-value pairs. + /// The value of the pair is the element itself, and the key is an integer which determines + /// the relative ordering of this element against other elements in the data source. + /// </remarks> + /// <param name="partitionCount">The number of partitions to create.</param> + /// <returns>A list containing <paramref name="partitionCount"/> enumerators.</returns> + public abstract IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int partitionCount); + + /// <summary> + /// Creates an object that can partition the underlying collection into a variable number of + /// partitions. + /// </summary> + /// <remarks> + /// <para> + /// The returned object implements the <see + /// cref="T:System.Collections.Generic.IEnumerable{TSource}"/> interface. Calling <see + /// cref="System.Collections.Generic.IEnumerable{TSource}.GetEnumerator">GetEnumerator</see> on the + /// object creates another partition over the sequence. + /// </para> + /// <para> + /// Each partition is represented as an enumerator over key-value pairs. The value in the pair is the element + /// itself, and the key is an integer which determines the relative ordering of this element against + /// other elements. + /// </para> + /// <para> + /// The <see cref="GetOrderableDynamicPartitions"/> method is only supported if the <see + /// cref="System.Collections.Concurrent.Partitioner{TSource}.SupportsDynamicPartitions">SupportsDynamicPartitions</see> + /// property returns true. + /// </para> + /// </remarks> + /// <returns>An object that can create partitions over the underlying data source.</returns> + /// <exception cref="NotSupportedException">Dynamic partitioning is not supported by this + /// partitioner.</exception> + public virtual IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions() + { + throw new NotSupportedException(Environment.GetResourceString("Partitioner_DynamicPartitionsNotSupported")); + } + + /// <summary> + /// Gets whether elements in each partition are yielded in the order of increasing keys. + /// </summary> + public bool KeysOrderedInEachPartition { get; private set; } + + /// <summary> + /// Gets whether elements in an earlier partition always come before elements in a later partition. + /// </summary> + /// <remarks> + /// If <see cref="KeysOrderedAcrossPartitions"/> returns true, each element in partition 0 has a + /// smaller order key than any element in partition 1, each element in partition 1 has a smaller + /// order key than any element in partition 2, and so on. + /// </remarks> + public bool KeysOrderedAcrossPartitions { get; private set; } + + /// <summary> + /// Gets whether order keys are normalized. + /// </summary> + /// <remarks> + /// If <see cref="KeysNormalized"/> returns true, all order keys are distinct integers in the range + /// [0 .. numberOfElements-1]. If the property returns false, order keys must still be dictinct, but + /// only their relative order is considered, not their absolute values. + /// </remarks> + public bool KeysNormalized { get; private set; } + + /// <summary> + /// Partitions the underlying collection into the given number of ordered partitions. + /// </summary> + /// <remarks> + /// The default implementation provides the same behavior as <see cref="GetOrderablePartitions"/> except + /// that the returned set of partitions does not provide the keys for the elements. + /// </remarks> + /// <param name="partitionCount">The number of partitions to create.</param> + /// <returns>A list containing <paramref name="partitionCount"/> enumerators.</returns> + public override IList<IEnumerator<TSource>> GetPartitions(int partitionCount) + { + IList<IEnumerator<KeyValuePair<long, TSource>>> orderablePartitions = GetOrderablePartitions(partitionCount); + + if (orderablePartitions.Count != partitionCount) + { + throw new InvalidOperationException("OrderablePartitioner_GetPartitions_WrongNumberOfPartitions"); + } + + IEnumerator<TSource>[] partitions = new IEnumerator<TSource>[partitionCount]; + for (int i = 0; i < partitionCount; i++) + { + partitions[i] = new EnumeratorDropIndices(orderablePartitions[i]); + } + return partitions; + } + + /// <summary> + /// Creates an object that can partition the underlying collection into a variable number of + /// partitions. + /// </summary> + /// <remarks> + /// <para> + /// The returned object implements the <see + /// cref="T:System.Collections.Generic.IEnumerable{TSource}"/> interface. Calling <see + /// cref="System.Collections.Generic.IEnumerable{TSource}.GetEnumerator">GetEnumerator</see> on the + /// object creates another partition over the sequence. + /// </para> + /// <para> + /// The default implementation provides the same behavior as <see cref="GetOrderableDynamicPartitions"/> except + /// that the returned set of partitions does not provide the keys for the elements. + /// </para> + /// <para> + /// The <see cref="GetDynamicPartitions"/> method is only supported if the <see + /// cref="System.Collections.Concurrent.Partitioner{TSource}.SupportsDynamicPartitions"/> + /// property returns true. + /// </para> + /// </remarks> + /// <returns>An object that can create partitions over the underlying data source.</returns> + /// <exception cref="NotSupportedException">Dynamic partitioning is not supported by this + /// partitioner.</exception> + public override IEnumerable<TSource> GetDynamicPartitions() + { + IEnumerable<KeyValuePair<long, TSource>> orderablePartitions = GetOrderableDynamicPartitions(); + return new EnumerableDropIndices(orderablePartitions); + } + + /// <summary> + /// Converts an enumerable over key-value pairs to an enumerable over values. + /// </summary> + private class EnumerableDropIndices : IEnumerable<TSource>, IDisposable + { + private readonly IEnumerable<KeyValuePair<long, TSource>> m_source; + public EnumerableDropIndices(IEnumerable<KeyValuePair<long, TSource>> source) + { + m_source = source; + } + public IEnumerator<TSource> GetEnumerator() + { + return new EnumeratorDropIndices(m_source.GetEnumerator()); + } + IEnumerator IEnumerable.GetEnumerator() + { + return ((EnumerableDropIndices)this).GetEnumerator(); + } + public void Dispose() + { + IDisposable d = m_source as IDisposable; + if (d != null) + { + d.Dispose(); + } + } + } + + private class EnumeratorDropIndices : IEnumerator<TSource> + { + private readonly IEnumerator<KeyValuePair<long, TSource>> m_source; + public EnumeratorDropIndices(IEnumerator<KeyValuePair<long, TSource>> source) + { + m_source = source; + } + public bool MoveNext() + { + return m_source.MoveNext(); + } + public TSource Current + { + get + { + return m_source.Current.Value; + } + } + Object IEnumerator.Current + { + get + { + return ((EnumeratorDropIndices)this).Current; + } + } + public void Dispose() + { + m_source.Dispose(); + } + public void Reset() + { + m_source.Reset(); + } + } + + } + +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/Partitioner.cs b/src/mscorlib/src/System/Collections/Concurrent/Partitioner.cs new file mode 100644 index 0000000000..3d54c1471b --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/Partitioner.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Represents a particular way of splitting a collection into multiple partitions. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Security.Permissions; +using System.Threading; + +namespace System.Collections.Concurrent +{ + /// <summary> + /// Represents a particular manner of splitting a data source into multiple partitions. + /// </summary> + /// <typeparam name="TSource">Type of the elements in the collection.</typeparam> + /// <remarks> + /// <para> + /// Inheritors of <see cref="Partitioner{TSource}"/> must adhere to the following rules: + /// <ol> + /// <li><see cref="GetPartitions"/> should throw a + /// <see cref="T:System.ArgumentOutOfRangeException"/> if the requested partition count is less than or + /// equal to zero.</li> + /// <li><see cref="GetPartitions"/> should always return a number of enumerables equal to the requested + /// partition count. If the partitioner runs out of data and cannot create as many partitions as + /// requested, an empty enumerator should be returned for each of the remaining partitions. If this rule + /// is not followed, consumers of the implementation may throw a <see + /// cref="T:System.InvalidOperationException"/>.</li> + /// <li><see cref="GetPartitions"/> and <see cref="GetDynamicPartitions"/> + /// should never return null. If null is returned, a consumer of the implementation may throw a + /// <see cref="T:System.InvalidOperationException"/>.</li> + /// <li><see cref="GetPartitions"/> and <see cref="GetDynamicPartitions"/> should always return + /// partitions that can fully and uniquely enumerate the input data source. All of the data and only the + /// data contained in the input source should be enumerated, with no duplication that was not already in + /// the input, unless specifically required by the particular partitioner's design. If this is not + /// followed, the output ordering may be scrambled.</li> + /// </ol> + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public abstract class Partitioner<TSource> + { + /// <summary> + /// Partitions the underlying collection into the given number of partitions. + /// </summary> + /// <param name="partitionCount">The number of partitions to create.</param> + /// <returns>A list containing <paramref name="partitionCount"/> enumerators.</returns> + public abstract IList<IEnumerator<TSource>> GetPartitions(int partitionCount); + + /// <summary> + /// Gets whether additional partitions can be created dynamically. + /// </summary> + /// <returns> + /// true if the <see cref="Partitioner{TSource}"/> can create partitions dynamically as they are + /// requested; false if the <see cref="Partitioner{TSource}"/> can only allocate + /// partitions statically. + /// </returns> + /// <remarks> + /// <para> + /// If a derived class does not override and implement <see cref="GetDynamicPartitions"/>, + /// <see cref="SupportsDynamicPartitions"/> should return false. The value of <see + /// cref="SupportsDynamicPartitions"/> should not vary over the lifetime of this instance. + /// </para> + /// </remarks> + public virtual bool SupportsDynamicPartitions + { + get { return false; } + } + + /// <summary> + /// Creates an object that can partition the underlying collection into a variable number of + /// partitions. + /// </summary> + /// <remarks> + /// <para> + /// The returned object implements the <see + /// cref="T:System.Collections.Generic.IEnumerable{TSource}"/> interface. Calling <see + /// cref="System.Collections.Generic.IEnumerable{TSource}.GetEnumerator">GetEnumerator</see> on the + /// object creates another partition over the sequence. + /// </para> + /// <para> + /// The <see cref="GetDynamicPartitions"/> method is only supported if the <see + /// cref="SupportsDynamicPartitions"/> + /// property returns true. + /// </para> + /// </remarks> + /// <returns>An object that can create partitions over the underlying data source.</returns> + /// <exception cref="NotSupportedException">Dynamic partitioning is not supported by this + /// partitioner.</exception> + public virtual IEnumerable<TSource> GetDynamicPartitions() + { + throw new NotSupportedException(Environment.GetResourceString("Partitioner_DynamicPartitionsNotSupported")); + } + } +} diff --git a/src/mscorlib/src/System/Collections/Concurrent/PartitionerStatic.cs b/src/mscorlib/src/System/Collections/Concurrent/PartitionerStatic.cs new file mode 100644 index 0000000000..2169c6dee7 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Concurrent/PartitionerStatic.cs @@ -0,0 +1,1733 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A class of default partitioners for Partitioner<TSource> +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Generic; +using System.Security.Permissions; +using System.Threading; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace System.Collections.Concurrent +{ + /// <summary> + /// Out-of-the-box partitioners are created with a set of default behaviors. + /// For example, by default, some form of buffering and chunking will be employed to achieve + /// optimal performance in the common scenario where an IEnumerable<> implementation is fast and + /// non-blocking. These behaviors can be overridden via this enumeration. + /// </summary> + [Flags] +#if !FEATURE_CORECLR + [Serializable] +#endif + public enum EnumerablePartitionerOptions + { + /// <summary> + /// Use the default behavior (i.e., use buffering to achieve optimal performance) + /// </summary> + None = 0x0, + + /// <summary> + /// Creates a partitioner that will take items from the source enumerable one at a time + /// and will not use intermediate storage that can be accessed more efficiently by multiple threads. + /// This option provides support for low latency (items will be processed as soon as they are available from + /// the source) and partial support for dependencies between items (a thread cannot deadlock waiting for an item + /// that it, itself, is responsible for processing). + /// </summary> + NoBuffering = 0x1 + } + + // The static class Partitioners implements 3 default partitioning strategies: + // 1. dynamic load balance partitioning for indexable data source (IList and arrays) + // 2. static partitioning for indexable data source (IList and arrays) + // 3. dynamic load balance partitioning for enumerables. Enumerables have indexes, which are the natural order + // of elements, but enuemrators are not indexable + // - data source of type IList/arrays have both dynamic and static partitioning, as 1 and 3. + // We assume that the source data of IList/Array is not changing concurrently. + // - data source of type IEnumerable can only be partitioned dynamically (load-balance) + // - Dynamic partitioning methods 1 and 3 are same, both being dynamic and load-balance. But the + // implementation is different for data source of IList/Array vs. IEnumerable: + // * When the source collection is IList/Arrays, we use Interlocked on the shared index; + // * When the source collection is IEnumerable, we use Monitor to wrap around the access to the source + // enumerator. + + /// <summary> + /// Provides common partitioning strategies for arrays, lists, and enumerables. + /// </summary> + /// <remarks> + /// <para> + /// The static methods on <see cref="Partitioner"/> are all thread-safe and may be used concurrently + /// from multiple threads. However, while a created partitioner is in use, the underlying data source + /// should not be modified, whether from the same thread that's using a partitioner or from a separate + /// thread. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public static class Partitioner + { + /// <summary> + /// Creates an orderable partitioner from an <see cref="System.Collections.Generic.IList{T}"/> + /// instance. + /// </summary> + /// <typeparam name="TSource">Type of the elements in source list.</typeparam> + /// <param name="list">The list to be partitioned.</param> + /// <param name="loadBalance"> + /// A Boolean value that indicates whether the created partitioner should dynamically + /// load balance between partitions rather than statically partition. + /// </param> + /// <returns> + /// An orderable partitioner based on the input list. + /// </returns> + public static OrderablePartitioner<TSource> Create<TSource>(IList<TSource> list, bool loadBalance) + { + if (list == null) + { + throw new ArgumentNullException("list"); + } + if (loadBalance) + { + return (new DynamicPartitionerForIList<TSource>(list)); + } + else + { + return (new StaticIndexRangePartitionerForIList<TSource>(list)); + } + } + + /// <summary> + /// Creates an orderable partitioner from a <see cref="System.Array"/> instance. + /// </summary> + /// <typeparam name="TSource">Type of the elements in source array.</typeparam> + /// <param name="array">The array to be partitioned.</param> + /// <param name="loadBalance"> + /// A Boolean value that indicates whether the created partitioner should dynamically load balance + /// between partitions rather than statically partition. + /// </param> + /// <returns> + /// An orderable partitioner based on the input array. + /// </returns> + public static OrderablePartitioner<TSource> Create<TSource>(TSource[] array, bool loadBalance) + { + // This implementation uses 'ldelem' instructions for element retrieval, rather than using a + // method call. + + if (array == null) + { + throw new ArgumentNullException("array"); + } + if (loadBalance) + { + return (new DynamicPartitionerForArray<TSource>(array)); + } + else + { + return (new StaticIndexRangePartitionerForArray<TSource>(array)); + } + } + + /// <summary> + /// Creates an orderable partitioner from a <see cref="System.Collections.Generic.IEnumerable{TSource}"/> instance. + /// </summary> + /// <typeparam name="TSource">Type of the elements in source enumerable.</typeparam> + /// <param name="source">The enumerable to be partitioned.</param> + /// <returns> + /// An orderable partitioner based on the input array. + /// </returns> + /// <remarks> + /// The ordering used in the created partitioner is determined by the natural order of the elements + /// as retrieved from the source enumerable. + /// </remarks> + public static OrderablePartitioner<TSource> Create<TSource>(IEnumerable<TSource> source) + { + return Create<TSource>(source, EnumerablePartitionerOptions.None); + } + + /// <summary> + /// Creates an orderable partitioner from a <see cref="System.Collections.Generic.IEnumerable{TSource}"/> instance. + /// </summary> + /// <typeparam name="TSource">Type of the elements in source enumerable.</typeparam> + /// <param name="source">The enumerable to be partitioned.</param> + /// <param name="partitionerOptions">Options to control the buffering behavior of the partitioner.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="partitionerOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Collections.Concurrent.EnumerablePartitionerOptions"/>. + /// </exception> + /// <returns> + /// An orderable partitioner based on the input array. + /// </returns> + /// <remarks> + /// The ordering used in the created partitioner is determined by the natural order of the elements + /// as retrieved from the source enumerable. + /// </remarks> + public static OrderablePartitioner<TSource> Create<TSource>(IEnumerable<TSource> source, EnumerablePartitionerOptions partitionerOptions) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if ((partitionerOptions & (~EnumerablePartitionerOptions.NoBuffering)) != 0) + throw new ArgumentOutOfRangeException("partitionerOptions"); + + return (new DynamicPartitionerForIEnumerable<TSource>(source, partitionerOptions)); + } + + /// <summary>Creates a partitioner that chunks the user-specified range.</summary> + /// <param name="fromInclusive">The lower, inclusive bound of the range.</param> + /// <param name="toExclusive">The upper, exclusive bound of the range.</param> + /// <returns>A partitioner.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="toExclusive"/> argument is + /// less than or equal to the <paramref name="fromInclusive"/> argument.</exception> + public static OrderablePartitioner<Tuple<long, long>> Create(long fromInclusive, long toExclusive) + { + // How many chunks do we want to divide the range into? If this is 1, then the + // answer is "one chunk per core". Generally, though, you'll achieve better + // load balancing on a busy system if you make it higher than 1. + int coreOversubscriptionRate = 3; + + if (toExclusive <= fromInclusive) throw new ArgumentOutOfRangeException("toExclusive"); + long rangeSize = (toExclusive - fromInclusive) / + (PlatformHelper.ProcessorCount * coreOversubscriptionRate); + if (rangeSize == 0) rangeSize = 1; + return Partitioner.Create(CreateRanges(fromInclusive, toExclusive, rangeSize), EnumerablePartitionerOptions.NoBuffering); // chunk one range at a time + } + + /// <summary>Creates a partitioner that chunks the user-specified range.</summary> + /// <param name="fromInclusive">The lower, inclusive bound of the range.</param> + /// <param name="toExclusive">The upper, exclusive bound of the range.</param> + /// <param name="rangeSize">The size of each subrange.</param> + /// <returns>A partitioner.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="toExclusive"/> argument is + /// less than or equal to the <paramref name="fromInclusive"/> argument.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="rangeSize"/> argument is + /// less than or equal to 0.</exception> + public static OrderablePartitioner<Tuple<long, long>> Create(long fromInclusive, long toExclusive, long rangeSize) + { + if (toExclusive <= fromInclusive) throw new ArgumentOutOfRangeException("toExclusive"); + if (rangeSize <= 0) throw new ArgumentOutOfRangeException("rangeSize"); + return Partitioner.Create(CreateRanges(fromInclusive, toExclusive, rangeSize), EnumerablePartitionerOptions.NoBuffering); // chunk one range at a time + } + + // Private method to parcel out range tuples. + private static IEnumerable<Tuple<long, long>> CreateRanges(long fromInclusive, long toExclusive, long rangeSize) + { + // Enumerate all of the ranges + long from, to; + bool shouldQuit = false; + + for (long i = fromInclusive; (i < toExclusive) && !shouldQuit; i += rangeSize) + { + from = i; + try { checked { to = i + rangeSize; } } + catch (OverflowException) + { + to = toExclusive; + shouldQuit = true; + } + if (to > toExclusive) to = toExclusive; + yield return new Tuple<long, long>(from, to); + } + } + + /// <summary>Creates a partitioner that chunks the user-specified range.</summary> + /// <param name="fromInclusive">The lower, inclusive bound of the range.</param> + /// <param name="toExclusive">The upper, exclusive bound of the range.</param> + /// <returns>A partitioner.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="toExclusive"/> argument is + /// less than or equal to the <paramref name="fromInclusive"/> argument.</exception> + public static OrderablePartitioner<Tuple<int, int>> Create(int fromInclusive, int toExclusive) + { + // How many chunks do we want to divide the range into? If this is 1, then the + // answer is "one chunk per core". Generally, though, you'll achieve better + // load balancing on a busy system if you make it higher than 1. + int coreOversubscriptionRate = 3; + + if (toExclusive <= fromInclusive) throw new ArgumentOutOfRangeException("toExclusive"); + int rangeSize = (toExclusive - fromInclusive) / + (PlatformHelper.ProcessorCount * coreOversubscriptionRate); + if (rangeSize == 0) rangeSize = 1; + return Partitioner.Create(CreateRanges(fromInclusive, toExclusive, rangeSize), EnumerablePartitionerOptions.NoBuffering); // chunk one range at a time + } + + /// <summary>Creates a partitioner that chunks the user-specified range.</summary> + /// <param name="fromInclusive">The lower, inclusive bound of the range.</param> + /// <param name="toExclusive">The upper, exclusive bound of the range.</param> + /// <param name="rangeSize">The size of each subrange.</param> + /// <returns>A partitioner.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="toExclusive"/> argument is + /// less than or equal to the <paramref name="fromInclusive"/> argument.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> The <paramref name="rangeSize"/> argument is + /// less than or equal to 0.</exception> + public static OrderablePartitioner<Tuple<int, int>> Create(int fromInclusive, int toExclusive, int rangeSize) + { + if (toExclusive <= fromInclusive) throw new ArgumentOutOfRangeException("toExclusive"); + if (rangeSize <= 0) throw new ArgumentOutOfRangeException("rangeSize"); + return Partitioner.Create(CreateRanges(fromInclusive, toExclusive, rangeSize), EnumerablePartitionerOptions.NoBuffering); // chunk one range at a time + } + + // Private method to parcel out range tuples. + private static IEnumerable<Tuple<int, int>> CreateRanges(int fromInclusive, int toExclusive, int rangeSize) + { + // Enumerate all of the ranges + int from, to; + bool shouldQuit = false; + + for (int i = fromInclusive; (i < toExclusive) && !shouldQuit; i += rangeSize) + { + from = i; + try { checked { to = i + rangeSize; } } + catch (OverflowException) + { + to = toExclusive; + shouldQuit = true; + } + if (to > toExclusive) to = toExclusive; + yield return new Tuple<int, int>(from, to); + } + } + + #region DynamicPartitionEnumerator_Abstract class + /// <summary> + /// DynamicPartitionEnumerator_Abstract defines the enumerator for each partition for the dynamic load-balance + /// partitioning algorithm. + /// - Partition is an enumerator of KeyValuePairs, each corresponding to an item in the data source: + /// the key is the index in the source collection; the value is the item itself. + /// - a set of such partitions share a reader over data source. The type of the reader is specified by + /// TSourceReader. + /// - each partition requests a contiguous chunk of elements at a time from the source data. The chunk + /// size is initially 1, and doubles every time until it reaches the maximum chunk size. + /// The implementation for GrabNextChunk() method has two versions: one for data source of IndexRange + /// types (IList and the array), one for data source of IEnumerable. + /// - The method "Reset" is not supported for any partitioning algorithm. + /// - The implementation for MoveNext() method is same for all dynanmic partitioners, so we provide it + /// in this abstract class. + /// </summary> + /// <typeparam name="TSource">Type of the elements in the data source</typeparam> + /// <typeparam name="TSourceReader">Type of the reader on the data source</typeparam> + //TSourceReader is + // - IList<TSource>, when source data is IList<TSource>, the shared reader is source data itself + // - TSource[], when source data is TSource[], the shared reader is source data itself + // - IEnumerator<TSource>, when source data is IEnumerable<TSource>, and the shared reader is an + // enumerator of the source data + private abstract class DynamicPartitionEnumerator_Abstract<TSource, TSourceReader> : IEnumerator<KeyValuePair<long, TSource>> + { + //----------------- common fields and constructor for all dynamic partitioners ----------------- + //--- shared by all dervied class with souce data type: IList, Array, and IEnumerator + protected readonly TSourceReader m_sharedReader; + + protected static int s_defaultMaxChunkSize = GetDefaultChunkSize<TSource>(); + + //deferred allocating in MoveNext() with initial value 0, to avoid false sharing + //we also use the fact that: (m_currentChunkSize==null) means MoveNext is never called on this enumerator + protected SharedInt m_currentChunkSize; + + //deferring allocation in MoveNext() with initial value -1, to avoid false sharing + protected SharedInt m_localOffset; + + private const int CHUNK_DOUBLING_RATE = 3; // Double the chunk size every this many grabs + private int m_doublingCountdown; // Number of grabs remaining until chunk size doubles + protected readonly int m_maxChunkSize; // s_defaultMaxChunkSize unless single-chunking is requested by the caller + + // m_sharedIndex shared by this set of partitions, and particularly when m_sharedReader is IEnuerable + // it serves as tracking of the natual order of elements in m_sharedReader + // the value of this field is passed in from outside (already initialized) by the constructor, + protected readonly SharedLong m_sharedIndex; + + protected DynamicPartitionEnumerator_Abstract(TSourceReader sharedReader, SharedLong sharedIndex) + : this(sharedReader, sharedIndex, false) + { + } + + protected DynamicPartitionEnumerator_Abstract(TSourceReader sharedReader, SharedLong sharedIndex, bool useSingleChunking) + { + m_sharedReader = sharedReader; + m_sharedIndex = sharedIndex; + m_maxChunkSize = useSingleChunking ? 1 : s_defaultMaxChunkSize; + } + + // ---------------- abstract method declarations -------------- + + /// <summary> + /// Abstract method to request a contiguous chunk of elements from the source collection + /// </summary> + /// <param name="requestedChunkSize">specified number of elements requested</param> + /// <returns> + /// true if we successfully reserved at least one element (up to #=requestedChunkSize) + /// false if all elements in the source collection have been reserved. + /// </returns> + //GrabNextChunk does the following: + // - grab # of requestedChunkSize elements from source data through shared reader, + // - at the time of function returns, m_currentChunkSize is updated with the number of + // elements actually got assigned (<=requestedChunkSize). + // - GrabNextChunk returns true if at least one element is assigned to this partition; + // false if the shared reader already hits the last element of the source data before + // we call GrabNextChunk + protected abstract bool GrabNextChunk(int requestedChunkSize); + + /// <summary> + /// Abstract property, returns whether or not the shared reader has already read the last + /// element of the source data + /// </summary> + protected abstract bool HasNoElementsLeft { get; set; } + + /// <summary> + /// Get the current element in the current partition. Property required by IEnumerator interface + /// This property is abstract because the implementation is different depending on the type + /// of the source data: IList, Array or IEnumerable + /// </summary> + public abstract KeyValuePair<long, TSource> Current { get; } + + /// <summary> + /// Dispose is abstract, and depends on the type of the source data: + /// - For source data type IList and Array, the type of the shared reader is just the dataitself. + /// We don't do anything in Dispose method for IList and Array. + /// - For source data type IEnumerable, the type of the shared reader is an enumerator we created. + /// Thus we need to dispose this shared reader enumerator, when there is no more active partitions + /// left. + /// </summary> + public abstract void Dispose(); + + /// <summary> + /// Reset on partitions is not supported + /// </summary> + public void Reset() + { + throw new NotSupportedException(); + } + + + /// <summary> + /// Get the current element in the current partition. Property required by IEnumerator interface + /// </summary> + Object IEnumerator.Current + { + get + { + return ((DynamicPartitionEnumerator_Abstract<TSource, TSourceReader>)this).Current; + } + } + + /// <summary> + /// Moves to the next element if any. + /// Try current chunk first, if the current chunk do not have any elements left, then we + /// attempt to grab a chunk from the source collection. + /// </summary> + /// <returns> + /// true if successfully moving to the next position; + /// false otherwise, if and only if there is no more elements left in the current chunk + /// AND the source collection is exhausted. + /// </returns> + public bool MoveNext() + { + //perform deferred allocating of the local variables. + if (m_localOffset == null) + { + Contract.Assert(m_currentChunkSize == null); + m_localOffset = new SharedInt(-1); + m_currentChunkSize = new SharedInt(0); + m_doublingCountdown = CHUNK_DOUBLING_RATE; + } + + if (m_localOffset.Value < m_currentChunkSize.Value - 1) + //attempt to grab the next element from the local chunk + { + m_localOffset.Value++; + return true; + } + else + //otherwise it means we exhausted the local chunk + //grab a new chunk from the source enumerator + { + // The second part of the || condition is necessary to handle the case when MoveNext() is called + // after a previous MoveNext call returned false. + Contract.Assert(m_localOffset.Value == m_currentChunkSize.Value - 1 || m_currentChunkSize.Value == 0); + + //set the requested chunk size to a proper value + int requestedChunkSize; + if (m_currentChunkSize.Value == 0) //first time grabbing from source enumerator + { + requestedChunkSize = 1; + } + else if (m_doublingCountdown > 0) + { + requestedChunkSize = m_currentChunkSize.Value; + } + else + { + requestedChunkSize = Math.Min(m_currentChunkSize.Value * 2, m_maxChunkSize); + m_doublingCountdown = CHUNK_DOUBLING_RATE; // reset + } + + // Decrement your doubling countdown + m_doublingCountdown--; + + Contract.Assert(requestedChunkSize > 0 && requestedChunkSize <= m_maxChunkSize); + //GrabNextChunk will update the value of m_currentChunkSize + if (GrabNextChunk(requestedChunkSize)) + { + Contract.Assert(m_currentChunkSize.Value <= requestedChunkSize && m_currentChunkSize.Value > 0); + m_localOffset.Value = 0; + return true; + } + else + { + return false; + } + } + } + } + #endregion + + #region Dynamic Partitioner for source data of IEnuemrable<> type + /// <summary> + /// Inherits from DynamicPartitioners + /// Provides customized implementation of GetOrderableDynamicPartitions_Factory method, to return an instance + /// of EnumerableOfPartitionsForIEnumerator defined internally + /// </summary> + /// <typeparam name="TSource">Type of elements in the source data</typeparam> + private class DynamicPartitionerForIEnumerable<TSource> : OrderablePartitioner<TSource> + { + IEnumerable<TSource> m_source; + readonly bool m_useSingleChunking; + + //constructor + internal DynamicPartitionerForIEnumerable(IEnumerable<TSource> source, EnumerablePartitionerOptions partitionerOptions) + : base(true, false, true) + { + m_source = source; + m_useSingleChunking = ((partitionerOptions & EnumerablePartitionerOptions.NoBuffering) != 0); + } + + /// <summary> + /// Overrides OrderablePartitioner.GetOrderablePartitions. + /// Partitions the underlying collection into the given number of orderable partitions. + /// </summary> + /// <param name="partitionCount">number of partitions requested</param> + /// <returns>A list containing <paramref name="partitionCount"/> enumerators.</returns> + override public IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int partitionCount) + { + if (partitionCount <= 0) + { + throw new ArgumentOutOfRangeException("partitionCount"); + } + IEnumerator<KeyValuePair<long, TSource>>[] partitions + = new IEnumerator<KeyValuePair<long, TSource>>[partitionCount]; + + IEnumerable<KeyValuePair<long, TSource>> partitionEnumerable = new InternalPartitionEnumerable(m_source.GetEnumerator(), m_useSingleChunking, true); + for (int i = 0; i < partitionCount; i++) + { + partitions[i] = partitionEnumerable.GetEnumerator(); + } + return partitions; + } + + /// <summary> + /// Overrides OrderablePartitioner.GetOrderableDyanmicPartitions + /// </summary> + /// <returns>a enumerable collection of orderable partitions</returns> + override public IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions() + { + return new InternalPartitionEnumerable(m_source.GetEnumerator(), m_useSingleChunking, false); + } + + /// <summary> + /// Whether additional partitions can be created dynamically. + /// </summary> + override public bool SupportsDynamicPartitions + { + get { return true; } + } + + #region Internal classes: InternalPartitionEnumerable, InternalPartitionEnumerator + /// <summary> + /// Provides customized implementation for source data of IEnumerable + /// Different from the counterpart for IList/Array, this enumerable maintains several additional fields + /// shared by the partitions it owns, including a boolean "m_hasNoElementsLef", a shared lock, and a + /// shared count "m_activePartitionCount" used to track active partitions when they were created statically + /// </summary> + private class InternalPartitionEnumerable : IEnumerable<KeyValuePair<long, TSource>>, IDisposable + { + //reader through which we access the source data + private readonly IEnumerator<TSource> m_sharedReader; + private SharedLong m_sharedIndex;//initial value -1 + + private volatile KeyValuePair<long, TSource>[] m_FillBuffer; // intermediate buffer to reduce locking + private volatile int m_FillBufferSize; // actual number of elements in m_FillBuffer. Will start + // at m_FillBuffer.Length, and might be reduced during the last refill + private volatile int m_FillBufferCurrentPosition; //shared value to be accessed by Interlock.Increment only + private volatile int m_activeCopiers; //number of active copiers + + //fields shared by all partitions that this Enumerable owns, their allocation is deferred + private SharedBool m_hasNoElementsLeft; // no elements left at all. + private SharedBool m_sourceDepleted; // no elements left in the enumerator, but there may be elements in the Fill Buffer + + //shared synchronization lock, created by this Enumerable + private object m_sharedLock;//deferring allocation by enumerator + + private bool m_disposed; + + // If dynamic partitioning, then m_activePartitionCount == null + // If static partitioning, then it keeps track of active partition count + private SharedInt m_activePartitionCount; + + // records whether or not the user has requested single-chunking behavior + private readonly bool m_useSingleChunking; + + internal InternalPartitionEnumerable(IEnumerator<TSource> sharedReader, bool useSingleChunking, bool isStaticPartitioning) + { + m_sharedReader = sharedReader; + m_sharedIndex = new SharedLong(-1); + m_hasNoElementsLeft = new SharedBool(false); + m_sourceDepleted = new SharedBool(false); + m_sharedLock = new object(); + m_useSingleChunking = useSingleChunking; + + // Only allocate the fill-buffer if single-chunking is not in effect + if (!m_useSingleChunking) + { + // Time to allocate the fill buffer which is used to reduce the contention on the shared lock. + // First pick the buffer size multiplier. We use 4 for when there are more than 4 cores, and just 1 for below. This is based on empirical evidence. + int fillBufferMultiplier = (PlatformHelper.ProcessorCount > 4) ? 4 : 1; + + // and allocate the fill buffer using these two numbers + m_FillBuffer = new KeyValuePair<long, TSource>[fillBufferMultiplier * Partitioner.GetDefaultChunkSize<TSource>()]; + } + + if (isStaticPartitioning) + { + // If this object is created for static partitioning (ie. via GetPartitions(int partitionCount), + // GetOrderablePartitions(int partitionCount)), we track the active partitions, in order to dispose + // this object when all the partitions have been disposed. + m_activePartitionCount = new SharedInt(0); + } + else + { + // Otherwise this object is created for dynamic partitioning (ie, via GetDynamicPartitions(), + // GetOrderableDynamicPartitions()), we do not need tracking. This object must be disposed + // explicitly + m_activePartitionCount = null; + } + } + + public IEnumerator<KeyValuePair<long, TSource>> GetEnumerator() + { + if (m_disposed) + { + throw new ObjectDisposedException(Environment.GetResourceString("PartitionerStatic_CanNotCallGetEnumeratorAfterSourceHasBeenDisposed")); + } + else + { + return new InternalPartitionEnumerator(m_sharedReader, m_sharedIndex, + m_hasNoElementsLeft, m_sharedLock, m_activePartitionCount, this, m_useSingleChunking); + } + } + + + IEnumerator IEnumerable.GetEnumerator() + { + return ((InternalPartitionEnumerable)this).GetEnumerator(); + } + + + /////////////////// + // + // Used by GrabChunk_Buffered() + private void TryCopyFromFillBuffer(KeyValuePair<long, TSource>[] destArray, + int requestedChunkSize, + ref int actualNumElementsGrabbed) + { + actualNumElementsGrabbed = 0; + + + // making a local defensive copy of the fill buffer reference, just in case it gets nulled out + KeyValuePair<long, TSource>[] fillBufferLocalRef = m_FillBuffer; + if (fillBufferLocalRef == null) return; + + // first do a quick check, and give up if the current position is at the end + // so that we don't do an unncessary pair of Interlocked.Increment / Decrement calls + if (m_FillBufferCurrentPosition >= m_FillBufferSize) + { + return; // no elements in the buffer to copy from + } + + // We might have a chance to grab elements from the buffer. We will know for sure + // when we do the Interlocked.Add below. + // But first we must register as a potential copier in order to make sure + // the elements we're copying from don't get overwritten by another thread + // that starts refilling the buffer right after our Interlocked.Add. + Interlocked.Increment(ref m_activeCopiers); + + int endPos = Interlocked.Add(ref m_FillBufferCurrentPosition, requestedChunkSize); + int beginPos = endPos - requestedChunkSize; + + if (beginPos < m_FillBufferSize) + { + // adjust index and do the actual copy + actualNumElementsGrabbed = (endPos < m_FillBufferSize) ? endPos : m_FillBufferSize - beginPos; + Array.Copy(fillBufferLocalRef, beginPos, destArray, 0, actualNumElementsGrabbed); + } + + // let the record show we are no longer accessing the buffer + Interlocked.Decrement(ref m_activeCopiers); + } + + /// <summary> + /// This is the common entry point for consuming items from the source enumerable + /// </summary> + /// <returns> + /// true if we successfully reserved at least one element + /// false if all elements in the source collection have been reserved. + /// </returns> + internal bool GrabChunk(KeyValuePair<long, TSource>[] destArray, int requestedChunkSize, ref int actualNumElementsGrabbed) + { + actualNumElementsGrabbed = 0; + + if (m_hasNoElementsLeft.Value) + { + return false; + } + + if (m_useSingleChunking) + { + return GrabChunk_Single(destArray, requestedChunkSize, ref actualNumElementsGrabbed); + } + else + { + return GrabChunk_Buffered(destArray, requestedChunkSize, ref actualNumElementsGrabbed); + } + } + + /// <summary> + /// Version of GrabChunk that grabs a single element at a time from the source enumerable + /// </summary> + /// <returns> + /// true if we successfully reserved an element + /// false if all elements in the source collection have been reserved. + /// </returns> + internal bool GrabChunk_Single(KeyValuePair<long,TSource>[] destArray, int requestedChunkSize, ref int actualNumElementsGrabbed) + { + Contract.Assert(m_useSingleChunking, "Expected m_useSingleChecking to be true"); + Contract.Assert(requestedChunkSize == 1, "Got requested chunk size of " + requestedChunkSize + " when single-chunking was on"); + Contract.Assert(actualNumElementsGrabbed == 0, "Expected actualNumElementsGrabbed == 0, instead it is " + actualNumElementsGrabbed); + Contract.Assert(destArray.Length == 1, "Expected destArray to be of length 1, instead its length is " + destArray.Length); + + lock (m_sharedLock) + { + if (m_hasNoElementsLeft.Value) return false; + + try + { + if (m_sharedReader.MoveNext()) + { + m_sharedIndex.Value = checked(m_sharedIndex.Value + 1); + destArray[0] + = new KeyValuePair<long, TSource>(m_sharedIndex.Value, + m_sharedReader.Current); + actualNumElementsGrabbed = 1; + return true; + } + else + { + //if MoveNext() return false, we set the flag to inform other partitions + m_sourceDepleted.Value = true; + m_hasNoElementsLeft.Value = true; + return false; + } + } + catch + { + // On an exception, make sure that no additional items are hereafter enumerated + m_sourceDepleted.Value = true; + m_hasNoElementsLeft.Value = true; + throw; + } + } + } + + + + /// <summary> + /// Version of GrabChunk that uses buffering scheme to grab items out of source enumerable + /// </summary> + /// <returns> + /// true if we successfully reserved at least one element (up to #=requestedChunkSize) + /// false if all elements in the source collection have been reserved. + /// </returns> + internal bool GrabChunk_Buffered(KeyValuePair<long,TSource>[] destArray, int requestedChunkSize, ref int actualNumElementsGrabbed) + { + Contract.Assert(requestedChunkSize > 0); + Contract.Assert(!m_useSingleChunking, "Did not expect to be in single-chunking mode"); + + TryCopyFromFillBuffer(destArray, requestedChunkSize, ref actualNumElementsGrabbed); + + if (actualNumElementsGrabbed == requestedChunkSize) + { + // that was easy. + return true; + } + else if (m_sourceDepleted.Value) + { + // looks like we both reached the end of the fill buffer, and the source was depleted previously + // this means no more work to do for any other worker + m_hasNoElementsLeft.Value = true; + m_FillBuffer = null; + return (actualNumElementsGrabbed > 0); + } + + + // + // now's the time to take the shared lock and enumerate + // + lock (m_sharedLock) + { + if (m_sourceDepleted.Value) + { + return (actualNumElementsGrabbed > 0); + } + + try + { + // we need to make sure all array copiers are finished + if (m_activeCopiers > 0) + { + SpinWait sw = new SpinWait(); + while( m_activeCopiers > 0) sw.SpinOnce(); + } + + Contract.Assert(m_sharedIndex != null); //already been allocated in MoveNext() before calling GrabNextChunk + + // Now's the time to actually enumerate the source + + // We first fill up the requested # of elements in the caller's array + // continue from the where TryCopyFromFillBuffer() left off + for (; actualNumElementsGrabbed < requestedChunkSize; actualNumElementsGrabbed++) + { + if (m_sharedReader.MoveNext()) + { + m_sharedIndex.Value = checked(m_sharedIndex.Value + 1); + destArray[actualNumElementsGrabbed] + = new KeyValuePair<long, TSource>(m_sharedIndex.Value, + m_sharedReader.Current); + } + else + { + //if MoveNext() return false, we set the flag to inform other partitions + m_sourceDepleted.Value = true; + break; + } + } + + // taking a local snapshot of m_FillBuffer in case some other thread decides to null out m_FillBuffer + // in the entry of this method after observing m_sourceCompleted = true + var localFillBufferRef = m_FillBuffer; + + // If the big buffer seems to be depleted, we will also fill that up while we are under the lock + // Note that this is the only place that m_FillBufferCurrentPosition can be reset + if (m_sourceDepleted.Value == false && localFillBufferRef != null && + m_FillBufferCurrentPosition >= localFillBufferRef.Length) + { + for (int i = 0; i < localFillBufferRef.Length; i++) + { + if( m_sharedReader.MoveNext()) + { + m_sharedIndex.Value = checked(m_sharedIndex.Value + 1); + localFillBufferRef[i] + = new KeyValuePair<long, TSource>(m_sharedIndex.Value, + m_sharedReader.Current); + } + else + { + // No more elements left in the enumerator. + // Record this, so that the next request can skip the lock + m_sourceDepleted.Value = true; + + // also record the current count in m_FillBufferSize + m_FillBufferSize = i; + + // and exit the for loop so that we don't keep incrementing m_FillBufferSize + break; + } + + } + + m_FillBufferCurrentPosition = 0; + } + + + } + catch + { + // If an exception occurs, don't let the other enumerators try to enumerate. + // NOTE: this could instead throw an InvalidOperationException, but that would be unexpected + // and not helpful to the end user. We know the root cause is being communicated already.) + m_sourceDepleted.Value = true; + m_hasNoElementsLeft.Value = true; + throw; + } + } + + return (actualNumElementsGrabbed > 0); + } + + public void Dispose() + { + if (!m_disposed) + { + m_disposed = true; + m_sharedReader.Dispose(); + } + } + } + + /// <summary> + /// Inherits from DynamicPartitionEnumerator_Abstract directly + /// Provides customized implementation for: GrabNextChunk, HasNoElementsLeft, Current, Dispose + /// </summary> + private class InternalPartitionEnumerator : DynamicPartitionEnumerator_Abstract<TSource, IEnumerator<TSource>> + { + //---- fields ---- + //cached local copy of the current chunk + private KeyValuePair<long, TSource>[] m_localList; //defer allocating to avoid false sharing + + // the values of the following two fields are passed in from + // outside(already initialized) by the constructor, + private readonly SharedBool m_hasNoElementsLeft; + private readonly object m_sharedLock; + private readonly SharedInt m_activePartitionCount; + private InternalPartitionEnumerable m_enumerable; + + //constructor + internal InternalPartitionEnumerator( + IEnumerator<TSource> sharedReader, + SharedLong sharedIndex, + SharedBool hasNoElementsLeft, + object sharedLock, + SharedInt activePartitionCount, + InternalPartitionEnumerable enumerable, + bool useSingleChunking) + : base(sharedReader, sharedIndex, useSingleChunking) + { + m_hasNoElementsLeft = hasNoElementsLeft; + m_sharedLock = sharedLock; + m_enumerable = enumerable; + m_activePartitionCount = activePartitionCount; + + if (m_activePartitionCount != null) + { + // If static partitioning, we need to increase the active partition count. + Interlocked.Increment(ref m_activePartitionCount.Value); + } + } + + //overriding methods + + /// <summary> + /// Reserves a contiguous range of elements from source data + /// </summary> + /// <param name="requestedChunkSize">specified number of elements requested</param> + /// <returns> + /// true if we successfully reserved at least one element (up to #=requestedChunkSize) + /// false if all elements in the source collection have been reserved. + /// </returns> + override protected bool GrabNextChunk(int requestedChunkSize) + { + Contract.Assert(requestedChunkSize > 0); + + if (HasNoElementsLeft) + { + return false; + } + + // defer allocation to avoid false sharing + if (m_localList == null) + { + m_localList = new KeyValuePair<long, TSource>[m_maxChunkSize]; + } + + // make the actual call to the enumerable that grabs a chunk + return m_enumerable.GrabChunk(m_localList, requestedChunkSize, ref m_currentChunkSize.Value); + } + + /// <summary> + /// Returns whether or not the shared reader has already read the last + /// element of the source data + /// </summary> + /// <remarks> + /// We cannot call m_sharedReader.MoveNext(), to see if it hits the last element + /// or not, because we can't undo MoveNext(). Thus we need to maintain a shared + /// boolean value m_hasNoElementsLeft across all partitions + /// </remarks> + override protected bool HasNoElementsLeft + { + get { return m_hasNoElementsLeft.Value; } + set + { + //we only set it from false to true once + //we should never set it back in any circumstances + Contract.Assert(value); + Contract.Assert(!m_hasNoElementsLeft.Value); + m_hasNoElementsLeft.Value = true; + } + } + + override public KeyValuePair<long, TSource> Current + { + get + { + //verify that MoveNext is at least called once before Current is called + if (m_currentChunkSize == null) + { + throw new InvalidOperationException(Environment.GetResourceString("PartitionerStatic_CurrentCalledBeforeMoveNext")); + } + Contract.Assert(m_localList != null); + Contract.Assert(m_localOffset.Value >= 0 && m_localOffset.Value < m_currentChunkSize.Value); + return (m_localList[m_localOffset.Value]); + } + } + + override public void Dispose() + { + // If this is static partitioning, ie. m_activePartitionCount != null, since the current partition + // is disposed, we decrement the number of active partitions for the shared reader. + if (m_activePartitionCount != null && Interlocked.Decrement(ref m_activePartitionCount.Value) == 0) + { + // If the number of active partitions becomes 0, we need to dispose the shared + // reader we created in the m_enumerable object. + m_enumerable.Dispose(); + } + // If this is dynamic partitioning, ie. m_activePartitionCount != null, then m_enumerable needs to + // be disposed explicitly by the user, and we do not need to anything here + } + } + #endregion + + } + #endregion + + #region Dynamic Partitioner for source data of IndexRange types (IList<> and Array<>) + /// <summary> + /// Dynamic load-balance partitioner. This class is abstract and to be derived from by + /// the customized partitioner classes for IList, Array, and IEnumerable + /// </summary> + /// <typeparam name="TSource">Type of the elements in the source data</typeparam> + /// <typeparam name="TCollection"> Type of the source data collection</typeparam> + private abstract class DynamicPartitionerForIndexRange_Abstract<TSource, TCollection> : OrderablePartitioner<TSource> + { + // TCollection can be: IList<TSource>, TSource[] and IEnumerable<TSource> + // Derived classes specify TCollection, and implement the abstract method GetOrderableDynamicPartitions_Factory accordingly + TCollection m_data; + + /// <summary> + /// Constructs a new orderable partitioner + /// </summary> + /// <param name="data">source data collection</param> + protected DynamicPartitionerForIndexRange_Abstract(TCollection data) + : base(true, false, true) + { + m_data = data; + } + + /// <summary> + /// Partition the source data and create an enumerable over the resulting partitions. + /// </summary> + /// <param name="data">the source data collection</param> + /// <returns>an enumerable of partitions of </returns> + protected abstract IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions_Factory(TCollection data); + + /// <summary> + /// Overrides OrderablePartitioner.GetOrderablePartitions. + /// Partitions the underlying collection into the given number of orderable partitions. + /// </summary> + /// <param name="partitionCount">number of partitions requested</param> + /// <returns>A list containing <paramref name="partitionCount"/> enumerators.</returns> + override public IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int partitionCount) + { + if (partitionCount <= 0) + { + throw new ArgumentOutOfRangeException("partitionCount"); + } + IEnumerator<KeyValuePair<long, TSource>>[] partitions + = new IEnumerator<KeyValuePair<long, TSource>>[partitionCount]; + IEnumerable<KeyValuePair<long, TSource>> partitionEnumerable = GetOrderableDynamicPartitions_Factory(m_data); + for (int i = 0; i < partitionCount; i++) + { + partitions[i] = partitionEnumerable.GetEnumerator(); + } + return partitions; + } + + /// <summary> + /// Overrides OrderablePartitioner.GetOrderableDyanmicPartitions + /// </summary> + /// <returns>a enumerable collection of orderable partitions</returns> + override public IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions() + { + return GetOrderableDynamicPartitions_Factory(m_data); + } + + /// <summary> + /// Whether additional partitions can be created dynamically. + /// </summary> + override public bool SupportsDynamicPartitions + { + get { return true; } + } + + } + + /// <summary> + /// Defines dynamic partition for source data of IList and Array. + /// This class inherits DynamicPartitionEnumerator_Abstract + /// - implements GrabNextChunk, HasNoElementsLeft, and Dispose methods for IList and Array + /// - Current property still remains abstract, implementation is different for IList and Array + /// - introduces another abstract method SourceCount, which returns the number of elements in + /// the source data. Implementation differs for IList and Array + /// </summary> + /// <typeparam name="TSource">Type of the elements in the data source</typeparam> + /// <typeparam name="TSourceReader">Type of the reader on the source data</typeparam> + private abstract class DynamicPartitionEnumeratorForIndexRange_Abstract<TSource, TSourceReader> : DynamicPartitionEnumerator_Abstract<TSource, TSourceReader> + { + //fields + protected int m_startIndex; //initially zero + + //constructor + protected DynamicPartitionEnumeratorForIndexRange_Abstract(TSourceReader sharedReader, SharedLong sharedIndex) + : base(sharedReader, sharedIndex) + { + } + + //abstract methods + //the Current property is still abstract, and will be implemented by derived classes + //we add another abstract method SourceCount to get the number of elements from the source reader + + /// <summary> + /// Get the number of elements from the source reader. + /// It calls IList.Count or Array.Length + /// </summary> + protected abstract int SourceCount { get; } + + //overriding methods + + /// <summary> + /// Reserves a contiguous range of elements from source data + /// </summary> + /// <param name="requestedChunkSize">specified number of elements requested</param> + /// <returns> + /// true if we successfully reserved at least one element (up to #=requestedChunkSize) + /// false if all elements in the source collection have been reserved. + /// </returns> + override protected bool GrabNextChunk(int requestedChunkSize) + { + Contract.Assert(requestedChunkSize > 0); + + while (!HasNoElementsLeft) + { + Contract.Assert(m_sharedIndex != null); + // use the new Volatile.Read method because it is cheaper than Interlocked.Read on AMD64 architecture + long oldSharedIndex = Volatile.Read(ref m_sharedIndex.Value); + + if (HasNoElementsLeft) + { + //HasNoElementsLeft situation changed from false to true immediately + //and oldSharedIndex becomes stale + return false; + } + + //there won't be overflow, because the index of IList/array is int, and we + //have casted it to long. + long newSharedIndex = Math.Min(SourceCount - 1, oldSharedIndex + requestedChunkSize); + + + //the following CAS, if successful, reserves a chunk of elements [oldSharedIndex+1, newSharedIndex] + //inclusive in the source collection + if (Interlocked.CompareExchange(ref m_sharedIndex.Value, newSharedIndex, oldSharedIndex) + == oldSharedIndex) + { + //set up local indexes. + //m_currentChunkSize is always set to requestedChunkSize when source data had + //enough elements of what we requested + m_currentChunkSize.Value = (int)(newSharedIndex - oldSharedIndex); + m_localOffset.Value = -1; + m_startIndex = (int)(oldSharedIndex + 1); + return true; + } + } + //didn't get any element, return false; + return false; + } + + /// <summary> + /// Returns whether or not the shared reader has already read the last + /// element of the source data + /// </summary> + override protected bool HasNoElementsLeft + { + get + { + Contract.Assert(m_sharedIndex != null); + // use the new Volatile.Read method because it is cheaper than Interlocked.Read on AMD64 architecture + return Volatile.Read(ref m_sharedIndex.Value) >= SourceCount - 1; + } + set + { + Contract.Assert(false); + } + } + + /// <summary> + /// For source data type IList and Array, the type of the shared reader is just the data itself. + /// We don't do anything in Dispose method for IList and Array. + /// </summary> + override public void Dispose() + { } + } + + + /// <summary> + /// Inherits from DynamicPartitioners + /// Provides customized implementation of GetOrderableDynamicPartitions_Factory method, to return an instance + /// of EnumerableOfPartitionsForIList defined internally + /// </summary> + /// <typeparam name="TSource">Type of elements in the source data</typeparam> + private class DynamicPartitionerForIList<TSource> : DynamicPartitionerForIndexRange_Abstract<TSource, IList<TSource>> + { + //constructor + internal DynamicPartitionerForIList(IList<TSource> source) + : base(source) + { } + + //override methods + override protected IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions_Factory(IList<TSource> m_data) + { + //m_data itself serves as shared reader + return new InternalPartitionEnumerable(m_data); + } + + /// <summary> + /// Inherits from PartitionList_Abstract + /// Provides customized implementation for source data of IList + /// </summary> + private class InternalPartitionEnumerable : IEnumerable<KeyValuePair<long, TSource>> + { + //reader through which we access the source data + private readonly IList<TSource> m_sharedReader; + private SharedLong m_sharedIndex; + + internal InternalPartitionEnumerable(IList<TSource> sharedReader) + { + m_sharedReader = sharedReader; + m_sharedIndex = new SharedLong(-1); + } + + public IEnumerator<KeyValuePair<long, TSource>> GetEnumerator() + { + return new InternalPartitionEnumerator(m_sharedReader, m_sharedIndex); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((InternalPartitionEnumerable)this).GetEnumerator(); + } + } + + /// <summary> + /// Inherits from DynamicPartitionEnumeratorForIndexRange_Abstract + /// Provides customized implementation of SourceCount property and Current property for IList + /// </summary> + private class InternalPartitionEnumerator : DynamicPartitionEnumeratorForIndexRange_Abstract<TSource, IList<TSource>> + { + //constructor + internal InternalPartitionEnumerator(IList<TSource> sharedReader, SharedLong sharedIndex) + : base(sharedReader, sharedIndex) + { } + + //overriding methods + override protected int SourceCount + { + get { return m_sharedReader.Count; } + } + /// <summary> + /// return a KeyValuePair of the current element and its key + /// </summary> + override public KeyValuePair<long, TSource> Current + { + get + { + //verify that MoveNext is at least called once before Current is called + if (m_currentChunkSize == null) + { + throw new InvalidOperationException(Environment.GetResourceString("PartitionerStatic_CurrentCalledBeforeMoveNext")); + } + + Contract.Assert(m_localOffset.Value >= 0 && m_localOffset.Value < m_currentChunkSize.Value); + return new KeyValuePair<long, TSource>(m_startIndex + m_localOffset.Value, + m_sharedReader[m_startIndex + m_localOffset.Value]); + } + } + } + } + + + + /// <summary> + /// Inherits from DynamicPartitioners + /// Provides customized implementation of GetOrderableDynamicPartitions_Factory method, to return an instance + /// of EnumerableOfPartitionsForArray defined internally + /// </summary> + /// <typeparam name="TSource">Type of elements in the source data</typeparam> + private class DynamicPartitionerForArray<TSource> : DynamicPartitionerForIndexRange_Abstract<TSource, TSource[]> + { + //constructor + internal DynamicPartitionerForArray(TSource[] source) + : base(source) + { } + + //override methods + override protected IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions_Factory(TSource[] m_data) + { + return new InternalPartitionEnumerable(m_data); + } + + /// <summary> + /// Inherits from PartitionList_Abstract + /// Provides customized implementation for source data of Array + /// </summary> + private class InternalPartitionEnumerable : IEnumerable<KeyValuePair<long, TSource>> + { + //reader through which we access the source data + private readonly TSource[] m_sharedReader; + private SharedLong m_sharedIndex; + + internal InternalPartitionEnumerable(TSource[] sharedReader) + { + m_sharedReader = sharedReader; + m_sharedIndex = new SharedLong(-1); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((InternalPartitionEnumerable)this).GetEnumerator(); + } + + + public IEnumerator<KeyValuePair<long, TSource>> GetEnumerator() + { + return new InternalPartitionEnumerator(m_sharedReader, m_sharedIndex); + } + } + + /// <summary> + /// Inherits from DynamicPartitionEnumeratorForIndexRange_Abstract + /// Provides customized implementation of SourceCount property and Current property for Array + /// </summary> + private class InternalPartitionEnumerator : DynamicPartitionEnumeratorForIndexRange_Abstract<TSource, TSource[]> + { + //constructor + internal InternalPartitionEnumerator(TSource[] sharedReader, SharedLong sharedIndex) + : base(sharedReader, sharedIndex) + { } + + //overriding methods + override protected int SourceCount + { + get { return m_sharedReader.Length; } + } + + override public KeyValuePair<long, TSource> Current + { + get + { + //verify that MoveNext is at least called once before Current is called + if (m_currentChunkSize == null) + { + throw new InvalidOperationException(Environment.GetResourceString("PartitionerStatic_CurrentCalledBeforeMoveNext")); + } + + Contract.Assert(m_localOffset.Value >= 0 && m_localOffset.Value < m_currentChunkSize.Value); + return new KeyValuePair<long, TSource>(m_startIndex + m_localOffset.Value, + m_sharedReader[m_startIndex + m_localOffset.Value]); + } + } + } + } + #endregion + + + #region Static partitioning for IList and Array, abstract classes + /// <summary> + /// Static partitioning over IList. + /// - dynamic and load-balance + /// - Keys are ordered within each partition + /// - Keys are ordered across partitions + /// - Keys are normalized + /// - Number of partitions is fixed once specified, and the elements of the source data are + /// distributed to each partition as evenly as possible. + /// </summary> + /// <typeparam name="TSource">type of the elements</typeparam> + /// <typeparam name="TCollection">Type of the source data collection</typeparam> + private abstract class StaticIndexRangePartitioner<TSource, TCollection> : OrderablePartitioner<TSource> + { + protected StaticIndexRangePartitioner() + : base(true, true, true) + { } + + /// <summary> + /// Abstract method to return the number of elements in the source data + /// </summary> + protected abstract int SourceCount { get; } + + /// <summary> + /// Abstract method to create a partition that covers a range over source data, + /// starting from "startIndex", ending at "endIndex" + /// </summary> + /// <param name="startIndex">start index of the current partition on the source data</param> + /// <param name="endIndex">end index of the current partition on the source data</param> + /// <returns>a partition enumerator over the specified range</returns> + // The partitioning algorithm is implemented in GetOrderablePartitions method + // This method delegates according to source data type IList/Array + protected abstract IEnumerator<KeyValuePair<long, TSource>> CreatePartition(int startIndex, int endIndex); + + /// <summary> + /// Overrides OrderablePartitioner.GetOrderablePartitions + /// Return a list of partitions, each of which enumerate a fixed part of the source data + /// The elements of the source data are distributed to each partition as evenly as possible. + /// Specifically, if the total number of elements is N, and number of partitions is x, and N = a*x +b, + /// where a is the quotient, and b is the remainder. Then the first b partitions each has a + 1 elements, + /// and the last x-b partitions each has a elements. + /// For example, if N=10, x =3, then + /// partition 0 ranges [0,3], + /// partition 1 ranges [4,6], + /// partition 2 ranges [7,9]. + /// This also takes care of the situation of (x>N), the last x-N partitions are empty enumerators. + /// An empty enumerator is indicated by + /// (m_startIndex == list.Count && m_endIndex == list.Count -1) + /// </summary> + /// <param name="partitionCount">specified number of partitions</param> + /// <returns>a list of partitions</returns> + override public IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int partitionCount) + { + if (partitionCount <= 0) + { + throw new ArgumentOutOfRangeException("partitionCount"); + } + + int quotient, remainder; + quotient = Math.DivRem(SourceCount, partitionCount, out remainder); + + IEnumerator<KeyValuePair<long, TSource>>[] partitions = new IEnumerator<KeyValuePair<long, TSource>>[partitionCount]; + int lastEndIndex = -1; + for (int i = 0; i < partitionCount; i++) + { + int startIndex = lastEndIndex + 1; + + if (i < remainder) + { + lastEndIndex = startIndex + quotient; + } + else + { + lastEndIndex = startIndex + quotient - 1; + } + partitions[i] = CreatePartition(startIndex, lastEndIndex); + } + return partitions; + } + } + + /// <summary> + /// Static Partition for IList/Array. + /// This class implements all methods required by IEnumerator interface, except for the Current property. + /// Current Property is different for IList and Array. Arrays calls 'ldelem' instructions for faster element + /// retrieval. + /// </summary> + //We assume the source collection is not being updated concurrently. Otherwise it will break the + //static partitioning, since each partition operates on the source collection directly, it does + //not have a local cache of the elements assigned to them. + private abstract class StaticIndexRangePartition<TSource> : IEnumerator<KeyValuePair<long, TSource>> + { + //the start and end position in the source collection for the current partition + //the partition is empty if and only if + // (m_startIndex == m_data.Count && m_endIndex == m_data.Count-1) + protected readonly int m_startIndex; + protected readonly int m_endIndex; + + //the current index of the current partition while enumerating on the source collection + protected volatile int m_offset; + + /// <summary> + /// Constructs an instance of StaticIndexRangePartition + /// </summary> + /// <param name="startIndex">the start index in the source collection for the current partition </param> + /// <param name="endIndex">the end index in the source collection for the current partition</param> + protected StaticIndexRangePartition(int startIndex, int endIndex) + { + m_startIndex = startIndex; + m_endIndex = endIndex; + m_offset = startIndex - 1; + } + + /// <summary> + /// Current Property is different for IList and Array. Arrays calls 'ldelem' instructions for faster + /// element retrieval. + /// </summary> + public abstract KeyValuePair<long, TSource> Current { get; } + + /// <summary> + /// We don't dispose the source for IList and array + /// </summary> + public void Dispose() + { } + + public void Reset() + { + throw new NotSupportedException(); + } + + /// <summary> + /// Moves to the next item + /// Before the first MoveNext is called: m_offset == m_startIndex-1; + /// </summary> + /// <returns>true if successful, false if there is no item left</returns> + public bool MoveNext() + { + if (m_offset < m_endIndex) + { + m_offset++; + return true; + } + else + { + //After we have enumerated over all elements, we set m_offset to m_endIndex +1. + //The reason we do this is, for an empty enumerator, we need to tell the Current + //property whether MoveNext has been called or not. + //For an empty enumerator, it starts with (m_offset == m_startIndex-1 == m_endIndex), + //and we don't set a new value to m_offset, then the above condition will always be + //true, and the Current property will mistakenly assume MoveNext is never called. + m_offset = m_endIndex + 1; + return false; + } + } + + Object IEnumerator.Current + { + get + { + return ((StaticIndexRangePartition<TSource>)this).Current; + } + } + } + #endregion + + #region Static partitioning for IList + /// <summary> + /// Inherits from StaticIndexRangePartitioner + /// Provides customized implementation of SourceCount and CreatePartition + /// </summary> + /// <typeparam name="TSource"></typeparam> + private class StaticIndexRangePartitionerForIList<TSource> : StaticIndexRangePartitioner<TSource, IList<TSource>> + { + IList<TSource> m_list; + internal StaticIndexRangePartitionerForIList(IList<TSource> list) + : base() + { + Contract.Assert(list != null); + m_list = list; + } + override protected int SourceCount + { + get { return m_list.Count; } + } + override protected IEnumerator<KeyValuePair<long, TSource>> CreatePartition(int startIndex, int endIndex) + { + return new StaticIndexRangePartitionForIList<TSource>(m_list, startIndex, endIndex); + } + } + + /// <summary> + /// Inherits from StaticIndexRangePartition + /// Provides customized implementation of Current property + /// </summary> + /// <typeparam name="TSource"></typeparam> + private class StaticIndexRangePartitionForIList<TSource> : StaticIndexRangePartition<TSource> + { + //the source collection shared by all partitions + private volatile IList<TSource> m_list; + + internal StaticIndexRangePartitionForIList(IList<TSource> list, int startIndex, int endIndex) + : base(startIndex, endIndex) + { + Contract.Assert(startIndex >= 0 && endIndex <= list.Count - 1); + m_list = list; + } + + override public KeyValuePair<long, TSource> Current + { + get + { + //verify that MoveNext is at least called once before Current is called + if (m_offset < m_startIndex) + { + throw new InvalidOperationException(Environment.GetResourceString("PartitionerStatic_CurrentCalledBeforeMoveNext")); + } + + Contract.Assert(m_offset >= m_startIndex && m_offset <= m_endIndex); + return (new KeyValuePair<long, TSource>(m_offset, m_list[m_offset])); + } + } + } + #endregion + + #region static partitioning for Arrays + /// <summary> + /// Inherits from StaticIndexRangePartitioner + /// Provides customized implementation of SourceCount and CreatePartition for Array + /// </summary> + private class StaticIndexRangePartitionerForArray<TSource> : StaticIndexRangePartitioner<TSource, TSource[]> + { + TSource[] m_array; + internal StaticIndexRangePartitionerForArray(TSource[] array) + : base() + { + Contract.Assert(array != null); + m_array = array; + } + override protected int SourceCount + { + get { return m_array.Length; } + } + override protected IEnumerator<KeyValuePair<long, TSource>> CreatePartition(int startIndex, int endIndex) + { + return new StaticIndexRangePartitionForArray<TSource>(m_array, startIndex, endIndex); + } + } + + /// <summary> + /// Inherits from StaticIndexRangePartitioner + /// Provides customized implementation of SourceCount and CreatePartition + /// </summary> + private class StaticIndexRangePartitionForArray<TSource> : StaticIndexRangePartition<TSource> + { + //the source collection shared by all partitions + private volatile TSource[] m_array; + + internal StaticIndexRangePartitionForArray(TSource[] array, int startIndex, int endIndex) + : base(startIndex, endIndex) + { + Contract.Assert(startIndex >= 0 && endIndex <= array.Length - 1); + m_array = array; + } + + override public KeyValuePair<long, TSource> Current + { + get + { + //verify that MoveNext is at least called once before Current is called + if (m_offset < m_startIndex) + { + throw new InvalidOperationException(Environment.GetResourceString("PartitionerStatic_CurrentCalledBeforeMoveNext")); + } + + Contract.Assert(m_offset >= m_startIndex && m_offset <= m_endIndex); + return (new KeyValuePair<long, TSource>(m_offset, m_array[m_offset])); + } + } + } + #endregion + + + #region Utility functions + /// <summary> + /// A very simple primitive that allows us to share a value across multiple threads. + /// </summary> + /// <typeparam name="TSource"></typeparam> + private class SharedInt + { + internal volatile int Value; + + internal SharedInt(int value) + { + this.Value = value; + } + + } + + /// <summary> + /// A very simple primitive that allows us to share a value across multiple threads. + /// </summary> + private class SharedBool + { + internal volatile bool Value; + + internal SharedBool(bool value) + { + this.Value = value; + } + + } + + /// <summary> + /// A very simple primitive that allows us to share a value across multiple threads. + /// </summary> + private class SharedLong + { + internal long Value; + internal SharedLong(long value) + { + this.Value = value; + } + + } + + //-------------------- + // The following part calculates the default chunk size. It is copied from System.Linq.Parallel.Scheduling, + // because mscorlib.dll cannot access System.Linq.Parallel.Scheduling + //-------------------- + + // The number of bytes we want "chunks" to be, when partitioning, etc. We choose 4 cache + // lines worth, assuming 128b cache line. Most (popular) architectures use 64b cache lines, + // but choosing 128b works for 64b too whereas a multiple of 64b isn't necessarily sufficient + // for 128b cache systems. So 128b it is. + private const int DEFAULT_BYTES_PER_CHUNK = 128 * 4; + + private static int GetDefaultChunkSize<TSource>() + { + int chunkSize; + + if (typeof(TSource).IsValueType) + { +#if !FEATURE_CORECLR // Marshal.SizeOf is not supported in CoreCLR + + if (typeof(TSource).StructLayoutAttribute.Value == LayoutKind.Explicit) + { + chunkSize = Math.Max(1, DEFAULT_BYTES_PER_CHUNK / Marshal.SizeOf(typeof(TSource))); + } + else + { + // We choose '128' because this ensures, no matter the actual size of the value type, + // the total bytes used will be a multiple of 128. This ensures it's cache aligned. + chunkSize = 128; + } +#else + chunkSize = 128; +#endif + } + else + { + Contract.Assert((DEFAULT_BYTES_PER_CHUNK % IntPtr.Size) == 0, "bytes per chunk should be a multiple of pointer size"); + chunkSize = (DEFAULT_BYTES_PER_CHUNK / IntPtr.Size); + } + return chunkSize; + } + #endregion + + } +} diff --git a/src/mscorlib/src/System/Collections/DictionaryEntry.cs b/src/mscorlib/src/System/Collections/DictionaryEntry.cs new file mode 100644 index 0000000000..fc1d57fe55 --- /dev/null +++ b/src/mscorlib/src/System/Collections/DictionaryEntry.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: DictionaryEntry +** +** +** +** +** Purpose: Return Value for IDictionaryEnumerator::GetEntry +** +** +===========================================================*/ +namespace System.Collections { + + using System; + // A DictionaryEntry holds a key and a value from a dictionary. + // It is returned by IDictionaryEnumerator::GetEntry(). +[System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public struct DictionaryEntry + { + private Object _key; + private Object _value; + + // Constructs a new DictionaryEnumerator by setting the Key + // and Value fields appropriately. + public DictionaryEntry(Object key, Object value) { + _key = key; + _value = value; + } + + public Object Key { + get { + return _key; + } + + set { + _key = value; + } + } + + public Object Value { + get { + return _value; + } + + set { + _value = value; + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/EmptyReadOnlyDictionaryInternal.cs b/src/mscorlib/src/System/Collections/EmptyReadOnlyDictionaryInternal.cs new file mode 100644 index 0000000000..6e28493ef8 --- /dev/null +++ b/src/mscorlib/src/System/Collections/EmptyReadOnlyDictionaryInternal.cs @@ -0,0 +1,194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: List for exceptions. +** +** +===========================================================*/ + +using System.Diagnostics.Contracts; + +namespace System.Collections { + /// This is a simple implementation of IDictionary that is empty and readonly. + [Serializable] + internal sealed class EmptyReadOnlyDictionaryInternal: IDictionary { + + // Note that this class must be agile with respect to AppDomains. See its usage in + // System.Exception to understand why this is the case. + + public EmptyReadOnlyDictionaryInternal() { + } + + // IEnumerable members + + IEnumerator IEnumerable.GetEnumerator() { + return new NodeEnumerator(); + } + + // ICollection members + + public void CopyTo(Array array, int index) { + if (array==null) + throw new ArgumentNullException("array"); + + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + + if ( array.Length - index < this.Count ) + throw new ArgumentException( Environment.GetResourceString("ArgumentOutOfRange_Index"), "index"); + Contract.EndContractBlock(); + + // the actual copy is a NOP + } + + public int Count { + get { + return 0; + } + } + + public Object SyncRoot { + get { + return this; + } + } + + public bool IsSynchronized { + get { + return false; + } + } + + // IDictionary members + + public Object this[Object key] { + get { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + return null; + } + set { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + + if (!key.GetType().IsSerializable) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "key"); + + if( (value != null) && (!value.GetType().IsSerializable ) ) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "value"); + Contract.EndContractBlock(); + + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); + } + } + + public ICollection Keys { + get { + return EmptyArray<Object>.Value; + } + } + + public ICollection Values { + get { + return EmptyArray<Object>.Value; + } + } + + public bool Contains(Object key) { + return false; + } + + public void Add(Object key, Object value) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + + if (!key.GetType().IsSerializable) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "key" ); + + if( (value != null) && (!value.GetType().IsSerializable) ) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "value"); + Contract.EndContractBlock(); + + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); + } + + public void Clear() { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); + } + + public bool IsReadOnly { + get { + return true; + } + } + + public bool IsFixedSize { + get { + return true; + } + } + + public IDictionaryEnumerator GetEnumerator() { + return new NodeEnumerator(); + } + + public void Remove(Object key) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly")); + } + + private sealed class NodeEnumerator : IDictionaryEnumerator { + + public NodeEnumerator() { + } + + // IEnumerator members + + public bool MoveNext() { + return false; + } + + public Object Current { + get { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + } + + public void Reset() { + } + + // IDictionaryEnumerator members + + public Object Key { + get { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + } + + public Object Value { + get { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + } + + public DictionaryEntry Entry { + get { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/ArraySortHelper.cs b/src/mscorlib/src/System/Collections/Generic/ArraySortHelper.cs new file mode 100644 index 0000000000..b2fed9d78f --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/ArraySortHelper.cs @@ -0,0 +1,1558 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: class to sort arrays +** +** +===========================================================*/ +namespace System.Collections.Generic +{ + using System; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Diagnostics.Contracts; + using System.Runtime.Versioning; + + #region ArraySortHelper for single arrays + + internal interface IArraySortHelper<TKey> + { + void Sort(TKey[] keys, int index, int length, IComparer<TKey> comparer); + int BinarySearch(TKey[] keys, int index, int length, TKey value, IComparer<TKey> comparer); + } + + internal static class IntrospectiveSortUtilities + { + // This is the threshold where Introspective sort switches to Insertion sort. + // Imperically, 16 seems to speed up most cases without slowing down others, at least for integers. + // Large value types may benefit from a smaller number. + internal const int IntrosortSizeThreshold = 16; + + internal const int QuickSortDepthThreshold = 32; + + internal static int FloorLog2(int n) + { + int result = 0; + while (n >= 1) + { + result++; + n = n / 2; + } + return result; + } + + internal static void ThrowOrIgnoreBadComparer(Object comparer) { + // This is hit when an invarant of QuickSort is violated due to a bad IComparer implementation (for + // example, imagine an IComparer that returns 0 when items are equal but -1 all other times). + // + // We could have thrown this exception on v4, but due to changes in v4.5 around how we partition arrays + // there are different sets of input where we would throw this exception. In order to reduce overall risk from + // an app compat persective, we're changing to never throw on v4. Instead, we'll return with a partially + // sorted array. + if(BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) { + throw new ArgumentException(Environment.GetResourceString("Arg_BogusIComparer", comparer)); + } + } + + } + + [TypeDependencyAttribute("System.Collections.Generic.GenericArraySortHelper`1")] + internal class ArraySortHelper<T> + : IArraySortHelper<T> + { + static volatile IArraySortHelper<T> defaultArraySortHelper; + + public static IArraySortHelper<T> Default + { + get + { + IArraySortHelper<T> sorter = defaultArraySortHelper; + if (sorter == null) + sorter = CreateArraySortHelper(); + + return sorter; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + private static IArraySortHelper<T> CreateArraySortHelper() + { + if (typeof(IComparable<T>).IsAssignableFrom(typeof(T))) + { + defaultArraySortHelper = (IArraySortHelper<T>)RuntimeTypeHandle.Allocate(typeof(GenericArraySortHelper<string>).TypeHandle.Instantiate(new Type[] { typeof(T) })); + } + else + { + defaultArraySortHelper = new ArraySortHelper<T>(); + } + return defaultArraySortHelper; + } + + #region IArraySortHelper<T> Members + + public void Sort(T[] keys, int index, int length, IComparer<T> comparer) + { + Contract.Assert(keys != null, "Check the arguments in the caller!"); + Contract.Assert( index >= 0 && length >= 0 && (keys.Length - index >= length), "Check the arguments in the caller!"); + + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + try + { + if (comparer == null) + { + comparer = Comparer<T>.Default; + } + +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + IntrospectiveSort(keys, index, length, comparer); +#else + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + IntrospectiveSort(keys, index, length, comparer); + } + else + { + DepthLimitedQuickSort(keys, index, length + index - 1, comparer, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + catch (IndexOutOfRangeException) + { + IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + public int BinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer) + { + try + { + if (comparer == null) + { + comparer = Comparer<T>.Default; + } + + return InternalBinarySearch(array, index, length, value, comparer); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + #endregion + + internal static int InternalBinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer) + { + Contract.Requires(array != null, "Check the arguments in the caller!"); + Contract.Requires(index >= 0 && length >= 0 && (array.Length - index >= length), "Check the arguments in the caller!"); + + int lo = index; + int hi = index + length - 1; + while (lo <= hi) + { + int i = lo + ((hi - lo) >> 1); + int order = comparer.Compare(array[i], value); + + if (order == 0) return i; + if (order < 0) + { + lo = i + 1; + } + else + { + hi = i - 1; + } + } + + return ~lo; + } + + private static void SwapIfGreater(T[] keys, IComparer<T> comparer, int a, int b) + { + if (a != b) + { + if (comparer.Compare(keys[a], keys[b]) > 0) + { + T key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + } + } + } + + private static void Swap(T[] a, int i, int j) + { + if(i != j) + { + T t = a[i]; + a[i] = a[j]; + a[j] = t; + } + } + + internal static void DepthLimitedQuickSort(T[] keys, int left, int right, IComparer<T> comparer, int depthLimit) + { + do + { + if (depthLimit == 0) + { + Heapsort(keys, left, right, comparer); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + SwapIfGreater(keys, comparer, i, middle); // swap the low with the mid point + SwapIfGreater(keys, comparer, i, j); // swap the low with the high + SwapIfGreater(keys, comparer, middle, j); // swap the middle with the high + + T x = keys[middle]; + do + { + while (comparer.Compare(keys[i], x) < 0) i++; + while (comparer.Compare(x, keys[j]) < 0) j--; + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + if (i < j) + { + T key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + } + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recursively sort the smaller half. So we subtract one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) DepthLimitedQuickSort(keys, left, j, comparer, depthLimit); + left = i; + } + else + { + if (i < right) DepthLimitedQuickSort(keys, i, right, comparer, depthLimit); + right = j; + } + } while (left < right); + } + + internal static void IntrospectiveSort(T[] keys, int left, int length, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(left >= 0); + Contract.Requires(length >= 0); + Contract.Requires(length <= keys.Length); + Contract.Requires(length + left <= keys.Length); + + if (length < 2) + return; + + IntroSort(keys, left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2(keys.Length), comparer); + } + + private static void IntroSort(T[] keys, int lo, int hi, int depthLimit, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi < keys.Length); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + SwapIfGreater(keys, comparer, lo, hi); + return; + } + if (partitionSize == 3) + { + SwapIfGreater(keys, comparer, lo, hi-1); + SwapIfGreater(keys, comparer, lo, hi); + SwapIfGreater(keys, comparer, hi-1, hi); + return; + } + + InsertionSort(keys, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + Heapsort(keys, lo, hi, comparer); + return; + } + depthLimit--; + + int p = PickPivotAndPartition(keys, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(keys, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition(T[] keys, int lo, int hi, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + Contract.Ensures(Contract.Result<int>() >= lo && Contract.Result<int>() <= hi); + + // Compute median-of-three. But also partition them, since we've done the comparison. + int middle = lo + ((hi - lo) / 2); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + SwapIfGreater(keys, comparer, lo, middle); // swap the low with the mid point + SwapIfGreater(keys, comparer, lo, hi); // swap the low with the high + SwapIfGreater(keys, comparer, middle, hi); // swap the middle with the high + + T pivot = keys[middle]; + Swap(keys, middle, hi - 1); + int left = lo, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. + + while (left < right) + { + while (comparer.Compare(keys[++left], pivot) < 0) ; + while (comparer.Compare(pivot, keys[--right]) < 0) ; + + if (left >= right) + break; + + Swap(keys, left, right); + } + + // Put pivot in the right location. + Swap(keys, left, (hi - 1)); + return left; + } + + private static void Heapsort(T[] keys, int lo, int hi, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; i = i - 1) + { + DownHeap(keys, i, n, lo, comparer); + } + for (int i = n; i > 1; i = i - 1) + { + Swap(keys, lo, lo + i - 1); + DownHeap(keys, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap(T[] keys, int i, int n, int lo, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + T d = keys[lo + i - 1]; + int child; + while (i <= n / 2) + { + child = 2 * i; + if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0) + { + child++; + } + if (!(comparer.Compare(d, keys[lo + child - 1]) < 0)) + break; + keys[lo + i - 1] = keys[lo + child - 1]; + i = child; + } + keys[lo + i - 1] = d; + } + + private static void InsertionSort(T[] keys, int lo, int hi, IComparer<T> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi >= lo); + Contract.Requires(hi <= keys.Length); + + int i, j; + T t; + for (i = lo; i < hi; i++) + { + j = i; + t = keys[i + 1]; + while (j >= lo && comparer.Compare(t, keys[j]) < 0) + { + keys[j + 1] = keys[j]; + j--; + } + keys[j + 1] = t; + } + } + } + + [Serializable()] + internal class GenericArraySortHelper<T> + : IArraySortHelper<T> + where T : IComparable<T> + { + // Do not add a constructor to this class because ArraySortHelper<T>.CreateSortHelper will not execute it + + #region IArraySortHelper<T> Members + + public void Sort(T[] keys, int index, int length, IComparer<T> comparer) + { + Contract.Assert(keys != null, "Check the arguments in the caller!"); + Contract.Assert(index >= 0 && length >= 0 && (keys.Length - index >= length), "Check the arguments in the caller!"); + + try + { + if (comparer == null || comparer == Comparer<T>.Default) { + +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + IntrospectiveSort(keys, index, length); +#else + // call the faster version of our sort algorithm if the user doesn't provide a comparer + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + IntrospectiveSort(keys, index, length); + } + else + { + DepthLimitedQuickSort(keys, index, length + index - 1, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + else + { +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + ArraySortHelper<T>.IntrospectiveSort(keys, index, length, comparer); +#else + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + ArraySortHelper<T>.IntrospectiveSort(keys, index, length, comparer); + } + else + { + ArraySortHelper<T>.DepthLimitedQuickSort(keys, index, length + index - 1, comparer, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + } + catch (IndexOutOfRangeException) + { + IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + public int BinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer) + { + Contract.Assert(array != null, "Check the arguments in the caller!"); + Contract.Assert(index >= 0 && length >= 0 && (array.Length - index >= length), "Check the arguments in the caller!"); + + try + { + if (comparer == null || comparer == Comparer<T>.Default) + { + return BinarySearch(array, index, length, value); + } + else + { + return ArraySortHelper<T>.InternalBinarySearch(array, index, length, value, comparer); + } + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + #endregion + + // This function is called when the user doesn't specify any comparer. + // Since T is constrained here, we can call IComparable<T>.CompareTo here. + // We can avoid boxing for value type and casting for reference types. + private static int BinarySearch(T[] array, int index, int length, T value) + { + int lo = index; + int hi = index + length - 1; + while (lo <= hi) + { + int i = lo + ((hi - lo) >> 1); + int order; + if (array[i] == null) + { + order = (value == null) ? 0 : -1; + } + else + { + order = array[i].CompareTo(value); + } + + if (order == 0) + { + return i; + } + + if (order < 0) + { + lo = i + 1; + } + else + { + hi = i - 1; + } + } + + return ~lo; + } + + private static void SwapIfGreaterWithItems(T[] keys, int a, int b) + { + Contract.Requires(keys != null); + Contract.Requires(0 <= a && a < keys.Length); + Contract.Requires(0 <= b && b < keys.Length); + + if (a != b) + { + if (keys[a] != null && keys[a].CompareTo(keys[b]) > 0) + { + T key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + } + } + } + + private static void Swap(T[] a, int i, int j) + { + if(i!=j) + { + T t = a[i]; + a[i] = a[j]; + a[j] = t; + } + } + + private static void DepthLimitedQuickSort(T[] keys, int left, int right, int depthLimit) + { + Contract.Requires(keys != null); + Contract.Requires(0 <= left && left < keys.Length); + Contract.Requires(0 <= right && right < keys.Length); + + // The code in this function looks very similar to QuickSort in ArraySortHelper<T> class. + // The difference is that T is constrainted to IComparable<T> here. + // So the IL code will be different. This function is faster than the one in ArraySortHelper<T>. + + do + { + if (depthLimit == 0) + { + Heapsort(keys, left, right); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + SwapIfGreaterWithItems(keys, i, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, i, j); // swap the low with the high + SwapIfGreaterWithItems(keys, middle, j); // swap the middle with the high + + T x = keys[middle]; + do + { + if (x == null) + { + // if x null, the loop to find two elements to be switched can be reduced. + while (keys[j] != null) j--; + } + else + { + while (x.CompareTo(keys[i]) > 0) i++; + while (x.CompareTo(keys[j]) < 0) j--; + } + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + if (i < j) + { + T key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + } + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recursively sort the smaller half. So we subtract one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) DepthLimitedQuickSort(keys, left, j, depthLimit); + left = i; + } + else + { + if (i < right) DepthLimitedQuickSort(keys, i, right, depthLimit); + right = j; + } + } while (left < right); + } + + internal static void IntrospectiveSort(T[] keys, int left, int length) + { + Contract.Requires(keys != null); + Contract.Requires(left >= 0); + Contract.Requires(length >= 0); + Contract.Requires(length <= keys.Length); + Contract.Requires(length + left <= keys.Length); + + if (length < 2) + return; + + IntroSort(keys, left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2(keys.Length)); + } + + private static void IntroSort(T[] keys, int lo, int hi, int depthLimit) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi < keys.Length); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + SwapIfGreaterWithItems(keys, lo, hi); + return; + } + if (partitionSize == 3) + { + SwapIfGreaterWithItems(keys, lo, hi-1); + SwapIfGreaterWithItems(keys, lo, hi); + SwapIfGreaterWithItems(keys, hi-1, hi); + return; + } + + InsertionSort(keys, lo, hi); + return; + } + + if (depthLimit == 0) + { + Heapsort(keys, lo, hi); + return; + } + depthLimit--; + + int p = PickPivotAndPartition(keys, lo, hi); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(keys, p + 1, hi, depthLimit); + hi = p - 1; + } + } + + private static int PickPivotAndPartition(T[] keys, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + Contract.Ensures(Contract.Result<int>() >= lo && Contract.Result<int>() <= hi); + + // Compute median-of-three. But also partition them, since we've done the comparison. + int middle = lo + ((hi - lo) / 2); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + SwapIfGreaterWithItems(keys, lo, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, lo, hi); // swap the low with the high + SwapIfGreaterWithItems(keys, middle, hi); // swap the middle with the high + + T pivot = keys[middle]; + Swap(keys, middle, hi - 1); + int left = lo, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. + + while (left < right) + { + if (pivot == null) + { + while (left < (hi - 1) && keys[++left] == null) ; + while (right > lo && keys[--right] != null) ; + } + else + { + while (pivot.CompareTo(keys[++left]) > 0) ; + while (pivot.CompareTo(keys[--right]) < 0) ; + } + + if (left >= right) + break; + + Swap(keys, left, right); + } + + // Put pivot in the right location. + Swap(keys, left, (hi - 1)); + return left; + } + + private static void Heapsort(T[] keys, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; i = i - 1) + { + DownHeap(keys, i, n, lo); + } + for (int i = n; i > 1; i = i - 1) + { + Swap(keys, lo, lo + i - 1); + DownHeap(keys, 1, i - 1, lo); + } + } + + private static void DownHeap(T[] keys, int i, int n, int lo) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + T d = keys[lo + i - 1]; + int child; + while (i <= n / 2) + { + child = 2 * i; + if (child < n && (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) + { + child++; + } + if (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(d) < 0) + break; + keys[lo + i - 1] = keys[lo + child - 1]; + i = child; + } + keys[lo + i - 1] = d; + } + + private static void InsertionSort(T[] keys, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi >= lo); + Contract.Requires(hi <= keys.Length); + + int i, j; + T t; + for (i = lo; i < hi; i++) + { + j = i; + t = keys[i + 1]; + while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) + { + keys[j + 1] = keys[j]; + j--; + } + keys[j + 1] = t; + } + } + } + + #endregion + + #region ArraySortHelper for paired key and value arrays + + internal interface IArraySortHelper<TKey, TValue> + { + void Sort(TKey[] keys, TValue[] values, int index, int length, IComparer<TKey> comparer); + } + + [TypeDependencyAttribute("System.Collections.Generic.GenericArraySortHelper`2")] + internal class ArraySortHelper<TKey, TValue> + : IArraySortHelper<TKey, TValue> + { + static volatile IArraySortHelper<TKey, TValue> defaultArraySortHelper; + + public static IArraySortHelper<TKey, TValue> Default + { + get + { + IArraySortHelper<TKey, TValue> sorter = defaultArraySortHelper; + if (sorter == null) + sorter = CreateArraySortHelper(); + + return sorter; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + private static IArraySortHelper<TKey, TValue> CreateArraySortHelper() + { + if (typeof(IComparable<TKey>).IsAssignableFrom(typeof(TKey))) + { + defaultArraySortHelper = (IArraySortHelper<TKey, TValue>)RuntimeTypeHandle.Allocate(typeof(GenericArraySortHelper<string, string>).TypeHandle.Instantiate(new Type[] { typeof(TKey), typeof(TValue) })); + } + else + { + defaultArraySortHelper = new ArraySortHelper<TKey, TValue>(); + } + return defaultArraySortHelper; + } + + public void Sort(TKey[] keys, TValue[] values, int index, int length, IComparer<TKey> comparer) + { + Contract.Assert(keys != null, "Check the arguments in the caller!"); // Precondition on interface method + Contract.Assert(index >= 0 && length >= 0 && (keys.Length - index >= length), "Check the arguments in the caller!"); + + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + try + { + if (comparer == null || comparer == Comparer<TKey>.Default) + { + comparer = Comparer<TKey>.Default; + } + +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + IntrospectiveSort(keys, values, index, length, comparer); +#else + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + IntrospectiveSort(keys, values, index, length, comparer); + } + else + { + DepthLimitedQuickSort(keys, values, index, length + index - 1, comparer, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + catch (IndexOutOfRangeException) + { + IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + private static void SwapIfGreaterWithItems(TKey[] keys, TValue[] values, IComparer<TKey> comparer, int a, int b) + { + Contract.Requires(keys != null); + Contract.Requires(values == null || values.Length >= keys.Length); + Contract.Requires(comparer != null); + Contract.Requires(0 <= a && a < keys.Length); + Contract.Requires(0 <= b && b < keys.Length); + + if (a != b) + { + if (comparer.Compare(keys[a], keys[b]) > 0) + { + TKey key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + if (values != null) + { + TValue value = values[a]; + values[a] = values[b]; + values[b] = value; + } + } + } + } + + private static void Swap(TKey[] keys, TValue[] values, int i, int j) + { + if(i!=j) + { + TKey k = keys[i]; + keys[i] = keys[j]; + keys[j] = k; + if(values != null) + { + TValue v = values[i]; + values[i] = values[j]; + values[j] = v; + } + } + } + + internal static void DepthLimitedQuickSort(TKey[] keys, TValue[] values, int left, int right, IComparer<TKey> comparer, int depthLimit) + { + do + { + if (depthLimit == 0) + { + Heapsort(keys, values, left, right, comparer); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + SwapIfGreaterWithItems(keys, values, comparer, i, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, values, comparer, i, j); // swap the low with the high + SwapIfGreaterWithItems(keys, values, comparer, middle, j); // swap the middle with the high + + TKey x = keys[middle]; + do + { + while (comparer.Compare(keys[i], x) < 0) i++; + while (comparer.Compare(x, keys[j]) < 0) j--; + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + if (i < j) + { + TKey key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + if (values != null) + { + TValue value = values[i]; + values[i] = values[j]; + values[j] = value; + } + } + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recursively sort the smaller half. So we subtract one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) DepthLimitedQuickSort(keys, values, left, j, comparer, depthLimit); + left = i; + } + else + { + if (i < right) DepthLimitedQuickSort(keys, values, i, right, comparer, depthLimit); + right = j; + } + } while (left < right); + } + + internal static void IntrospectiveSort(TKey[] keys, TValue[] values, int left, int length, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(comparer != null); + Contract.Requires(left >= 0); + Contract.Requires(length >= 0); + Contract.Requires(length <= keys.Length); + Contract.Requires(length + left <= keys.Length); + Contract.Requires(length + left <= values.Length); + + if (length < 2) + return; + + IntroSort(keys, values, left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2(keys.Length), comparer); + } + + private static void IntroSort(TKey[] keys, TValue[] values, int lo, int hi, int depthLimit, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi < keys.Length); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + SwapIfGreaterWithItems(keys, values, comparer, lo, hi); + return; + } + if (partitionSize == 3) + { + SwapIfGreaterWithItems(keys, values, comparer, lo, hi-1); + SwapIfGreaterWithItems(keys, values, comparer, lo, hi); + SwapIfGreaterWithItems(keys, values, comparer, hi-1, hi); + return; + } + + InsertionSort(keys, values, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + Heapsort(keys, values, lo, hi, comparer); + return; + } + depthLimit--; + + int p = PickPivotAndPartition(keys, values, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(keys, values, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition(TKey[] keys, TValue[] values, int lo, int hi, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + Contract.Ensures(Contract.Result<int>() >= lo && Contract.Result<int>() <= hi); + + // Compute median-of-three. But also partition them, since we've done the comparison. + int middle = lo + ((hi - lo) / 2); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + SwapIfGreaterWithItems(keys, values, comparer, lo, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, values, comparer, lo, hi); // swap the low with the high + SwapIfGreaterWithItems(keys, values, comparer, middle, hi); // swap the middle with the high + + TKey pivot = keys[middle]; + Swap(keys, values, middle, hi - 1); + int left = lo, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. + + while (left < right) + { + while (comparer.Compare(keys[++left], pivot) < 0) ; + while (comparer.Compare(pivot, keys[--right]) < 0) ; + + if (left >= right) + break; + + Swap(keys, values, left, right); + } + + // Put pivot in the right location. + Swap(keys, values, left, (hi - 1)); + return left; + } + + private static void Heapsort(TKey[] keys, TValue[] values, int lo, int hi, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; i = i - 1) + { + DownHeap(keys, values, i, n, lo, comparer); + } + for (int i = n; i > 1; i = i - 1) + { + Swap(keys, values, lo, lo + i - 1); + DownHeap(keys, values, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap(TKey[] keys, TValue[] values, int i, int n, int lo, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + TKey d = keys[lo + i - 1]; + TValue dValue = (values != null) ? values[lo + i - 1] : default(TValue); + int child; + while (i <= n / 2) + { + child = 2 * i; + if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0) + { + child++; + } + if (!(comparer.Compare(d, keys[lo + child - 1]) < 0)) + break; + keys[lo + i - 1] = keys[lo + child - 1]; + if(values != null) + values[lo + i - 1] = values[lo + child - 1]; + i = child; + } + keys[lo + i - 1] = d; + if(values != null) + values[lo + i - 1] = dValue; + } + + private static void InsertionSort(TKey[] keys, TValue[] values, int lo, int hi, IComparer<TKey> comparer) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(comparer != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi >= lo); + Contract.Requires(hi <= keys.Length); + + int i, j; + TKey t; + TValue tValue; + for (i = lo; i < hi; i++) + { + j = i; + t = keys[i + 1]; + tValue = (values != null) ? values[i + 1] : default(TValue); + while (j >= lo && comparer.Compare(t, keys[j]) < 0) + { + keys[j + 1] = keys[j]; + if(values != null) + values[j + 1] = values[j]; + j--; + } + keys[j + 1] = t; + if(values != null) + values[j + 1] = tValue; + } + } + } + + internal class GenericArraySortHelper<TKey, TValue> + : IArraySortHelper<TKey, TValue> + where TKey : IComparable<TKey> + { + public void Sort(TKey[] keys, TValue[] values, int index, int length, IComparer<TKey> comparer) + { + Contract.Assert(keys != null, "Check the arguments in the caller!"); + Contract.Assert( index >= 0 && length >= 0 && (keys.Length - index >= length), "Check the arguments in the caller!"); + + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + try + { + if (comparer == null || comparer == Comparer<TKey>.Default) + { +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + IntrospectiveSort(keys, values, index, length); +#else + // call the faster version of our sort algorithm if the user doesn't provide a comparer + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + IntrospectiveSort(keys, values, index, length); + } + else + { + DepthLimitedQuickSort(keys, values, index, length + index - 1, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + else + { +#if FEATURE_CORECLR + // Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade + // to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka + // IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort. + + ArraySortHelper<TKey, TValue>.IntrospectiveSort(keys, values, index, length, comparer); +#else + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + ArraySortHelper<TKey, TValue>.IntrospectiveSort(keys, values, index, length, comparer); + } + else + { + ArraySortHelper<TKey, TValue>.DepthLimitedQuickSort(keys, values, index, length + index - 1, comparer, IntrospectiveSortUtilities.QuickSortDepthThreshold); + } +#endif + } + + } + catch (IndexOutOfRangeException) + { + IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), e); + } + } + + private static void SwapIfGreaterWithItems(TKey[] keys, TValue[] values, int a, int b) + { + if (a != b) + { + if (keys[a] != null && keys[a].CompareTo(keys[b]) > 0) + { + TKey key = keys[a]; + keys[a] = keys[b]; + keys[b] = key; + if (values != null) + { + TValue value = values[a]; + values[a] = values[b]; + values[b] = value; + } + } + } + } + + private static void Swap(TKey[] keys, TValue[] values, int i, int j) + { + if(i != j) + { + TKey k = keys[i]; + keys[i] = keys[j]; + keys[j] = k; + if(values != null) + { + TValue v = values[i]; + values[i] = values[j]; + values[j] = v; + } + } + } + + private static void DepthLimitedQuickSort(TKey[] keys, TValue[] values, int left, int right, int depthLimit) + { + // The code in this function looks very similar to QuickSort in ArraySortHelper<T> class. + // The difference is that T is constrainted to IComparable<T> here. + // So the IL code will be different. This function is faster than the one in ArraySortHelper<T>. + + do + { + if (depthLimit == 0) + { + Heapsort(keys, values, left, right); + return; + } + + int i = left; + int j = right; + + // pre-sort the low, middle (pivot), and high values in place. + // this improves performance in the face of already sorted data, or + // data that is made up of multiple sorted runs appended together. + int middle = i + ((j - i) >> 1); + SwapIfGreaterWithItems(keys, values, i, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, values, i, j); // swap the low with the high + SwapIfGreaterWithItems(keys, values, middle, j); // swap the middle with the high + + TKey x = keys[middle]; + do + { + if (x == null) + { + // if x null, the loop to find two elements to be switched can be reduced. + while (keys[j] != null) j--; + } + else + { + while (x.CompareTo(keys[i]) > 0) i++; + while (x.CompareTo(keys[j]) < 0) j--; + } + Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); + if (i > j) break; + if (i < j) + { + TKey key = keys[i]; + keys[i] = keys[j]; + keys[j] = key; + if (values != null) + { + TValue value = values[i]; + values[i] = values[j]; + values[j] = value; + } + } + i++; + j--; + } while (i <= j); + + // The next iteration of the while loop is to "recursively" sort the larger half of the array and the + // following calls recursively sort the smaller half. So we subtract one from depthLimit here so + // both sorts see the new value. + depthLimit--; + + if (j - left <= right - i) + { + if (left < j) DepthLimitedQuickSort(keys, values, left, j, depthLimit); + left = i; + } + else + { + if (i < right) DepthLimitedQuickSort(keys, values, i, right, depthLimit); + right = j; + } + } while (left < right); + } + + internal static void IntrospectiveSort(TKey[] keys, TValue[] values, int left, int length) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(left >= 0); + Contract.Requires(length >= 0); + Contract.Requires(length <= keys.Length); + Contract.Requires(length + left <= keys.Length); + Contract.Requires(length + left <= values.Length); + + if (length < 2) + return; + + IntroSort(keys, values, left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2(keys.Length)); + } + + private static void IntroSort(TKey[] keys, TValue[] values, int lo, int hi, int depthLimit) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi < keys.Length); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + SwapIfGreaterWithItems(keys, values, lo, hi); + return; + } + if (partitionSize == 3) + { + SwapIfGreaterWithItems(keys, values, lo, hi-1); + SwapIfGreaterWithItems(keys, values, lo, hi); + SwapIfGreaterWithItems(keys, values, hi-1, hi); + return; + } + + InsertionSort(keys, values, lo, hi); + return; + } + + if (depthLimit == 0) + { + Heapsort(keys, values, lo, hi); + return; + } + depthLimit--; + + int p = PickPivotAndPartition(keys, values, lo, hi); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(keys, values, p + 1, hi, depthLimit); + hi = p - 1; + } + } + + private static int PickPivotAndPartition(TKey[] keys, TValue[] values, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + Contract.Ensures(Contract.Result<int>() >= lo && Contract.Result<int>() <= hi); + + // Compute median-of-three. But also partition them, since we've done the comparison. + int middle = lo + ((hi - lo) / 2); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + SwapIfGreaterWithItems(keys, values, lo, middle); // swap the low with the mid point + SwapIfGreaterWithItems(keys, values, lo, hi); // swap the low with the high + SwapIfGreaterWithItems(keys, values, middle, hi); // swap the middle with the high + + TKey pivot = keys[middle]; + Swap(keys, values, middle, hi - 1); + int left = lo, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. + + while (left < right) + { + if(pivot == null) + { + while (left < (hi - 1) && keys[++left] == null) ; + while (right > lo && keys[--right] != null); + } + else + { + while (pivot.CompareTo(keys[++left]) > 0) ; + while (pivot.CompareTo(keys[--right]) < 0) ; + } + + if (left >= right) + break; + + Swap(keys, values, left, right); + } + + // Put pivot in the right location. + Swap(keys, values, left, (hi - 1)); + return left; + } + + private static void Heapsort(TKey[] keys, TValue[] values, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi > lo); + Contract.Requires(hi < keys.Length); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; i = i - 1) + { + DownHeap(keys, values, i, n, lo); + } + for (int i = n; i > 1; i = i - 1) + { + Swap(keys, values, lo, lo + i - 1); + DownHeap(keys, values, 1, i - 1, lo); + } + } + + private static void DownHeap(TKey[] keys, TValue[] values, int i, int n, int lo) + { + Contract.Requires(keys != null); + Contract.Requires(lo >= 0); + Contract.Requires(lo < keys.Length); + + TKey d = keys[lo + i - 1]; + TValue dValue = (values != null) ? values[lo + i - 1] : default(TValue); + int child; + while (i <= n / 2) + { + child = 2 * i; + if (child < n && (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) + { + child++; + } + if (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(d) < 0) + break; + keys[lo + i - 1] = keys[lo + child - 1]; + if(values != null) + values[lo + i - 1] = values[lo + child - 1]; + i = child; + } + keys[lo + i - 1] = d; + if(values != null) + values[lo + i - 1] = dValue; + } + + private static void InsertionSort(TKey[] keys, TValue[] values, int lo, int hi) + { + Contract.Requires(keys != null); + Contract.Requires(values != null); + Contract.Requires(lo >= 0); + Contract.Requires(hi >= lo); + Contract.Requires(hi <= keys.Length); + + int i, j; + TKey t; + TValue tValue; + for (i = lo; i < hi; i++) + { + j = i; + t = keys[i + 1]; + tValue = (values != null)? values[i + 1] : default(TValue); + while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) + { + keys[j + 1] = keys[j]; + if(values != null) + values[j + 1] = values[j]; + j--; + } + keys[j + 1] = t; + if(values != null) + values[j + 1] = tValue; + } + } + } + + #endregion +} + + diff --git a/src/mscorlib/src/System/Collections/Generic/Comparer.cs b/src/mscorlib/src/System/Collections/Generic/Comparer.cs new file mode 100644 index 0000000000..9f1a8bff6f --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/Comparer.cs @@ -0,0 +1,325 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +//using System.Globalization; +using System.Runtime.CompilerServices; +using System.Security; +using System.Runtime.Serialization; + +namespace System.Collections.Generic +{ + [Serializable] + [TypeDependencyAttribute("System.Collections.Generic.ObjectComparer`1")] + public abstract class Comparer<T> : IComparer, IComparer<T> + { + static readonly Comparer<T> defaultComparer = CreateComparer(); + + public static Comparer<T> Default { + get { + Contract.Ensures(Contract.Result<Comparer<T>>() != null); + return defaultComparer; + } + } + + public static Comparer<T> Create(Comparison<T> comparison) + { + Contract.Ensures(Contract.Result<Comparer<T>>() != null); + + if (comparison == null) + throw new ArgumentNullException("comparison"); + + return new ComparisonComparer<T>(comparison); + } + + // + // Note that logic in this method is replicated in vm\compile.cpp to ensure that NGen + // saves the right instantiations + // + [System.Security.SecuritySafeCritical] // auto-generated + private static Comparer<T> CreateComparer() + { + object result = null; + RuntimeType t = (RuntimeType)typeof(T); + + // If T implements IComparable<T> return a GenericComparer<T> + if (typeof(IComparable<T>).IsAssignableFrom(t)) + { + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer<int>), t); + } + else if (default(T) == null) + { + // If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U> + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { + RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; + if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) { + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer<int>), u); + } + } + } + else if (t.IsEnum) + { + // Explicitly call Enum.GetUnderlyingType here. Although GetTypeCode + // ends up doing this anyway, we end up avoiding an unnecessary P/Invoke + // and virtual method call. + TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t)); + + // Depending on the enum type, we need to special case the comparers so that we avoid boxing + // Specialize differently for signed/unsigned types so we avoid problems with large numbers + switch (underlyingTypeCode) + { + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int32EnumComparer<int>), t); + break; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt32EnumComparer<uint>), t); + break; + // 64-bit enums: use UnsafeEnumCastLong + case TypeCode.Int64: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int64EnumComparer<long>), t); + break; + case TypeCode.UInt64: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt64EnumComparer<ulong>), t); + break; + } + } + + return result != null ? + (Comparer<T>)result : + new ObjectComparer<T>(); // Fallback to ObjectComparer, which uses boxing + } + + public abstract int Compare(T x, T y); + + int IComparer.Compare(object x, object y) { + if (x == null) return y == null ? 0 : -1; + if (y == null) return 1; + if (x is T && y is T) return Compare((T)x, (T)y); + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); + return 0; + } + } + + // Note: although there is a lot of shared code in the following + // comparers, we do not incorporate it into a base class for perf + // reasons. Adding another base class (even one with no fields) + // means another generic instantiation, which can be costly esp. + // for value types. + + [Serializable] + internal sealed class GenericComparer<T> : Comparer<T> where T : IComparable<T> + { + public override int Compare(T x, T y) { + if (x != null) { + if (y != null) return x.CompareTo(y); + return 1; + } + if (y != null) return -1; + return 0; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + } + + [Serializable] + internal sealed class NullableComparer<T> : Comparer<T?> where T : struct, IComparable<T> + { + public override int Compare(Nullable<T> x, Nullable<T> y) { + if (x.HasValue) { + if (y.HasValue) return x.value.CompareTo(y.value); + return 1; + } + if (y.HasValue) return -1; + return 0; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + } + + [Serializable] + internal sealed class ObjectComparer<T> : Comparer<T> + { + public override int Compare(T x, T y) { + return System.Collections.Comparer.Default.Compare(x, y); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + } + + [Serializable] + internal sealed class ComparisonComparer<T> : Comparer<T> + { + private readonly Comparison<T> _comparison; + + public ComparisonComparer(Comparison<T> comparison) { + _comparison = comparison; + } + + public override int Compare(T x, T y) { + return _comparison(x, y); + } + } + + // Enum comparers (specialized to avoid boxing) + // NOTE: Each of these needs to implement ISerializable + // and have a SerializationInfo/StreamingContext ctor, + // since we want to serialize as ObjectComparer for + // back-compat reasons (see below). + + [Serializable] + internal sealed class Int32EnumComparer<T> : Comparer<T>, ISerializable where T : struct + { + public Int32EnumComparer() + { + Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); + } + + // Used by the serialization engine. + private Int32EnumComparer(SerializationInfo info, StreamingContext context) { } + + public override int Compare(T x, T y) + { + int ix = JitHelpers.UnsafeEnumCast(x); + int iy = JitHelpers.UnsafeEnumCast(y); + return ix.CompareTo(iy); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + // Previously Comparer<T> was not specialized for enums, + // and instead fell back to ObjectComparer which uses boxing. + // Set the type as ObjectComparer here so code that serializes + // Comparer for enums will not break. + info.SetType(typeof(ObjectComparer<T>)); + } + } + + [Serializable] + internal sealed class UInt32EnumComparer<T> : Comparer<T>, ISerializable where T : struct + { + public UInt32EnumComparer() + { + Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); + } + + // Used by the serialization engine. + private UInt32EnumComparer(SerializationInfo info, StreamingContext context) { } + + public override int Compare(T x, T y) + { + uint ix = (uint)JitHelpers.UnsafeEnumCast(x); + uint iy = (uint)JitHelpers.UnsafeEnumCast(y); + return ix.CompareTo(iy); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.SetType(typeof(ObjectComparer<T>)); + } + } + + [Serializable] + internal sealed class Int64EnumComparer<T> : Comparer<T>, ISerializable where T : struct + { + public Int64EnumComparer() + { + Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); + } + + // Used by the serialization engine. + private Int64EnumComparer(SerializationInfo info, StreamingContext context) { } + + public override int Compare(T x, T y) + { + long lx = JitHelpers.UnsafeEnumCastLong(x); + long ly = JitHelpers.UnsafeEnumCastLong(y); + return lx.CompareTo(ly); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.SetType(typeof(ObjectComparer<T>)); + } + } + + [Serializable] + internal sealed class UInt64EnumComparer<T> : Comparer<T>, ISerializable where T : struct + { + public UInt64EnumComparer() + { + Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); + } + + // Used by the serialization engine. + private UInt64EnumComparer(SerializationInfo info, StreamingContext context) { } + + public override int Compare(T x, T y) + { + ulong lx = (ulong)JitHelpers.UnsafeEnumCastLong(x); + ulong ly = (ulong)JitHelpers.UnsafeEnumCastLong(y); + return lx.CompareTo(ly); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) => + obj != null && GetType() == obj.GetType(); + + public override int GetHashCode() => + GetType().GetHashCode(); + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.SetType(typeof(ObjectComparer<T>)); + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/DebugView.cs b/src/mscorlib/src/System/Collections/Generic/DebugView.cs new file mode 100644 index 0000000000..57b91eff51 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/DebugView.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** +** +** Purpose: DebugView class for generic collections +** +** +** +** +=============================================================================*/ + +namespace System.Collections.Generic { + using System; + using System.Collections.ObjectModel; + using System.Security.Permissions; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + // + // VS IDE can't differentiate between types with the same name from different + // assembly. So we need to use different names for collection debug view for + // collections in mscorlib.dll and system.dll. + // + internal sealed class Mscorlib_CollectionDebugView<T> { + private ICollection<T> collection; + + public Mscorlib_CollectionDebugView(ICollection<T> collection) { + if (collection == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + + this.collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items { + get { + T[] items = new T[collection.Count]; + collection.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class Mscorlib_DictionaryKeyCollectionDebugView<TKey, TValue> { + private ICollection<TKey> collection; + + public Mscorlib_DictionaryKeyCollectionDebugView(ICollection<TKey> collection) { + if (collection == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + + this.collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public TKey[] Items { + get { + TKey[] items = new TKey[collection.Count]; + collection.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class Mscorlib_DictionaryValueCollectionDebugView<TKey, TValue> { + private ICollection<TValue> collection; + + public Mscorlib_DictionaryValueCollectionDebugView(ICollection<TValue> collection) { + if (collection == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + + this.collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public TValue[] Items { + get { + TValue[] items = new TValue[collection.Count]; + collection.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class Mscorlib_DictionaryDebugView<K, V> { + private IDictionary<K, V> dict; + + public Mscorlib_DictionaryDebugView(IDictionary<K, V> dictionary) { + if (dictionary == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + + this.dict = dictionary; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair<K, V>[] Items { + get { + KeyValuePair<K, V>[] items = new KeyValuePair<K, V>[dict.Count]; + dict.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class Mscorlib_KeyedCollectionDebugView<K, T> { + private KeyedCollection<K, T> kc; + + public Mscorlib_KeyedCollectionDebugView(KeyedCollection<K, T> keyedCollection) { + if (keyedCollection == null) { + throw new ArgumentNullException("keyedCollection"); + } + Contract.EndContractBlock(); + + kc = keyedCollection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items { + get { + T[] items = new T[kc.Count]; + kc.CopyTo(items, 0); + return items; + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/Dictionary.cs b/src/mscorlib/src/System/Collections/Generic/Dictionary.cs new file mode 100644 index 0000000000..9cbfff5a57 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/Dictionary.cs @@ -0,0 +1,1187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** Purpose: Generic hash table implementation +** +** #DictionaryVersusHashtableThreadSafety +** Hashtable has multiple reader/single writer (MR/SW) thread safety built into +** certain methods and properties, whereas Dictionary doesn't. If you're +** converting framework code that formerly used Hashtable to Dictionary, it's +** important to consider whether callers may have taken a dependence on MR/SW +** thread safety. If a reader writer lock is available, then that may be used +** with a Dictionary to get the same thread safety guarantee. +** +** Reader writer locks don't exist in silverlight, so we do the following as a +** result of removing non-generic collections from silverlight: +** 1. If the Hashtable was fully synchronized, then we replace it with a +** Dictionary with full locks around reads/writes (same thread safety +** guarantee). +** 2. Otherwise, the Hashtable has the default MR/SW thread safety behavior, +** so we do one of the following on a case-by-case basis: +** a. If the race condition can be addressed by rearranging the code and using a temp +** variable (for example, it's only populated immediately after created) +** then we address the race condition this way and use Dictionary. +** b. If there's concern about degrading performance with the increased +** locking, we ifdef with FEATURE_NONGENERIC_COLLECTIONS so we can at +** least use Hashtable in the desktop build, but Dictionary with full +** locks in silverlight builds. Note that this is heavier locking than +** MR/SW, but this is the only option without rewriting (or adding back) +** the reader writer lock. +** c. If there's no performance concern (e.g. debug-only code) we +** consistently replace Hashtable with Dictionary plus full locks to +** reduce complexity. +** d. Most of serialization is dead code in silverlight. Instead of updating +** those Hashtable occurences in serialization, we carved out references +** to serialization such that this code doesn't need to build in +** silverlight. +===========================================================*/ +namespace System.Collections.Generic { + + using System; + using System.Collections; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Runtime.Serialization; + using System.Security.Permissions; + + [DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + [System.Runtime.InteropServices.ComVisible(false)] + public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback { + + private struct Entry { + public int hashCode; // Lower 31 bits of hash code, -1 if unused + public int next; // Index of next entry, -1 if last + public TKey key; // Key of entry + public TValue value; // Value of entry + } + + private int[] buckets; + private Entry[] entries; + private int count; + private int version; + private int freeList; + private int freeCount; + private IEqualityComparer<TKey> comparer; + private KeyCollection keys; + private ValueCollection values; + private Object _syncRoot; + + // constants for serialization + private const String VersionName = "Version"; + private const String HashSizeName = "HashSize"; // Must save buckets.Length + private const String KeyValuePairsName = "KeyValuePairs"; + private const String ComparerName = "Comparer"; + + public Dictionary(): this(0, null) {} + + public Dictionary(int capacity): this(capacity, null) {} + + public Dictionary(IEqualityComparer<TKey> comparer): this(0, comparer) {} + + public Dictionary(int capacity, IEqualityComparer<TKey> comparer) { + if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + if (capacity > 0) Initialize(capacity); + this.comparer = comparer ?? EqualityComparer<TKey>.Default; + +#if FEATURE_RANDOMIZED_STRING_HASHING && FEATURE_CORECLR + if (HashHelpers.s_UseRandomizedStringHashing && comparer == EqualityComparer<string>.Default) + { + this.comparer = (IEqualityComparer<TKey>) NonRandomizedStringEqualityComparer.Default; + } +#endif // FEATURE_RANDOMIZED_STRING_HASHING && FEATURE_CORECLR + } + + public Dictionary(IDictionary<TKey,TValue> dictionary): this(dictionary, null) {} + + public Dictionary(IDictionary<TKey,TValue> dictionary, IEqualityComparer<TKey> comparer): + this(dictionary != null? dictionary.Count: 0, comparer) { + + if( dictionary == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + } + + // It is likely that the passed-in dictionary is Dictionary<TKey,TValue>. When this is the case, + // avoid the enumerator allocation and overhead by looping through the entries array directly. + // We only do this when dictionary is Dictionary<TKey,TValue> and not a subclass, to maintain + // back-compat with subclasses that may have overridden the enumerator behavior. + if (dictionary.GetType() == typeof(Dictionary<TKey,TValue>)) { + Dictionary<TKey,TValue> d = (Dictionary<TKey,TValue>)dictionary; + int count = d.count; + Entry[] entries = d.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) { + Add(entries[i].key, entries[i].value); + } + } + return; + } + + foreach (KeyValuePair<TKey,TValue> pair in dictionary) { + Add(pair.Key, pair.Value); + } + } + + protected Dictionary(SerializationInfo info, StreamingContext context) { + //We can't do anything with the keys and values until the entire graph has been deserialized + //and we have a resonable estimate that GetHashCode is not going to fail. For the time being, + //we'll just cache this. The graph is not valid until OnDeserialization has been called. + HashHelpers.SerializationInfoTable.Add(this, info); + } + + public IEqualityComparer<TKey> Comparer { + get { + return comparer; + } + } + + public int Count { + get { return count - freeCount; } + } + + public KeyCollection Keys { + get { + Contract.Ensures(Contract.Result<KeyCollection>() != null); + if (keys == null) keys = new KeyCollection(this); + return keys; + } + } + + ICollection<TKey> IDictionary<TKey, TValue>.Keys { + get { + if (keys == null) keys = new KeyCollection(this); + return keys; + } + } + + IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys { + get { + if (keys == null) keys = new KeyCollection(this); + return keys; + } + } + + public ValueCollection Values { + get { + Contract.Ensures(Contract.Result<ValueCollection>() != null); + if (values == null) values = new ValueCollection(this); + return values; + } + } + + ICollection<TValue> IDictionary<TKey, TValue>.Values { + get { + if (values == null) values = new ValueCollection(this); + return values; + } + } + + IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values { + get { + if (values == null) values = new ValueCollection(this); + return values; + } + } + + public TValue this[TKey key] { + get { + int i = FindEntry(key); + if (i >= 0) return entries[i].value; + ThrowHelper.ThrowKeyNotFoundException(); + return default(TValue); + } + set { + Insert(key, value, false); + } + } + + public void Add(TKey key, TValue value) { + Insert(key, value, true); + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair) { + Add(keyValuePair.Key, keyValuePair.Value); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair) { + int i = FindEntry(keyValuePair.Key); + if( i >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, keyValuePair.Value)) { + return true; + } + return false; + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair) { + int i = FindEntry(keyValuePair.Key); + if( i >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, keyValuePair.Value)) { + Remove(keyValuePair.Key); + return true; + } + return false; + } + + public void Clear() { + if (count > 0) { + for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; + Array.Clear(entries, 0, count); + freeList = -1; + count = 0; + freeCount = 0; + version++; + } + } + + public bool ContainsKey(TKey key) { + return FindEntry(key) >= 0; + } + + public bool ContainsValue(TValue value) { + if (value == null) { + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0 && entries[i].value == null) return true; + } + } + else { + EqualityComparer<TValue> c = EqualityComparer<TValue>.Default; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true; + } + } + return false; + } + + private void CopyTo(KeyValuePair<TKey,TValue>[] array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0 || index > array.Length ) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + int count = this.count; + Entry[] entries = this.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) { + array[index++] = new KeyValuePair<TKey,TValue>(entries[i].key, entries[i].value); + } + } + } + + public Enumerator GetEnumerator() { + return new Enumerator(this, Enumerator.KeyValuePair); + } + + IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() { + return new Enumerator(this, Enumerator.KeyValuePair); + } + + [System.Security.SecurityCritical] // auto-generated_required + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { + if (info==null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + info.AddValue(VersionName, version); + +#if FEATURE_RANDOMIZED_STRING_HASHING + info.AddValue(ComparerName, HashHelpers.GetEqualityComparerForSerialization(comparer), typeof(IEqualityComparer<TKey>)); +#else + info.AddValue(ComparerName, comparer, typeof(IEqualityComparer<TKey>)); +#endif + + info.AddValue(HashSizeName, buckets == null ? 0 : buckets.Length); //This is the length of the bucket array. + if( buckets != null) { + KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count]; + CopyTo(array, 0); + info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[])); + } + } + + private int FindEntry(TKey key) { + if( key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (buckets != null) { + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) { + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; + } + } + return -1; + } + + private void Initialize(int capacity) { + int size = HashHelpers.GetPrime(capacity); + buckets = new int[size]; + for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; + entries = new Entry[size]; + freeList = -1; + } + + private void Insert(TKey key, TValue value, bool add) { + + if( key == null ) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (buckets == null) Initialize(0); + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + int targetBucket = hashCode % buckets.Length; + +#if FEATURE_RANDOMIZED_STRING_HASHING + int collisionCount = 0; +#endif + + for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) { + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { + if (add) { +#if FEATURE_CORECLR + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); +#else + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate); +#endif + } + entries[i].value = value; + version++; + return; + } + +#if FEATURE_RANDOMIZED_STRING_HASHING + collisionCount++; +#endif + } + int index; + if (freeCount > 0) { + index = freeList; + freeList = entries[index].next; + freeCount--; + } + else { + if (count == entries.Length) + { + Resize(); + targetBucket = hashCode % buckets.Length; + } + index = count; + count++; + } + + entries[index].hashCode = hashCode; + entries[index].next = buckets[targetBucket]; + entries[index].key = key; + entries[index].value = value; + buckets[targetBucket] = index; + version++; + +#if FEATURE_RANDOMIZED_STRING_HASHING + +#if FEATURE_CORECLR + // In case we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing + // in this case will be EqualityComparer<string>.Default. + // Note, randomized string hashing is turned on by default on coreclr so EqualityComparer<string>.Default will + // be using randomized string hashing + + if (collisionCount > HashHelpers.HashCollisionThreshold && comparer == NonRandomizedStringEqualityComparer.Default) + { + comparer = (IEqualityComparer<TKey>) EqualityComparer<string>.Default; + Resize(entries.Length, true); + } +#else + if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer)) + { + comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer); + Resize(entries.Length, true); + } +#endif // FEATURE_CORECLR + +#endif + + } + + public virtual void OnDeserialization(Object sender) { + SerializationInfo siInfo; + HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo); + + if (siInfo==null) { + // It might be necessary to call OnDeserialization from a container if the container object also implements + // OnDeserialization. However, remoting will call OnDeserialization again. + // We can return immediately if this function is called twice. + // Note we set remove the serialization info from the table at the end of this method. + return; + } + + int realVersion = siInfo.GetInt32(VersionName); + int hashsize = siInfo.GetInt32(HashSizeName); + comparer = (IEqualityComparer<TKey>)siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>)); + + if( hashsize != 0) { + buckets = new int[hashsize]; + for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; + entries = new Entry[hashsize]; + freeList = -1; + + KeyValuePair<TKey, TValue>[] array = (KeyValuePair<TKey, TValue>[]) + siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[])); + + if (array==null) { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingKeys); + } + + for (int i=0; i<array.Length; i++) { + if ( array[i].Key == null) { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_NullKey); + } + Insert(array[i].Key, array[i].Value, true); + } + } + else { + buckets = null; + } + + version = realVersion; + HashHelpers.SerializationInfoTable.Remove(this); + } + + private void Resize() { + Resize(HashHelpers.ExpandPrime(count), false); + } + + private void Resize(int newSize, bool forceNewHashCodes) { + Contract.Assert(newSize >= entries.Length); + int[] newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; + Entry[] newEntries = new Entry[newSize]; + Array.Copy(entries, 0, newEntries, 0, count); + if(forceNewHashCodes) { + for (int i = 0; i < count; i++) { + if(newEntries[i].hashCode != -1) { + newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF); + } + } + } + for (int i = 0; i < count; i++) { + if (newEntries[i].hashCode >= 0) { + int bucket = newEntries[i].hashCode % newSize; + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + buckets = newBuckets; + entries = newEntries; + } + + public bool Remove(TKey key) { + if(key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (buckets != null) { + int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + int bucket = hashCode % buckets.Length; + int last = -1; + for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) { + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { + if (last < 0) { + buckets[bucket] = entries[i].next; + } + else { + entries[last].next = entries[i].next; + } + entries[i].hashCode = -1; + entries[i].next = freeList; + entries[i].key = default(TKey); + entries[i].value = default(TValue); + freeList = i; + freeCount++; + version++; + return true; + } + } + } + return false; + } + + public bool TryGetValue(TKey key, out TValue value) { + int i = FindEntry(key); + if (i >= 0) { + value = entries[i].value; + return true; + } + value = default(TValue); + return false; + } + + // This is a convenience method for the internal callers that were converted from using Hashtable. + // Many were combining key doesn't exist and key exists but null value (for non-value types) checks. + // This allows them to continue getting that behavior with minimal code delta. This is basically + // TryGetValue without the out param + internal TValue GetValueOrDefault(TKey key) { + int i = FindEntry(key); + if (i >= 0) { + return entries[i].value; + } + return default(TValue); + } + + bool ICollection<KeyValuePair<TKey,TValue>>.IsReadOnly { + get { return false; } + } + + void ICollection<KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair<TKey,TValue>[] array, int index) { + CopyTo(array, index); + } + + void ICollection.CopyTo(Array array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if( array.GetLowerBound(0) != 0 ) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + KeyValuePair<TKey,TValue>[] pairs = array as KeyValuePair<TKey,TValue>[]; + if (pairs != null) { + CopyTo(pairs, index); + } + else if( array is DictionaryEntry[]) { + DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; + Entry[] entries = this.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) { + dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); + } + } + } + else { + object[] objects = array as object[]; + if (objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try { + int count = this.count; + Entry[] entries = this.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) { + objects[index++] = new KeyValuePair<TKey,TValue>(entries[i].key, entries[i].value); + } + } + } + catch(ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return new Enumerator(this, Enumerator.KeyValuePair); + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + bool IDictionary.IsFixedSize { + get { return false; } + } + + bool IDictionary.IsReadOnly { + get { return false; } + } + + ICollection IDictionary.Keys { + get { return (ICollection)Keys; } + } + + ICollection IDictionary.Values { + get { return (ICollection)Values; } + } + + object IDictionary.this[object key] { + get { + if( IsCompatibleKey(key)) { + int i = FindEntry((TKey)key); + if (i >= 0) { + return entries[i].value; + } + } + return null; + } + set { + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value); + + try { + TKey tempKey = (TKey)key; + try { + this[tempKey] = (TValue)value; + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue)); + } + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey)); + } + } + } + + private static bool IsCompatibleKey(object key) { + if( key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + return (key is TKey); + } + + void IDictionary.Add(object key, object value) { + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value); + + try { + TKey tempKey = (TKey)key; + + try { + Add(tempKey, (TValue)value); + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue)); + } + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey)); + } + } + + bool IDictionary.Contains(object key) { + if(IsCompatibleKey(key)) { + return ContainsKey((TKey)key); + } + + return false; + } + + IDictionaryEnumerator IDictionary.GetEnumerator() { + return new Enumerator(this, Enumerator.DictEntry); + } + + void IDictionary.Remove(object key) { + if(IsCompatibleKey(key)) { + Remove((TKey)key); + } + } + + [Serializable] + public struct Enumerator: IEnumerator<KeyValuePair<TKey,TValue>>, + IDictionaryEnumerator + { + private Dictionary<TKey,TValue> dictionary; + private int version; + private int index; + private KeyValuePair<TKey,TValue> current; + private int getEnumeratorRetType; // What should Enumerator.Current return? + + internal const int DictEntry = 1; + internal const int KeyValuePair = 2; + + internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) { + this.dictionary = dictionary; + version = dictionary.version; + index = 0; + this.getEnumeratorRetType = getEnumeratorRetType; + current = new KeyValuePair<TKey, TValue>(); + } + + public bool MoveNext() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. + // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue + while ((uint)index < (uint)dictionary.count) { + if (dictionary.entries[index].hashCode >= 0) { + current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value); + index++; + return true; + } + index++; + } + + index = dictionary.count + 1; + current = new KeyValuePair<TKey, TValue>(); + return false; + } + + public KeyValuePair<TKey,TValue> Current { + get { return current; } + } + + public void Dispose() { + } + + object IEnumerator.Current { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + if (getEnumeratorRetType == DictEntry) { + return new System.Collections.DictionaryEntry(current.Key, current.Value); + } else { + return new KeyValuePair<TKey, TValue>(current.Key, current.Value); + } + } + } + + void IEnumerator.Reset() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + index = 0; + current = new KeyValuePair<TKey, TValue>(); + } + + DictionaryEntry IDictionaryEnumerator.Entry { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return new DictionaryEntry(current.Key, current.Value); + } + } + + object IDictionaryEnumerator.Key { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return current.Key; + } + } + + object IDictionaryEnumerator.Value { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return current.Value; + } + } + } + + [DebuggerTypeProxy(typeof(Mscorlib_DictionaryKeyCollectionDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public sealed class KeyCollection: ICollection<TKey>, ICollection, IReadOnlyCollection<TKey> + { + private Dictionary<TKey,TValue> dictionary; + + public KeyCollection(Dictionary<TKey,TValue> dictionary) { + if (dictionary == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + } + this.dictionary = dictionary; + } + + public Enumerator GetEnumerator() { + return new Enumerator(dictionary); + } + + public void CopyTo(TKey[] array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < dictionary.Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + int count = dictionary.count; + Entry[] entries = dictionary.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) array[index++] = entries[i].key; + } + } + + public int Count { + get { return dictionary.Count; } + } + + bool ICollection<TKey>.IsReadOnly { + get { return true; } + } + + void ICollection<TKey>.Add(TKey item){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); + } + + void ICollection<TKey>.Clear(){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); + } + + bool ICollection<TKey>.Contains(TKey item){ + return dictionary.ContainsKey(item); + } + + bool ICollection<TKey>.Remove(TKey item){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); + return false; + } + + IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator() { + return new Enumerator(dictionary); + } + + IEnumerator IEnumerable.GetEnumerator() { + return new Enumerator(dictionary); + } + + void ICollection.CopyTo(Array array, int index) { + if (array==null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if( array.GetLowerBound(0) != 0 ) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < dictionary.Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + TKey[] keys = array as TKey[]; + if (keys != null) { + CopyTo(keys, index); + } + else { + object[] objects = array as object[]; + if (objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + int count = dictionary.count; + Entry[] entries = dictionary.entries; + try { + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) objects[index++] = entries[i].key; + } + } + catch(ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + Object ICollection.SyncRoot { + get { return ((ICollection)dictionary).SyncRoot; } + } + + [Serializable] + public struct Enumerator : IEnumerator<TKey>, System.Collections.IEnumerator + { + private Dictionary<TKey, TValue> dictionary; + private int index; + private int version; + private TKey currentKey; + + internal Enumerator(Dictionary<TKey, TValue> dictionary) { + this.dictionary = dictionary; + version = dictionary.version; + index = 0; + currentKey = default(TKey); + } + + public void Dispose() { + } + + public bool MoveNext() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + while ((uint)index < (uint)dictionary.count) { + if (dictionary.entries[index].hashCode >= 0) { + currentKey = dictionary.entries[index].key; + index++; + return true; + } + index++; + } + + index = dictionary.count + 1; + currentKey = default(TKey); + return false; + } + + public TKey Current { + get { + return currentKey; + } + } + + Object System.Collections.IEnumerator.Current { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return currentKey; + } + } + + void System.Collections.IEnumerator.Reset() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + index = 0; + currentKey = default(TKey); + } + } + } + + [DebuggerTypeProxy(typeof(Mscorlib_DictionaryValueCollectionDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public sealed class ValueCollection: ICollection<TValue>, ICollection, IReadOnlyCollection<TValue> + { + private Dictionary<TKey,TValue> dictionary; + + public ValueCollection(Dictionary<TKey,TValue> dictionary) { + if (dictionary == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + } + this.dictionary = dictionary; + } + + public Enumerator GetEnumerator() { + return new Enumerator(dictionary); + } + + public void CopyTo(TValue[] array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < dictionary.Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + int count = dictionary.count; + Entry[] entries = dictionary.entries; + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) array[index++] = entries[i].value; + } + } + + public int Count { + get { return dictionary.Count; } + } + + bool ICollection<TValue>.IsReadOnly { + get { return true; } + } + + void ICollection<TValue>.Add(TValue item){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); + } + + bool ICollection<TValue>.Remove(TValue item){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); + return false; + } + + void ICollection<TValue>.Clear(){ + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); + } + + bool ICollection<TValue>.Contains(TValue item){ + return dictionary.ContainsValue(item); + } + + IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() { + return new Enumerator(dictionary); + } + + IEnumerator IEnumerable.GetEnumerator() { + return new Enumerator(dictionary); + } + + void ICollection.CopyTo(Array array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if( array.GetLowerBound(0) != 0 ) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < dictionary.Count) + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + + TValue[] values = array as TValue[]; + if (values != null) { + CopyTo(values, index); + } + else { + object[] objects = array as object[]; + if (objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + int count = dictionary.count; + Entry[] entries = dictionary.entries; + try { + for (int i = 0; i < count; i++) { + if (entries[i].hashCode >= 0) objects[index++] = entries[i].value; + } + } + catch(ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + Object ICollection.SyncRoot { + get { return ((ICollection)dictionary).SyncRoot; } + } + + [Serializable] + public struct Enumerator : IEnumerator<TValue>, System.Collections.IEnumerator + { + private Dictionary<TKey, TValue> dictionary; + private int index; + private int version; + private TValue currentValue; + + internal Enumerator(Dictionary<TKey, TValue> dictionary) { + this.dictionary = dictionary; + version = dictionary.version; + index = 0; + currentValue = default(TValue); + } + + public void Dispose() { + } + + public bool MoveNext() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + while ((uint)index < (uint)dictionary.count) { + if (dictionary.entries[index].hashCode >= 0) { + currentValue = dictionary.entries[index].value; + index++; + return true; + } + index++; + } + index = dictionary.count + 1; + currentValue = default(TValue); + return false; + } + + public TValue Current { + get { + return currentValue; + } + } + + Object System.Collections.IEnumerator.Current { + get { + if( index == 0 || (index == dictionary.count + 1)) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return currentValue; + } + } + + void System.Collections.IEnumerator.Reset() { + if (version != dictionary.version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + index = 0; + currentValue = default(TValue); + } + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs new file mode 100644 index 0000000000..b845d64fed --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs @@ -0,0 +1,621 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Security; +using System.Runtime.Serialization; + +namespace System.Collections.Generic +{ + using System.Globalization; + using System.Runtime; + using System.Runtime.CompilerServices; + using System.Diagnostics.Contracts; + + [Serializable] + [TypeDependencyAttribute("System.Collections.Generic.ObjectEqualityComparer`1")] + public abstract class EqualityComparer<T> : IEqualityComparer, IEqualityComparer<T> + { + static readonly EqualityComparer<T> defaultComparer = CreateComparer(); + + public static EqualityComparer<T> Default { + get { + Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null); + return defaultComparer; + } + } + + // + // Note that logic in this method is replicated in vm\compile.cpp to ensure that NGen + // saves the right instantiations + // + [System.Security.SecuritySafeCritical] // auto-generated + private static EqualityComparer<T> CreateComparer() + { + Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null); + + object result = null; + RuntimeType t = (RuntimeType)typeof(T); + + // Specialize type byte for performance reasons + if (t == typeof(byte)) { + result = new ByteEqualityComparer(); + } + // If T implements IEquatable<T> return a GenericEqualityComparer<T> + else if (typeof(IEquatable<T>).IsAssignableFrom(t)) + { + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), t); + } + else if (default(T) == null) // Reference type/Nullable + { + // If T is a Nullable<U> where U implements IEquatable<U> return a NullableEqualityComparer<U> + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { + RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; + if (typeof(IEquatable<>).MakeGenericType(u).IsAssignableFrom(u)) { + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableEqualityComparer<int>), u); + } + } + } + // See the METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST and METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST_LONG cases in getILIntrinsicImplementation + else if (t.IsEnum) { + TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t)); + + // Depending on the enum type, we need to special case the comparers so that we avoid boxing + // Note: We have different comparers for Short and SByte because for those types we need to make sure we call GetHashCode on the actual underlying type as the + // implementation of GetHashCode is more complex than for the other types. + switch (underlyingTypeCode) { + case TypeCode.Int16: // short + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ShortEnumEqualityComparer<short>), t); + break; + case TypeCode.SByte: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(SByteEnumEqualityComparer<sbyte>), t); + break; + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Byte: + case TypeCode.UInt16: //ushort + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<int>), t); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(LongEnumEqualityComparer<long>), t); + break; + } + } + + return result != null ? + (EqualityComparer<T>)result : + new ObjectEqualityComparer<T>(); // Fallback to ObjectEqualityComparer, which uses boxing + } + + [Pure] + public abstract bool Equals(T x, T y); + [Pure] + public abstract int GetHashCode(T obj); + + internal virtual int IndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex + count; + for (int i = startIndex; i < endIndex; i++) { + if (Equals(array[i], value)) return i; + } + return -1; + } + + internal virtual int LastIndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex - count + 1; + for (int i = startIndex; i >= endIndex; i--) { + if (Equals(array[i], value)) return i; + } + return -1; + } + + int IEqualityComparer.GetHashCode(object obj) { + if (obj == null) return 0; + if (obj is T) return GetHashCode((T)obj); + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); + return 0; + } + + bool IEqualityComparer.Equals(object x, object y) { + if (x == y) return true; + if (x == null || y == null) return false; + if ((x is T) && (y is T)) return Equals((T)x, (T)y); + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); + return false; + } + } + + // The methods in this class look identical to the inherited methods, but the calls + // to Equal bind to IEquatable<T>.Equals(T) instead of Object.Equals(Object) + [Serializable] + internal class GenericEqualityComparer<T>: EqualityComparer<T> where T: IEquatable<T> + { + [Pure] + public override bool Equals(T x, T y) { + if (x != null) { + if (y != null) return x.Equals(y); + return false; + } + if (y != null) return false; + return true; + } + + [Pure] + public override int GetHashCode(T obj) { + if (obj == null) return 0; + return obj.GetHashCode(); + } + + internal override int IndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex + count; + if (value == null) { + for (int i = startIndex; i < endIndex; i++) { + if (array[i] == null) return i; + } + } + else { + for (int i = startIndex; i < endIndex; i++) { + if (array[i] != null && array[i].Equals(value)) return i; + } + } + return -1; + } + + internal override int LastIndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex - count + 1; + if (value == null) { + for (int i = startIndex; i >= endIndex; i--) { + if (array[i] == null) return i; + } + } + else { + for (int i = startIndex; i >= endIndex; i--) { + if (array[i] != null && array[i].Equals(value)) return i; + } + } + return -1; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + GenericEqualityComparer<T> comparer = obj as GenericEqualityComparer<T>; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + } + + [Serializable] + internal class NullableEqualityComparer<T> : EqualityComparer<Nullable<T>> where T : struct, IEquatable<T> + { + [Pure] + public override bool Equals(Nullable<T> x, Nullable<T> y) { + if (x.HasValue) { + if (y.HasValue) return x.value.Equals(y.value); + return false; + } + if (y.HasValue) return false; + return true; + } + + [Pure] + public override int GetHashCode(Nullable<T> obj) { + return obj.GetHashCode(); + } + + internal override int IndexOf(Nullable<T>[] array, Nullable<T> value, int startIndex, int count) { + int endIndex = startIndex + count; + if (!value.HasValue) { + for (int i = startIndex; i < endIndex; i++) { + if (!array[i].HasValue) return i; + } + } + else { + for (int i = startIndex; i < endIndex; i++) { + if (array[i].HasValue && array[i].value.Equals(value.value)) return i; + } + } + return -1; + } + + internal override int LastIndexOf(Nullable<T>[] array, Nullable<T> value, int startIndex, int count) { + int endIndex = startIndex - count + 1; + if (!value.HasValue) { + for (int i = startIndex; i >= endIndex; i--) { + if (!array[i].HasValue) return i; + } + } + else { + for (int i = startIndex; i >= endIndex; i--) { + if (array[i].HasValue && array[i].value.Equals(value.value)) return i; + } + } + return -1; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + NullableEqualityComparer<T> comparer = obj as NullableEqualityComparer<T>; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + } + + [Serializable] + internal class ObjectEqualityComparer<T>: EqualityComparer<T> + { + [Pure] + public override bool Equals(T x, T y) { + if (x != null) { + if (y != null) return x.Equals(y); + return false; + } + if (y != null) return false; + return true; + } + + [Pure] + public override int GetHashCode(T obj) { + if (obj == null) return 0; + return obj.GetHashCode(); + } + + internal override int IndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex + count; + if (value == null) { + for (int i = startIndex; i < endIndex; i++) { + if (array[i] == null) return i; + } + } + else { + for (int i = startIndex; i < endIndex; i++) { + if (array[i] != null && array[i].Equals(value)) return i; + } + } + return -1; + } + + internal override int LastIndexOf(T[] array, T value, int startIndex, int count) { + int endIndex = startIndex - count + 1; + if (value == null) { + for (int i = startIndex; i >= endIndex; i--) { + if (array[i] == null) return i; + } + } + else { + for (int i = startIndex; i >= endIndex; i--) { + if (array[i] != null && array[i].Equals(value)) return i; + } + } + return -1; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + ObjectEqualityComparer<T> comparer = obj as ObjectEqualityComparer<T>; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + } + +#if FEATURE_CORECLR + // NonRandomizedStringEqualityComparer is the comparer used by default with the Dictionary<string,...> + // As the randomized string hashing is turned on by default on coreclr, we need to keep the performance not affected + // as much as possible in the main stream scenarios like Dictionary<string,> + // We use NonRandomizedStringEqualityComparer as default comparer as it doesnt use the randomized string hashing which + // keep the perofrmance not affected till we hit collision threshold and then we switch to the comparer which is using + // randomized string hashing GenericEqualityComparer<string> + + internal class NonRandomizedStringEqualityComparer : GenericEqualityComparer<string> { + static IEqualityComparer<string> s_nonRandomizedComparer; + + internal static new IEqualityComparer<string> Default { + get { + if (s_nonRandomizedComparer == null) { + s_nonRandomizedComparer = new NonRandomizedStringEqualityComparer(); + } + return s_nonRandomizedComparer; + } + } + + [Pure] + public override int GetHashCode(string obj) { + if (obj == null) return 0; + return obj.GetLegacyNonRandomizedHashCode(); + } + } +#endif // FEATURE_CORECLR + + // Performance of IndexOf on byte array is very important for some scenarios. + // We will call the C runtime function memchr, which is optimized. + [Serializable] + internal class ByteEqualityComparer: EqualityComparer<byte> + { + [Pure] + public override bool Equals(byte x, byte y) { + return x == y; + } + + [Pure] + public override int GetHashCode(byte b) { + return b.GetHashCode(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal unsafe override int IndexOf(byte[] array, byte value, int startIndex, int count) { + if (array==null) + throw new ArgumentNullException("array"); + if (startIndex < 0) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + if (count > array.Length - startIndex) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + if (count == 0) return -1; + fixed (byte* pbytes = array) { + return Buffer.IndexOfByte(pbytes, value, startIndex, count); + } + } + + internal override int LastIndexOf(byte[] array, byte value, int startIndex, int count) { + int endIndex = startIndex - count + 1; + for (int i = startIndex; i >= endIndex; i--) { + if (array[i] == value) return i; + } + return -1; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + ByteEqualityComparer comparer = obj as ByteEqualityComparer; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + } + + [Serializable] + internal class EnumEqualityComparer<T> : EqualityComparer<T>, ISerializable where T : struct + { + [Pure] + public override bool Equals(T x, T y) { + int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(x); + int y_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(y); + return x_final == y_final; + } + + [Pure] + public override int GetHashCode(T obj) { + int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); + return x_final.GetHashCode(); + } + + public EnumEqualityComparer() { } + + // This is used by the serialization engine. + protected EnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) { + // For back-compat we need to serialize the comparers for enums with underlying types other than int as ObjectEqualityComparer + if (Type.GetTypeCode(Enum.GetUnderlyingType(typeof(T))) != TypeCode.Int32) { + info.SetType(typeof(ObjectEqualityComparer<T>)); + } + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + EnumEqualityComparer<T> comparer = obj as EnumEqualityComparer<T>; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + } + + [Serializable] + internal sealed class SByteEnumEqualityComparer<T> : EnumEqualityComparer<T>, ISerializable where T : struct + { + public SByteEnumEqualityComparer() { } + + // This is used by the serialization engine. + public SByteEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + + [Pure] + public override int GetHashCode(T obj) { + int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); + return ((sbyte)x_final).GetHashCode(); + } + } + + [Serializable] + internal sealed class ShortEnumEqualityComparer<T> : EnumEqualityComparer<T>, ISerializable where T : struct + { + public ShortEnumEqualityComparer() { } + + // This is used by the serialization engine. + public ShortEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + + [Pure] + public override int GetHashCode(T obj) { + int x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast(obj); + return ((short)x_final).GetHashCode(); + } + } + + [Serializable] + internal sealed class LongEnumEqualityComparer<T> : EqualityComparer<T>, ISerializable where T : struct + { + [Pure] + public override bool Equals(T x, T y) { + long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(x); + long y_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(y); + return x_final == y_final; + } + + [Pure] + public override int GetHashCode(T obj) { + long x_final = System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong(obj); + return x_final.GetHashCode(); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + LongEnumEqualityComparer<T> comparer = obj as LongEnumEqualityComparer<T>; + return comparer != null; + } + + public override int GetHashCode() { + return this.GetType().Name.GetHashCode(); + } + + public LongEnumEqualityComparer() { } + + // This is used by the serialization engine. + public LongEnumEqualityComparer(SerializationInfo information, StreamingContext context) { } + + [SecurityCritical] + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + // The LongEnumEqualityComparer does not exist on 4.0 so we need to serialize this comparer as ObjectEqualityComparer + // to allow for roundtrip between 4.0 and 4.5. + info.SetType(typeof(ObjectEqualityComparer<T>)); + } + } + +#if FEATURE_RANDOMIZED_STRING_HASHING + // This type is not serializeable by design. It does not exist in previous versions and will be removed + // Once we move the framework to using secure hashing by default. + internal sealed class RandomizedStringEqualityComparer : IEqualityComparer<String>, IEqualityComparer, IWellKnownStringEqualityComparer + { + private long _entropy; + + public RandomizedStringEqualityComparer() { + _entropy = HashHelpers.GetEntropy(); + } + + public new bool Equals(object x, object y) { + if (x == y) return true; + if (x == null || y == null) return false; + if ((x is string) && (y is string)) return Equals((string)x, (string)y); + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); + return false; + } + + [Pure] + public bool Equals(string x, string y) { + if (x != null) { + if (y != null) return x.Equals(y); + return false; + } + if (y != null) return false; + return true; + } + + [Pure] + [SecuritySafeCritical] + public int GetHashCode(String obj) { + if(obj == null) return 0; + return String.InternalMarvin32HashString(obj, obj.Length, _entropy); + } + + [Pure] + [SecuritySafeCritical] + public int GetHashCode(Object obj) { + if(obj == null) return 0; + + string sObj = obj as string; + if(sObj != null) return String.InternalMarvin32HashString(sObj, sObj.Length, _entropy); + + return obj.GetHashCode(); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) { + RandomizedStringEqualityComparer comparer = obj as RandomizedStringEqualityComparer; + return (comparer != null) && (this._entropy == comparer._entropy); + } + + public override int GetHashCode() { + return (this.GetType().Name.GetHashCode() ^ ((int) (_entropy & 0x7FFFFFFF))); + } + + + IEqualityComparer IWellKnownStringEqualityComparer.GetRandomizedEqualityComparer() { + return new RandomizedStringEqualityComparer(); + } + + // We want to serialize the old comparer. + IEqualityComparer IWellKnownStringEqualityComparer.GetEqualityComparerForSerialization() { + return EqualityComparer<string>.Default; + } + } + + // This type is not serializeable by design. It does not exist in previous versions and will be removed + // Once we move the framework to using secure hashing by default. + internal sealed class RandomizedObjectEqualityComparer : IEqualityComparer, IWellKnownStringEqualityComparer + { + private long _entropy; + + public RandomizedObjectEqualityComparer() { + _entropy = HashHelpers.GetEntropy(); + } + + [Pure] + public new bool Equals(Object x, Object y) { + if (x != null) { + if (y != null) return x.Equals(y); + return false; + } + if (y != null) return false; + return true; + } + + [Pure] + [SecuritySafeCritical] + public int GetHashCode(Object obj) { + if(obj == null) return 0; + + string sObj = obj as string; + if(sObj != null) return String.InternalMarvin32HashString(sObj, sObj.Length, _entropy); + + return obj.GetHashCode(); + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj){ + RandomizedObjectEqualityComparer comparer = obj as RandomizedObjectEqualityComparer; + return (comparer != null) && (this._entropy == comparer._entropy); + } + + public override int GetHashCode() { + return (this.GetType().Name.GetHashCode() ^ ((int) (_entropy & 0x7FFFFFFF))); + } + + IEqualityComparer IWellKnownStringEqualityComparer.GetRandomizedEqualityComparer() { + return new RandomizedObjectEqualityComparer(); + } + + // We want to serialize the old comparer, which in this case was null. + IEqualityComparer IWellKnownStringEqualityComparer.GetEqualityComparerForSerialization() { + return null; + } + } +#endif +} + diff --git a/src/mscorlib/src/System/Collections/Generic/ICollection.cs b/src/mscorlib/src/System/Collections/Generic/ICollection.cs new file mode 100644 index 0000000000..741e8cc79b --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/ICollection.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: ICollection +** +** +** +** +** Purpose: Base interface for all generic collections. +** +** +===========================================================*/ +namespace System.Collections.Generic { + using System; + using System.Runtime.CompilerServices; + using System.Diagnostics.Contracts; + + // Base interface for all collections, defining enumerators, size, and + // synchronization methods. + + // Note that T[] : IList<T>, and we want to ensure that if you use + // IList<YourValueType>, we ensure a YourValueType[] can be used + // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. + // This is a special workaround internally though - see VM\compile.cpp. + // The same attribute is on IEnumerable<T> and ICollection<T>. + [TypeDependencyAttribute("System.SZArrayHelper")] + public interface ICollection<T> : IEnumerable<T> + { + // Number of items in the collections. + int Count { get; } + + bool IsReadOnly { get; } + + void Add(T item); + + void Clear(); + + bool Contains(T item); + + // CopyTo copies a collection into an Array, starting at a particular + // index into the array. + // + void CopyTo(T[] array, int arrayIndex); + + //void CopyTo(int sourceIndex, T[] destinationArray, int destinationIndex, int count); + + bool Remove(T item); + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IComparer.cs b/src/mscorlib/src/System/Collections/Generic/IComparer.cs new file mode 100644 index 0000000000..7b9e97ff0e --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IComparer.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IComparer +** +** +** +** +** Purpose: Interface for comparing two generic Objects. +** +** +===========================================================*/ +namespace System.Collections.Generic { + + using System; + // The generic IComparer interface implements a method that compares + // two objects. It is used in conjunction with the Sort and + // BinarySearch methods on the Array, List, and SortedList classes. + public interface IComparer<in T> + { + // Compares two objects. An implementation of this method must return a + // value less than zero if x is less than y, zero if x is equal to y, or a + // value greater than zero if x is greater than y. + // + int Compare(T x, T y); + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IDictionary.cs b/src/mscorlib/src/System/Collections/Generic/IDictionary.cs new file mode 100644 index 0000000000..2a2da944d3 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IDictionary.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IDictionary +** +** +** +** +** Purpose: Base interface for all generic dictionaries. +** +** +===========================================================*/ +namespace System.Collections.Generic { + using System; + using System.Diagnostics.Contracts; + + // An IDictionary is a possibly unordered set of key-value pairs. + // Keys can be any non-null object. Values can be any object. + // You can look up a value in an IDictionary via the default indexed + // property, Items. + public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>> + { + // Interfaces are not serializable + // The Item property provides methods to read and edit entries + // in the Dictionary. + TValue this[TKey key] { + get; + set; + } + + // Returns a collections of the keys in this dictionary. + ICollection<TKey> Keys { + get; + } + + // Returns a collections of the values in this dictionary. + ICollection<TValue> Values { + get; + } + + // Returns whether this dictionary contains a particular key. + // + bool ContainsKey(TKey key); + + // Adds a key-value pair to the dictionary. + // + void Add(TKey key, TValue value); + + // Removes a particular key from the dictionary. + // + bool Remove(TKey key); + + bool TryGetValue(TKey key, out TValue value); + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IEnumerable.cs b/src/mscorlib/src/System/Collections/Generic/IEnumerable.cs new file mode 100644 index 0000000000..67f35ce675 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IEnumerable.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IEnumerable +** +** +** +** +** Purpose: Interface for providing generic IEnumerators +** +** +===========================================================*/ +namespace System.Collections.Generic { + using System; + using System.Collections; + using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; + using System.Diagnostics.Contracts; + + // Implement this interface if you need to support foreach semantics. + + // Note that T[] : IList<T>, and we want to ensure that if you use + // IList<YourValueType>, we ensure a YourValueType[] can be used + // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. + // This is a special workaround internally though - see VM\compile.cpp. + // The same attribute is on IList<T> and ICollection<T>. + [TypeDependencyAttribute("System.SZArrayHelper")] + public interface IEnumerable<out T> : IEnumerable + { + // Returns an IEnumerator for this enumerable Object. The enumerator provides + // a simple way to access all the contents of a collection. + /// <include file='doc\IEnumerable.uex' path='docs/doc[@for="IEnumerable.GetEnumerator"]/*' /> + new IEnumerator<T> GetEnumerator(); + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IEnumerator.cs b/src/mscorlib/src/System/Collections/Generic/IEnumerator.cs new file mode 100644 index 0000000000..335616757b --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IEnumerator.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IEnumerator +** +** +** +** +** Purpose: Base interface for all generic enumerators. +** +** +===========================================================*/ +namespace System.Collections.Generic { + using System; + using System.Runtime.InteropServices; + + // Base interface for all generic enumerators, providing a simple approach + // to iterating over a collection. + public interface IEnumerator<out T> : IDisposable, IEnumerator + { + // Returns the current element of the enumeration. The returned value is + // undefined before the first call to MoveNext and following a + // call to MoveNext that returned false. Multiple calls to + // GetCurrent with no intervening calls to MoveNext + // will return the same object. + // + /// <include file='doc\IEnumerator.uex' path='docs/doc[@for="IEnumerator.Current"]/*' /> + new T Current { + get; + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IEqualityComparer.cs b/src/mscorlib/src/System/Collections/Generic/IEqualityComparer.cs new file mode 100644 index 0000000000..b6ac3be006 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IEqualityComparer.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Collections.Generic { + using System; + + // The generic IEqualityComparer interface implements methods to if check two objects are equal + // and generate Hashcode for an object. + // It is use in Dictionary class. + public interface IEqualityComparer<in T> + { + bool Equals(T x, T y); + int GetHashCode(T obj); + } +} + diff --git a/src/mscorlib/src/System/Collections/Generic/IList.cs b/src/mscorlib/src/System/Collections/Generic/IList.cs new file mode 100644 index 0000000000..75ca0a9b00 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IList.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IList +** +** +** +** +** Purpose: Base interface for all generic lists. +** +** +===========================================================*/ +namespace System.Collections.Generic { + + using System; + using System.Collections; + using System.Runtime.CompilerServices; + using System.Diagnostics.Contracts; + + // An IList is an ordered collection of objects. The exact ordering + // is up to the implementation of the list, ranging from a sorted + // order to insertion order. + + // Note that T[] : IList<T>, and we want to ensure that if you use + // IList<YourValueType>, we ensure a YourValueType[] can be used + // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. + // This is a special workaround internally though - see VM\compile.cpp. + // The same attribute is on IEnumerable<T> and ICollection<T>. + [TypeDependencyAttribute("System.SZArrayHelper")] + public interface IList<T> : ICollection<T> + { + // The Item property provides methods to read and edit entries in the List. + T this[int index] { + get; + set; + } + + // Returns the index of a particular item, if it is in the list. + // Returns -1 if the item isn't in the list. + int IndexOf(T item); + + // Inserts value into the list at position index. + // index must be non-negative and less than or equal to the + // number of elements in the list. If index equals the number + // of items in the list, then value is appended to the end. + void Insert(int index, T item); + + // Removes the item at position index. + void RemoveAt(int index); + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IReadOnlyCollection.cs b/src/mscorlib/src/System/Collections/Generic/IReadOnlyCollection.cs new file mode 100644 index 0000000000..13bc718760 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IReadOnlyCollection.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IReadOnlyCollection<T> +** +** +** +** Purpose: Base interface for read-only generic lists. +** +===========================================================*/ +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace System.Collections.Generic +{ + + // Provides a read-only, covariant view of a generic list. + + // Note that T[] : IReadOnlyList<T>, and we want to ensure that if you use + // IList<YourValueType>, we ensure a YourValueType[] can be used + // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. + // This is a special workaround internally though - see VM\compile.cpp. + // The same attribute is on IList<T>, IEnumerable<T>, ICollection<T>, and IReadOnlyList<T>. + [TypeDependencyAttribute("System.SZArrayHelper")] + // If we ever implement more interfaces on IReadOnlyCollection, we should also update RuntimeTypeCache.PopulateInterfaces() in rttype.cs + public interface IReadOnlyCollection<out T> : IEnumerable<T> + { + int Count { get; } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IReadOnlyDictionary.cs b/src/mscorlib/src/System/Collections/Generic/IReadOnlyDictionary.cs new file mode 100644 index 0000000000..3603b9a4ea --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IReadOnlyDictionary.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IReadOnlyDictionary<TKey, TValue> +** +** +** +** Purpose: Base interface for read-only generic dictionaries. +** +===========================================================*/ +using System; +using System.Diagnostics.Contracts; + +namespace System.Collections.Generic +{ + // Provides a read-only view of a generic dictionary. + public interface IReadOnlyDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey, TValue>> + { + bool ContainsKey(TKey key); + bool TryGetValue(TKey key, out TValue value); + + TValue this[TKey key] { get; } + IEnumerable<TKey> Keys { get; } + IEnumerable<TValue> Values { get; } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/IReadOnlyList.cs b/src/mscorlib/src/System/Collections/Generic/IReadOnlyList.cs new file mode 100644 index 0000000000..77366f0b2f --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/IReadOnlyList.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IReadOnlyList<T> +** +** +** +** Purpose: Base interface for read-only generic lists. +** +===========================================================*/ +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace System.Collections.Generic +{ + + // Provides a read-only, covariant view of a generic list. + + // Note that T[] : IReadOnlyList<T>, and we want to ensure that if you use + // IList<YourValueType>, we ensure a YourValueType[] can be used + // without jitting. Hence the TypeDependencyAttribute on SZArrayHelper. + // This is a special workaround internally though - see VM\compile.cpp. + // The same attribute is on IList<T>, IEnumerable<T>, ICollection<T> and IReadOnlyCollection<T>. + [TypeDependencyAttribute("System.SZArrayHelper")] + // If we ever implement more interfaces on IReadOnlyList, we should also update RuntimeTypeCache.PopulateInterfaces() in rttype.cs + public interface IReadOnlyList<out T> : IReadOnlyCollection<T> + { + T this[int index] { get; } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/KeyNotFoundException.cs b/src/mscorlib/src/System/Collections/Generic/KeyNotFoundException.cs new file mode 100644 index 0000000000..ffcd0405fd --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/KeyNotFoundException.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** +** +** +** +** Purpose: Exception class for Hashtable and Dictionary. +** +** +=============================================================================*/ + +namespace System.Collections.Generic { + + using System; + using System.Runtime.Remoting; + using System.Runtime.Serialization; + + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class KeyNotFoundException : SystemException, ISerializable { + + public KeyNotFoundException () + : base(Environment.GetResourceString("Arg_KeyNotFound")) { + SetErrorCode(System.__HResults.COR_E_KEYNOTFOUND); + } + + public KeyNotFoundException(String message) + : base(message) { + SetErrorCode(System.__HResults.COR_E_KEYNOTFOUND); + } + + public KeyNotFoundException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(System.__HResults.COR_E_KEYNOTFOUND); + } + + + protected KeyNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/KeyValuePair.cs b/src/mscorlib/src/System/Collections/Generic/KeyValuePair.cs new file mode 100644 index 0000000000..17e1c531f1 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/KeyValuePair.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: KeyValuePair +** +** +** +** +** Purpose: Generic key-value pair for dictionary enumerators. +** +** +===========================================================*/ +namespace System.Collections.Generic { + + using System; + using System.Text; + + // A KeyValuePair holds a key and a value from a dictionary. + // It is used by the IEnumerable<T> implementation for both IDictionary<TKey, TValue> + // and IReadOnlyDictionary<TKey, TValue>. + [Serializable] + public struct KeyValuePair<TKey, TValue> { + private TKey key; + private TValue value; + + public KeyValuePair(TKey key, TValue value) { + this.key = key; + this.value = value; + } + + public TKey Key { + get { return key; } + } + + public TValue Value { + get { return value; } + } + + public override string ToString() { + StringBuilder s = StringBuilderCache.Acquire(); + s.Append('['); + if( Key != null) { + s.Append(Key.ToString()); + } + s.Append(", "); + if( Value != null) { + s.Append(Value.ToString()); + } + s.Append(']'); + return StringBuilderCache.GetStringAndRelease(s); + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/List.cs b/src/mscorlib/src/System/Collections/Generic/List.cs new file mode 100644 index 0000000000..ae3356d372 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Generic/List.cs @@ -0,0 +1,1120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** Purpose: Implements a generic, dynamically sized list as an +** array. +** +** +===========================================================*/ +namespace System.Collections.Generic { + + using System; + using System.Runtime; + using System.Runtime.Versioning; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Collections.ObjectModel; + using System.Security.Permissions; + + // Implements a variable-size List that uses an array of objects to store the + // elements. A List has a capacity, which is the allocated length + // of the internal array. As elements are added to a List, the capacity + // of the List is automatically increased as required by reallocating the + // internal array. + // + [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T> + { + private const int _defaultCapacity = 4; + + private T[] _items; + [ContractPublicPropertyName("Count")] + private int _size; + private int _version; + [NonSerialized] + private Object _syncRoot; + + static readonly T[] _emptyArray = new T[0]; + + // Constructs a List. The list is initially empty and has a capacity + // of zero. Upon adding the first element to the list the capacity is + // increased to _defaultCapacity, and then increased in multiples of two + // as required. + public List() { + _items = _emptyArray; + } + + // Constructs a List with a given initial capacity. The list is + // initially empty, but will have room for the given number of elements + // before any reallocations are required. + // + public List(int capacity) { + if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + Contract.EndContractBlock(); + + if (capacity == 0) + _items = _emptyArray; + else + _items = new T[capacity]; + } + + // Constructs a List, copying the contents of the given collection. The + // size and capacity of the new list will both be equal to the size of the + // given collection. + // + public List(IEnumerable<T> collection) { + if (collection==null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + Contract.EndContractBlock(); + + ICollection<T> c = collection as ICollection<T>; + if( c != null) { + int count = c.Count; + if (count == 0) + { + _items = _emptyArray; + } + else { + _items = new T[count]; + c.CopyTo(_items, 0); + _size = count; + } + } + else { + _size = 0; + _items = _emptyArray; + // This enumerable could be empty. Let Add allocate a new array, if needed. + // Note it will also go to _defaultCapacity first, not 1, then 2, etc. + + using(IEnumerator<T> en = collection.GetEnumerator()) { + while(en.MoveNext()) { + Add(en.Current); + } + } + } + } + + // Gets and sets the capacity of this list. The capacity is the size of + // the internal array used to hold items. When set, the internal + // array of the list is reallocated to the given capacity. + // + public int Capacity { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + return _items.Length; + } + set { + if (value < _size) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity); + } + Contract.EndContractBlock(); + + if (value != _items.Length) { + if (value > 0) { + T[] newItems = new T[value]; + if (_size > 0) { + Array.Copy(_items, 0, newItems, 0, _size); + } + _items = newItems; + } + else { + _items = _emptyArray; + } + } + } + } + + // Read-only property describing how many elements are in the List. + public int Count { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + return _size; + } + } + + bool System.Collections.IList.IsFixedSize { + get { return false; } + } + + + // Is this List read-only? + bool ICollection<T>.IsReadOnly { + get { return false; } + } + + bool System.Collections.IList.IsReadOnly { + get { return false; } + } + + // Is this List synchronized (thread-safe)? + bool System.Collections.ICollection.IsSynchronized { + get { return false; } + } + + // Synchronization root for this object. + Object System.Collections.ICollection.SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + // Sets or Gets the element at the given index. + // + public T this[int index] { + get { + // Following trick can reduce the range check by one + if ((uint) index >= (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + } + Contract.EndContractBlock(); + return _items[index]; + } + + set { + if ((uint) index >= (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + } + Contract.EndContractBlock(); + _items[index] = value; + _version++; + } + } + + private static bool IsCompatibleObject(object value) { + // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>. + // Note that default(T) is not equal to null for value types except when T is Nullable<U>. + return ((value is T) || (value == null && default(T) == null)); + } + + Object System.Collections.IList.this[int index] { + get { + return this[index]; + } + set { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value); + + try { + this[index] = (T)value; + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + } + } + + // Adds the given object to the end of this list. The size of the list is + // increased by one. If required, the capacity of the list is doubled + // before adding the new element. + // + public void Add(T item) { + if (_size == _items.Length) EnsureCapacity(_size + 1); + _items[_size++] = item; + _version++; + } + + int System.Collections.IList.Add(Object item) + { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item); + + try { + Add((T) item); + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T)); + } + + return Count - 1; + } + + + // Adds the elements of the given collection to the end of this list. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. + // + public void AddRange(IEnumerable<T> collection) { + Contract.Ensures(Count >= Contract.OldValue(Count)); + + InsertRange(_size, collection); + } + + public ReadOnlyCollection<T> AsReadOnly() { + Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null); + return new ReadOnlyCollection<T>(this); + } + + // Searches a section of the list for a given element using a binary search + // algorithm. Elements of the list are compared to the search value using + // the given IComparer interface. If comparer is null, elements of + // the list are compared to the search value using the IComparable + // interface, which in that case must be implemented by all elements of the + // list and the given search value. This method assumes that the given + // section of the list is already sorted; if this is not the case, the + // result will be incorrect. + // + // The method returns the index of the given value in the list. If the + // list does not contain the given value, the method returns a negative + // integer. The bitwise complement operator (~) can be applied to a + // negative result to produce the index of the first element (if any) that + // is larger than the given search value. This is also the index at which + // the search value should be inserted into the list in order for the list + // to remain sorted. + // + // The method uses the Array.BinarySearch method to perform the + // search. + // + public int BinarySearch(int index, int count, T item, IComparer<T> comparer) { + if (index < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + if (count < 0) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + if (_size - index < count) + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + Contract.Ensures(Contract.Result<int>() <= index + count); + Contract.EndContractBlock(); + + return Array.BinarySearch<T>(_items, index, count, item, comparer); + } + + public int BinarySearch(T item) + { + Contract.Ensures(Contract.Result<int>() <= Count); + return BinarySearch(0, Count, item, null); + } + + public int BinarySearch(T item, IComparer<T> comparer) + { + Contract.Ensures(Contract.Result<int>() <= Count); + return BinarySearch(0, Count, item, comparer); + } + + + // Clears the contents of List. + public void Clear() { + if (_size > 0) + { + Array.Clear(_items, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references. + _size = 0; + } + _version++; + } + + // Contains returns true if the specified element is in the List. + // It does a linear, O(n) search. Equality is determined by calling + // EqualityComparer<T>.Default.Equals(). + + public bool Contains(T item) + { + // PERF: IndexOf calls Array.IndexOf, which internally + // calls EqualityComparer<T>.Default.IndexOf, which + // is specialized for different types. This + // boosts performance since instead of making a + // virtual method call each iteration of the loop, + // via EqualityComparer<T>.Default.Equals, we + // only make one virtual call to EqualityComparer.IndexOf. + + return _size != 0 && IndexOf(item) != -1; + } + + bool System.Collections.IList.Contains(Object item) + { + if(IsCompatibleObject(item)) { + return Contains((T) item); + } + return false; + } + + public List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> converter) { + if( converter == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter); + } + + Contract.EndContractBlock(); + + List<TOutput> list = new List<TOutput>(_size); + for( int i = 0; i< _size; i++) { + list._items[i] = converter(_items[i]); + } + list._size = _size; + return list; + } + + // Copies this List into array, which must be of a + // compatible array type. + // + public void CopyTo(T[] array) { + CopyTo(array, 0); + } + + // Copies this List into array, which must be of a + // compatible array type. + // + void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) { + if ((array != null) && (array.Rank != 1)) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + Contract.EndContractBlock(); + + try { + // Array.Copy will check for NULL. + Array.Copy(_items, 0, array, arrayIndex, _size); + } + catch(ArrayTypeMismatchException){ + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + + // Copies a section of this list to the given array at the given index. + // + // The method uses the Array.Copy method to copy the elements. + // + public void CopyTo(int index, T[] array, int arrayIndex, int count) { + if (_size - index < count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + } + Contract.EndContractBlock(); + + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, index, array, arrayIndex, count); + } + + public void CopyTo(T[] array, int arrayIndex) { + // Delegate rest of error checking to Array.Copy. + Array.Copy(_items, 0, array, arrayIndex, _size); + } + + // Ensures that the capacity of this list is at least the given minimum + // value. If the currect capacity of the list is less than min, the + // capacity is increased to twice the current capacity or to min, + // whichever is larger. + private void EnsureCapacity(int min) { + if (_items.Length < min) { + int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2; + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + public bool Exists(Predicate<T> match) { + return FindIndex(match) != -1; + } + + public T Find(Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.EndContractBlock(); + + for(int i = 0 ; i < _size; i++) { + if(match(_items[i])) { + return _items[i]; + } + } + return default(T); + } + + public List<T> FindAll(Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.EndContractBlock(); + + List<T> list = new List<T>(); + for(int i = 0 ; i < _size; i++) { + if(match(_items[i])) { + list.Add(_items[i]); + } + } + return list; + } + + public int FindIndex(Predicate<T> match) { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + return FindIndex(0, _size, match); + } + + public int FindIndex(int startIndex, Predicate<T> match) { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < startIndex + Count); + return FindIndex(startIndex, _size - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate<T> match) { + if( (uint)startIndex > (uint)_size ) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index); + } + + if (count < 0 || startIndex > _size - count) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); + } + + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < startIndex + count); + Contract.EndContractBlock(); + + int endIndex = startIndex + count; + for( int i = startIndex; i < endIndex; i++) { + if( match(_items[i])) return i; + } + return -1; + } + + public T FindLast(Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.EndContractBlock(); + + for(int i = _size - 1 ; i >= 0; i--) { + if(match(_items[i])) { + return _items[i]; + } + } + return default(T); + } + + public int FindLastIndex(Predicate<T> match) { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + return FindLastIndex(_size - 1, _size, match); + } + + public int FindLastIndex(int startIndex, Predicate<T> match) { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() <= startIndex); + return FindLastIndex(startIndex, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() <= startIndex); + Contract.EndContractBlock(); + + if(_size == 0) { + // Special case for 0 length List + if( startIndex != -1) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index); + } + } + else { + // Make sure we're not out of range + if ( (uint)startIndex >= (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index); + } + } + + // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. + if (count < 0 || startIndex - count + 1 < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); + } + + int endIndex = startIndex - count; + for( int i = startIndex; i > endIndex; i--) { + if( match(_items[i])) { + return i; + } + } + return -1; + } + + public void ForEach(Action<T> action) { + if( action == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action); + } + Contract.EndContractBlock(); + + int version = _version; + + for(int i = 0 ; i < _size; i++) { + if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) { + break; + } + action(_items[i]); + } + + if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + // Returns an enumerator for this list with the given + // permission for removal of elements. If modifications made to the list + // while an enumeration is in progress, the MoveNext and + // GetObject methods of the enumerator will throw an exception. + // + public Enumerator GetEnumerator() { + return new Enumerator(this); + } + + /// <internalonly/> + IEnumerator<T> IEnumerable<T>.GetEnumerator() { + return new Enumerator(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return new Enumerator(this); + } + + public List<T> GetRange(int index, int count) { + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (count < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_size - index < count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + } + Contract.Ensures(Contract.Result<List<T>>() != null); + Contract.EndContractBlock(); + + List<T> list = new List<T>(count); + Array.Copy(_items, index, list._items, 0, count); + list._size = count; + return list; + } + + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards from beginning to end. + // The elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item) { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + return Array.IndexOf(_items, item, 0, _size); + } + + int System.Collections.IList.IndexOf(Object item) + { + if(IsCompatibleObject(item)) { + return IndexOf((T)item); + } + return -1; + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // index and ending at count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item, int index) { + if (index > _size) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index); + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + return Array.IndexOf(_items, item, index, _size - index); + } + + // Returns the index of the first occurrence of a given value in a range of + // this list. The list is searched forwards, starting at index + // index and upto count number of elements. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.IndexOf method to perform the + // search. + // + public int IndexOf(T item, int index, int count) { + if (index > _size) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index); + + if (count <0 || index > _size - count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + Contract.EndContractBlock(); + + return Array.IndexOf(_items, item, index, count); + } + + // Inserts an element into this list at a given index. The size of the list + // is increased by one. If required, the capacity of the list is doubled + // before inserting the new element. + // + public void Insert(int index, T item) { + // Note that insertions at the end are legal. + if ((uint) index > (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert); + } + Contract.EndContractBlock(); + if (_size == _items.Length) EnsureCapacity(_size + 1); + if (index < _size) { + Array.Copy(_items, index, _items, index + 1, _size - index); + } + _items[index] = item; + _size++; + _version++; + } + + void System.Collections.IList.Insert(int index, Object item) + { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item); + + try { + Insert(index, (T) item); + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T)); + } + } + + // Inserts the elements of the given collection at a given index. If + // required, the capacity of the list is increased to twice the previous + // capacity or the new size, whichever is larger. Ranges may be added + // to the end of the list by setting index to the List's size. + // + public void InsertRange(int index, IEnumerable<T> collection) { + if (collection==null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + + if ((uint)index > (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index); + } + Contract.EndContractBlock(); + + ICollection<T> c = collection as ICollection<T>; + if( c != null ) { // if collection is ICollection<T> + int count = c.Count; + if (count > 0) { + EnsureCapacity(_size + count); + if (index < _size) { + Array.Copy(_items, index, _items, index + count, _size - index); + } + + // If we're inserting a List into itself, we want to be able to deal with that. + if (this == c) { + // Copy first part of _items to insert location + Array.Copy(_items, 0, _items, index, index); + // Copy last part of _items back to inserted location + Array.Copy(_items, index+count, _items, index*2, _size-index); + } + else { + T[] itemsToInsert = new T[count]; + c.CopyTo(itemsToInsert, 0); + itemsToInsert.CopyTo(_items, index); + } + _size += count; + } + } + else { + using(IEnumerator<T> en = collection.GetEnumerator()) { + while(en.MoveNext()) { + Insert(index++, en.Current); + } + } + } + _version++; + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at the end + // and ending at the first element in the list. The elements of the list + // are compared to the given value using the Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item) + { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < Count); + if (_size == 0) { // Special case for empty list + return -1; + } + else { + return LastIndexOf(item, _size - 1, _size); + } + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // index and ending at the first element in the list. The + // elements of the list are compared to the given value using the + // Object.Equals method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item, int index) + { + if (index >= _size) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index); + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(((Count == 0) && (Contract.Result<int>() == -1)) || ((Count > 0) && (Contract.Result<int>() <= index))); + Contract.EndContractBlock(); + return LastIndexOf(item, index, index + 1); + } + + // Returns the index of the last occurrence of a given value in a range of + // this list. The list is searched backwards, starting at index + // index and upto count elements. The elements of + // the list are compared to the given value using the Object.Equals + // method. + // + // This method uses the Array.LastIndexOf method to perform the + // search. + // + public int LastIndexOf(T item, int index, int count) { + if ((Count != 0) && (index < 0)) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if ((Count !=0) && (count < 0)) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(((Count == 0) && (Contract.Result<int>() == -1)) || ((Count > 0) && (Contract.Result<int>() <= index))); + Contract.EndContractBlock(); + + if (_size == 0) { // Special case for empty list + return -1; + } + + if (index >= _size) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection); + } + + if (count > index + 1) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_BiggerThanCollection); + } + + return Array.LastIndexOf(_items, item, index, count); + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + // + public bool Remove(T item) { + int index = IndexOf(item); + if (index >= 0) { + RemoveAt(index); + return true; + } + + return false; + } + + void System.Collections.IList.Remove(Object item) + { + if(IsCompatibleObject(item)) { + Remove((T) item); + } + } + + // This method removes all items which matches the predicate. + // The complexity is O(n). + public int RemoveAll(Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.Ensures(Contract.Result<int>() <= Contract.OldValue(Count)); + Contract.EndContractBlock(); + + int freeIndex = 0; // the first free slot in items array + + // Find the first item which needs to be removed. + while( freeIndex < _size && !match(_items[freeIndex])) freeIndex++; + if( freeIndex >= _size) return 0; + + int current = freeIndex + 1; + while( current < _size) { + // Find the first item which needs to be kept. + while( current < _size && match(_items[current])) current++; + + if( current < _size) { + // copy item to the free slot. + _items[freeIndex++] = _items[current++]; + } + } + + Array.Clear(_items, freeIndex, _size - freeIndex); + int result = _size - freeIndex; + _size = freeIndex; + _version++; + return result; + } + + // Removes the element at the given index. The size of the list is + // decreased by one. + // + public void RemoveAt(int index) { + if ((uint)index >= (uint)_size) { + ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + } + Contract.EndContractBlock(); + _size--; + if (index < _size) { + Array.Copy(_items, index + 1, _items, index, _size - index); + } + _items[_size] = default(T); + _version++; + } + + // Removes a range of elements from this list. + // + public void RemoveRange(int index, int count) { + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (count < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_size - index < count) + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + Contract.EndContractBlock(); + + if (count > 0) { + int i = _size; + _size -= count; + if (index < _size) { + Array.Copy(_items, index + count, _items, index, _size - index); + } + Array.Clear(_items, _size, count); + _version++; + } + } + + // Reverses the elements in this list. + public void Reverse() { + Reverse(0, Count); + } + + // Reverses the elements in a range of this list. Following a call to this + // method, an element in the range given by index and count + // which was previously located at index i will now be located at + // index index + (index + count - i - 1). + // + public void Reverse(int index, int count) { + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (count < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_size - index < count) + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + Contract.EndContractBlock(); + + // The non-generic Array.Reverse is not used because it does not perform + // well for non-primitive value types. + // If/when a generic Array.Reverse<T> becomes available, the below code + // can be deleted and replaced with a call to Array.Reverse<T>. + int i = index; + int j = index + count - 1; + T[] array = _items; + while (i < j) + { + T temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + + _version++; + } + + // Sorts the elements in this list. Uses the default comparer and + // Array.Sort. + public void Sort() + { + Sort(0, Count, null); + } + + // Sorts the elements in this list. Uses Array.Sort with the + // provided comparer. + public void Sort(IComparer<T> comparer) + { + Sort(0, Count, comparer); + } + + // Sorts the elements in a section of this list. The sort compares the + // elements to each other using the given IComparer interface. If + // comparer is null, the elements are compared to each other using + // the IComparable interface, which in that case must be implemented by all + // elements of the list. + // + // This method uses the Array.Sort method to sort the elements. + // + public void Sort(int index, int count, IComparer<T> comparer) { + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (count < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_size - index < count) + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen); + Contract.EndContractBlock(); + + Array.Sort<T>(_items, index, count, comparer); + _version++; + } + + public void Sort(Comparison<T> comparison) { + if( comparison == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison); + } + Contract.EndContractBlock(); + + if( _size > 0) { + IComparer<T> comparer = Comparer<T>.Create(comparison); + Array.Sort(_items, 0, _size, comparer); + } + } + + // ToArray returns an array containing the contents of the List. + // This requires copying the List, which is an O(n) operation. + public T[] ToArray() { + Contract.Ensures(Contract.Result<T[]>() != null); + Contract.Ensures(Contract.Result<T[]>().Length == Count); + +#if FEATURE_CORECLR + if (_size == 0) + { + return _emptyArray; + } +#endif + + T[] array = new T[_size]; + Array.Copy(_items, 0, array, 0, _size); + return array; + } + + // Sets the capacity of this list to the size of the list. This method can + // be used to minimize a list's memory overhead once it is known that no + // new elements will be added to the list. To completely clear a list and + // release all memory referenced by the list, execute the following + // statements: + // + // list.Clear(); + // list.TrimExcess(); + // + public void TrimExcess() { + int threshold = (int)(((double)_items.Length) * 0.9); + if( _size < threshold ) { + Capacity = _size; + } + } + + public bool TrueForAll(Predicate<T> match) { + if( match == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); + } + Contract.EndContractBlock(); + + for(int i = 0 ; i < _size; i++) { + if( !match(_items[i])) { + return false; + } + } + return true; + } + + [Serializable] + public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator + { + private List<T> list; + private int index; + private int version; + private T current; + + internal Enumerator(List<T> list) { + this.list = list; + index = 0; + version = list._version; + current = default(T); + } + + public void Dispose() { + } + + public bool MoveNext() { + + List<T> localList = list; + + if (version == localList._version && ((uint)index < (uint)localList._size)) + { + current = localList._items[index]; + index++; + return true; + } + return MoveNextRare(); + } + + private bool MoveNextRare() + { + if (version != list._version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + index = list._size + 1; + current = default(T); + return false; + } + + public T Current { + get { + return current; + } + } + + Object System.Collections.IEnumerator.Current { + get { + if( index == 0 || index == list._size + 1) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + return Current; + } + } + + void System.Collections.IEnumerator.Reset() { + if (version != list._version) { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + index = 0; + current = default(T); + } + + } + } +} + diff --git a/src/mscorlib/src/System/Collections/Hashtable.cs b/src/mscorlib/src/System/Collections/Hashtable.cs new file mode 100644 index 0000000000..262ccedea6 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Hashtable.cs @@ -0,0 +1,1847 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Hash table implementation +** +** +===========================================================*/ + +namespace System.Collections { + using System; + using System.Runtime; + using System.Runtime.Serialization; + using System.Security.Permissions; + using System.Diagnostics; + using System.Threading; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Diagnostics.Contracts; +#if !FEATURE_CORECLR + using System.Security.Cryptography; +#endif + + // The Hashtable class represents a dictionary of associated keys and values + // with constant lookup time. + // + // Objects used as keys in a hashtable must implement the GetHashCode + // and Equals methods (or they can rely on the default implementations + // inherited from Object if key equality is simply reference + // equality). Furthermore, the GetHashCode and Equals methods of + // a key object must produce the same results given the same parameters for the + // entire time the key is present in the hashtable. In practical terms, this + // means that key objects should be immutable, at least for the time they are + // used as keys in a hashtable. + // + // When entries are added to a hashtable, they are placed into + // buckets based on the hashcode of their keys. Subsequent lookups of + // keys will use the hashcode of the keys to only search a particular bucket, + // thus substantially reducing the number of key comparisons required to find + // an entry. A hashtable's maximum load factor, which can be specified + // when the hashtable is instantiated, determines the maximum ratio of + // hashtable entries to hashtable buckets. Smaller load factors cause faster + // average lookup times at the cost of increased memory consumption. The + // default maximum load factor of 1.0 generally provides the best balance + // between speed and size. As entries are added to a hashtable, the hashtable's + // actual load factor increases, and when the actual load factor reaches the + // maximum load factor value, the number of buckets in the hashtable is + // automatically increased by approximately a factor of two (to be precise, the + // number of hashtable buckets is increased to the smallest prime number that + // is larger than twice the current number of hashtable buckets). + // + // Each object provides their own hash function, accessed by calling + // GetHashCode(). However, one can write their own object + // implementing IEqualityComparer and pass it to a constructor on + // the Hashtable. That hash function (and the equals method on the + // IEqualityComparer) would be used for all objects in the table. + // + // Changes since V1 during Whidbey: + // *) Deprecated IHashCodeProvider, use IEqualityComparer instead. This will + // allow better performance for objects where equality checking can be + // done much faster than establishing an ordering between two objects, + // such as an ordinal string equality check. + // + [DebuggerTypeProxy(typeof(System.Collections.Hashtable.HashtableDebugView))] + [DebuggerDisplay("Count = {Count}")] + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public class Hashtable : IDictionary, ISerializable, IDeserializationCallback, ICloneable { + /* + Implementation Notes: + The generic Dictionary was copied from Hashtable's source - any bug + fixes here probably need to be made to the generic Dictionary as well. + + This Hashtable uses double hashing. There are hashsize buckets in the + table, and each bucket can contain 0 or 1 element. We a bit to mark + whether there's been a collision when we inserted multiple elements + (ie, an inserted item was hashed at least a second time and we probed + this bucket, but it was already in use). Using the collision bit, we + can terminate lookups & removes for elements that aren't in the hash + table more quickly. We steal the most significant bit from the hash code + to store the collision bit. + + Our hash function is of the following form: + + h(key, n) = h1(key) + n*h2(key) + + where n is the number of times we've hit a collided bucket and rehashed + (on this particular lookup). Here are our hash functions: + + h1(key) = GetHash(key); // default implementation calls key.GetHashCode(); + h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1)); + + The h1 can return any number. h2 must return a number between 1 and + hashsize - 1 that is relatively prime to hashsize (not a problem if + hashsize is prime). (Knuth's Art of Computer Programming, Vol. 3, p. 528-9) + If this is true, then we are guaranteed to visit every bucket in exactly + hashsize probes, since the least common multiple of hashsize and h2(key) + will be hashsize * h2(key). (This is the first number where adding h2 to + h1 mod hashsize will be 0 and we will search the same bucket twice). + + We previously used a different h2(key, n) that was not constant. That is a + horrifically bad idea, unless you can prove that series will never produce + any identical numbers that overlap when you mod them by hashsize, for all + subranges from i to i+hashsize, for all i. It's not worth investigating, + since there was no clear benefit from using that hash function, and it was + broken. + + For efficiency reasons, we've implemented this by storing h1 and h2 in a + temporary, and setting a variable called seed equal to h1. We do a probe, + and if we collided, we simply add h2 to seed each time through the loop. + + A good test for h2() is to subclass Hashtable, provide your own implementation + of GetHash() that returns a constant, then add many items to the hash table. + Make sure Count equals the number of items you inserted. + + Note that when we remove an item from the hash table, we set the key + equal to buckets, if there was a collision in this bucket. Otherwise + we'd either wipe out the collision bit, or we'd still have an item in + the hash table. + + -- + */ + + internal const Int32 HashPrime = 101; + private const Int32 InitialSize = 3; + private const String LoadFactorName = "LoadFactor"; + private const String VersionName = "Version"; + private const String ComparerName = "Comparer"; + private const String HashCodeProviderName = "HashCodeProvider"; + private const String HashSizeName = "HashSize"; // Must save buckets.Length + private const String KeysName = "Keys"; + private const String ValuesName = "Values"; + private const String KeyComparerName = "KeyComparer"; + + // Deleted entries have their key set to buckets + + // The hash table data. + // This cannot be serialised + private struct bucket { + public Object key; + public Object val; + public int hash_coll; // Store hash code; sign bit means there was a collision. + } + + private bucket[] buckets; + + // The total number of entries in the hash table. + private int count; + + // The total number of collision bits set in the hashtable + private int occupancy; + + private int loadsize; + private float loadFactor; + + private volatile int version; + private volatile bool isWriterInProgress; + + private ICollection keys; + private ICollection values; + + private IEqualityComparer _keycomparer; + private Object _syncRoot; + + [Obsolete("Please use EqualityComparer property.")] + protected IHashCodeProvider hcp + { + get + { + if( _keycomparer is CompatibleComparer) { + return ((CompatibleComparer)_keycomparer).HashCodeProvider; + } + else if( _keycomparer == null) { + return null; + } + else { + throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure")); + } + } + set + { + if (_keycomparer is CompatibleComparer) { + CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer; + _keycomparer = new CompatibleComparer(keyComparer.Comparer, value); + } + else if( _keycomparer == null) { + _keycomparer = new CompatibleComparer((IComparer)null, value); + } + else { + throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure")); + } + } + } + + + [Obsolete("Please use KeyComparer properties.")] + protected IComparer comparer + { + get + { + if( _keycomparer is CompatibleComparer) { + return ((CompatibleComparer)_keycomparer).Comparer; + } + else if( _keycomparer == null) { + return null; + } + else { + throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure")); + } + } + set + { + if (_keycomparer is CompatibleComparer) { + CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer; + _keycomparer = new CompatibleComparer(value, keyComparer.HashCodeProvider); + } + else if( _keycomparer == null) { + _keycomparer = new CompatibleComparer(value, (IHashCodeProvider)null); + } + else { + throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure")); + } + } + } + + protected IEqualityComparer EqualityComparer + { + get + { + return _keycomparer; + } + } + + // Note: this constructor is a bogus constructor that does nothing + // and is for use only with SyncHashtable. + internal Hashtable( bool trash ) + { + } + + // Constructs a new hashtable. The hashtable is created with an initial + // capacity of zero and a load factor of 1.0. + public Hashtable() : this(0, 1.0f) { + } + + // Constructs a new hashtable with the given initial capacity and a load + // factor of 1.0. The capacity argument serves as an indication of + // the number of entries the hashtable will contain. When this number (or + // an approximation) is known, specifying it in the constructor can + // eliminate a number of resizing operations that would otherwise be + // performed when elements are added to the hashtable. + // + public Hashtable(int capacity) : this(capacity, 1.0f) { + } + + // Constructs a new hashtable with the given initial capacity and load + // factor. The capacity argument serves as an indication of the + // number of entries the hashtable will contain. When this number (or an + // approximation) is known, specifying it in the constructor can eliminate + // a number of resizing operations that would otherwise be performed when + // elements are added to the hashtable. The loadFactor argument + // indicates the maximum ratio of hashtable entries to hashtable buckets. + // Smaller load factors cause faster average lookup times at the cost of + // increased memory consumption. A load factor of 1.0 generally provides + // the best balance between speed and size. + // + public Hashtable(int capacity, float loadFactor) { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (!(loadFactor >= 0.1f && loadFactor <= 1.0f)) + throw new ArgumentOutOfRangeException("loadFactor", Environment.GetResourceString("ArgumentOutOfRange_HashtableLoadFactor", .1, 1.0)); + Contract.EndContractBlock(); + + // Based on perf work, .72 is the optimal load factor for this table. + this.loadFactor = 0.72f * loadFactor; + + double rawsize = capacity / this.loadFactor; + if (rawsize > Int32.MaxValue) + throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow")); + + // Avoid awfully small sizes + int hashsize = (rawsize > InitialSize) ? HashHelpers.GetPrime((int)rawsize) : InitialSize; + buckets = new bucket[hashsize]; + + loadsize = (int)(this.loadFactor * hashsize); + isWriterInProgress = false; + // Based on the current algorithm, loadsize must be less than hashsize. + Contract.Assert( loadsize < hashsize, "Invalid hashtable loadsize!"); + } + + // Constructs a new hashtable with the given initial capacity and load + // factor. The capacity argument serves as an indication of the + // number of entries the hashtable will contain. When this number (or an + // approximation) is known, specifying it in the constructor can eliminate + // a number of resizing operations that would otherwise be performed when + // elements are added to the hashtable. The loadFactor argument + // indicates the maximum ratio of hashtable entries to hashtable buckets. + // Smaller load factors cause faster average lookup times at the cost of + // increased memory consumption. A load factor of 1.0 generally provides + // the best balance between speed and size. The hcp argument + // is used to specify an Object that will provide hash codes for all + // the Objects in the table. Using this, you can in effect override + // GetHashCode() on each Object using your own hash function. The + // comparer argument will let you specify a custom function for + // comparing keys. By specifying user-defined objects for hcp + // and comparer, users could make a hash table using strings + // as keys do case-insensitive lookups. + // + [Obsolete("Please use Hashtable(int, float, IEqualityComparer) instead.")] + public Hashtable(int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) : this(capacity, loadFactor) { + if (hcp == null && comparer == null) { + this._keycomparer = null; + } + else { + this._keycomparer = new CompatibleComparer(comparer,hcp); + } + } + + public Hashtable(int capacity, float loadFactor, IEqualityComparer equalityComparer) : this(capacity, loadFactor) { + this._keycomparer = equalityComparer; + } + + // Constructs a new hashtable using a custom hash function + // and a custom comparison function for keys. This will enable scenarios + // such as doing lookups with case-insensitive strings. + // + [Obsolete("Please use Hashtable(IEqualityComparer) instead.")] + public Hashtable(IHashCodeProvider hcp, IComparer comparer) : this(0, 1.0f, hcp, comparer) { + } + + public Hashtable(IEqualityComparer equalityComparer) : this(0, 1.0f, equalityComparer) { + } + + // Constructs a new hashtable using a custom hash function + // and a custom comparison function for keys. This will enable scenarios + // such as doing lookups with case-insensitive strings. + // + [Obsolete("Please use Hashtable(int, IEqualityComparer) instead.")] + public Hashtable(int capacity, IHashCodeProvider hcp, IComparer comparer) + : this(capacity, 1.0f, hcp, comparer) { + } + + public Hashtable(int capacity, IEqualityComparer equalityComparer) + : this(capacity, 1.0f, equalityComparer) { + } + + // Constructs a new hashtable containing a copy of the entries in the given + // dictionary. The hashtable is created with a load factor of 1.0. + // + public Hashtable(IDictionary d) : this(d, 1.0f) { + } + + // Constructs a new hashtable containing a copy of the entries in the given + // dictionary. The hashtable is created with the given load factor. + // + public Hashtable(IDictionary d, float loadFactor) + : this(d, loadFactor, (IEqualityComparer)null) { + } + + [Obsolete("Please use Hashtable(IDictionary, IEqualityComparer) instead.")] + public Hashtable(IDictionary d, IHashCodeProvider hcp, IComparer comparer) + : this(d, 1.0f, hcp, comparer) { + } + + public Hashtable(IDictionary d, IEqualityComparer equalityComparer) + : this(d, 1.0f, equalityComparer) { + } + + [Obsolete("Please use Hashtable(IDictionary, float, IEqualityComparer) instead.")] + public Hashtable(IDictionary d, float loadFactor, IHashCodeProvider hcp, IComparer comparer) + : this((d != null ? d.Count : 0), loadFactor, hcp, comparer) { + if (d==null) + throw new ArgumentNullException("d", Environment.GetResourceString("ArgumentNull_Dictionary")); + Contract.EndContractBlock(); + + IDictionaryEnumerator e = d.GetEnumerator(); + while (e.MoveNext()) Add(e.Key, e.Value); + } + + public Hashtable(IDictionary d, float loadFactor, IEqualityComparer equalityComparer) + : this((d != null ? d.Count : 0), loadFactor, equalityComparer) { + if (d==null) + throw new ArgumentNullException("d", Environment.GetResourceString("ArgumentNull_Dictionary")); + Contract.EndContractBlock(); + + IDictionaryEnumerator e = d.GetEnumerator(); + while (e.MoveNext()) Add(e.Key, e.Value); + } + + protected Hashtable(SerializationInfo info, StreamingContext context) { + //We can't do anything with the keys and values until the entire graph has been deserialized + //and we have a reasonable estimate that GetHashCode is not going to fail. For the time being, + //we'll just cache this. The graph is not valid until OnDeserialization has been called. + HashHelpers.SerializationInfoTable.Add(this, info); + } + + // ‘InitHash’ is basically an implementation of classic DoubleHashing (see http://en.wikipedia.org/wiki/Double_hashing) + // + // 1) The only ‘correctness’ requirement is that the ‘increment’ used to probe + // a. Be non-zero + // b. Be relatively prime to the table size ‘hashSize’. (This is needed to insure you probe all entries in the table before you ‘wrap’ and visit entries already probed) + // 2) Because we choose table sizes to be primes, we just need to insure that the increment is 0 < incr < hashSize + // + // Thus this function would work: Incr = 1 + (seed % (hashSize-1)) + // + // While this works well for ‘uniformly distributed’ keys, in practice, non-uniformity is common. + // In particular in practice we can see ‘mostly sequential’ where you get long clusters of keys that ‘pack’. + // To avoid bad behavior you want it to be the case that the increment is ‘large’ even for ‘small’ values (because small + // values tend to happen more in practice). Thus we multiply ‘seed’ by a number that will make these small values + // bigger (and not hurt large values). We picked HashPrime (101) because it was prime, and if ‘hashSize-1’ is not a multiple of HashPrime + // (enforced in GetPrime), then incr has the potential of being every value from 1 to hashSize-1. The choice was largely arbitrary. + // + // Computes the hash function: H(key, i) = h1(key) + i*h2(key, hashSize). + // The out parameter seed is h1(key), while the out parameter + // incr is h2(key, hashSize). Callers of this function should + // add incr each time through a loop. + private uint InitHash(Object key, int hashsize, out uint seed, out uint incr) { + // Hashcode must be positive. Also, we must not use the sign bit, since + // that is used for the collision bit. + uint hashcode = (uint) GetHash(key) & 0x7FFFFFFF; + seed = (uint) hashcode; + // Restriction: incr MUST be between 1 and hashsize - 1, inclusive for + // the modular arithmetic to work correctly. This guarantees you'll + // visit every bucket in the table exactly once within hashsize + // iterations. Violate this and it'll cause obscure bugs forever. + // If you change this calculation for h2(key), update putEntry too! + incr = (uint)(1 + ((seed * HashPrime) % ((uint)hashsize - 1))); + return hashcode; + } + + // Adds an entry with the given key and value to this hashtable. An + // ArgumentException is thrown if the key is null or if the key is already + // present in the hashtable. + // + public virtual void Add(Object key, Object value) { + Insert(key, value, true); + } + + // Removes all entries from this hashtable. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public virtual void Clear() { + Contract.Assert(!isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized."); + + if (count == 0 && occupancy == 0) + return; + +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + for (int i = 0; i < buckets.Length; i++){ + buckets[i].hash_coll = 0; + buckets[i].key = null; + buckets[i].val = null; + } + + count = 0; + occupancy = 0; + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + } + + // Clone returns a virtually identical copy of this hash table. This does + // a shallow copy - the Objects in the table aren't cloned, only the references + // to those Objects. + public virtual Object Clone() + { + bucket[] lbuckets = buckets; + Hashtable ht = new Hashtable(count,_keycomparer); + ht.version = version; + ht.loadFactor = loadFactor; + ht.count = 0; + + int bucket = lbuckets.Length; + while (bucket > 0) { + bucket--; + Object keyv = lbuckets[bucket].key; + if ((keyv!= null) && (keyv != lbuckets)) { + ht[keyv] = lbuckets[bucket].val; + } + } + + return ht; + } + + // Checks if this hashtable contains the given key. + public virtual bool Contains(Object key) { + return ContainsKey(key); + } + + // Checks if this hashtable contains an entry with the given key. This is + // an O(1) operation. + // + public virtual bool ContainsKey(Object key) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + + uint seed; + uint incr; + // Take a snapshot of buckets, in case another thread resizes table + bucket[] lbuckets = buckets; + uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr); + int ntry = 0; + + bucket b; + int bucketNumber = (int) (seed % (uint)lbuckets.Length); + do { + b = lbuckets[bucketNumber]; + if (b.key == null) { + return false; + } + if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && + KeyEquals (b.key, key)) + return true; + bucketNumber = (int) (((long)bucketNumber + incr)% (uint)lbuckets.Length); + } while (b.hash_coll < 0 && ++ntry < lbuckets.Length); + return false; + } + + + + // Checks if this hashtable contains an entry with the given value. The + // values of the entries of the hashtable are compared to the given value + // using the Object.Equals method. This method performs a linear + // search and is thus be substantially slower than the ContainsKey + // method. + // + public virtual bool ContainsValue(Object value) { + + if (value == null) { + for (int i = buckets.Length; --i >= 0;) { + if (buckets[i].key != null && buckets[i].key != buckets && buckets[i].val == null) + return true; + } + } + else { + for (int i = buckets.Length; --i >= 0;) { + Object val = buckets[i].val; + if (val!=null && val.Equals(value)) return true; + } + } + return false; + } + + // Copies the keys of this hashtable to a given array starting at a given + // index. This method is used by the implementation of the CopyTo method in + // the KeyCollection class. + private void CopyKeys(Array array, int arrayIndex) { + Contract.Requires(array != null); + Contract.Requires(array.Rank == 1); + + bucket[] lbuckets = buckets; + for (int i = lbuckets.Length; --i >= 0;) { + Object keyv = lbuckets[i].key; + if ((keyv != null) && (keyv != buckets)){ + array.SetValue(keyv, arrayIndex++); + } + } + } + + // Copies the keys of this hashtable to a given array starting at a given + // index. This method is used by the implementation of the CopyTo method in + // the KeyCollection class. + private void CopyEntries(Array array, int arrayIndex) { + Contract.Requires(array != null); + Contract.Requires(array.Rank == 1); + + bucket[] lbuckets = buckets; + for (int i = lbuckets.Length; --i >= 0;) { + Object keyv = lbuckets[i].key; + if ((keyv != null) && (keyv != buckets)){ + DictionaryEntry entry = new DictionaryEntry(keyv,lbuckets[i].val); + array.SetValue(entry, arrayIndex++); + } + } + } + + // Copies the values in this hash table to an array at + // a given index. Note that this only copies values, and not keys. + public virtual void CopyTo(Array array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Array")); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - arrayIndex < Count) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall")); + Contract.EndContractBlock(); + CopyEntries(array, arrayIndex); + } + + // Copies the values in this Hashtable to an KeyValuePairs array. + // KeyValuePairs is different from Dictionary Entry in that it has special + // debugger attributes on its fields. + + internal virtual KeyValuePairs[] ToKeyValuePairsArray() { + + KeyValuePairs[] array = new KeyValuePairs[count]; + int index = 0; + bucket[] lbuckets = buckets; + for (int i = lbuckets.Length; --i >= 0;) { + Object keyv = lbuckets[i].key; + if ((keyv != null) && (keyv != buckets)){ + array[index++] = new KeyValuePairs(keyv,lbuckets[i].val); + } + } + + return array; + } + + + // Copies the values of this hashtable to a given array starting at a given + // index. This method is used by the implementation of the CopyTo method in + // the ValueCollection class. + private void CopyValues(Array array, int arrayIndex) { + Contract.Requires(array != null); + Contract.Requires(array.Rank == 1); + + bucket[] lbuckets = buckets; + for (int i = lbuckets.Length; --i >= 0;) { + Object keyv = lbuckets[i].key; + if ((keyv != null) && (keyv != buckets)){ + array.SetValue(lbuckets[i].val, arrayIndex++); + } + } + } + + // Returns the value associated with the given key. If an entry with the + // given key is not found, the returned value is null. + // + public virtual Object this[Object key] { + get { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + + uint seed; + uint incr; + + + // Take a snapshot of buckets, in case another thread does a resize + bucket[] lbuckets = buckets; + uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr); + int ntry = 0; + + bucket b; + int bucketNumber = (int) (seed % (uint)lbuckets.Length); + do + { + int currentversion; + + // A read operation on hashtable has three steps: + // (1) calculate the hash and find the slot number. + // (2) compare the hashcode, if equal, go to step 3. Otherwise end. + // (3) compare the key, if equal, go to step 4. Otherwise end. + // (4) return the value contained in the bucket. + // After step 3 and before step 4. A writer can kick in a remove the old item and add a new one + // in the same bukcet. So in the reader we need to check if the hash table is modified during above steps. + // + // Writers (Insert, Remove, Clear) will set 'isWriterInProgress' flag before it starts modifying + // the hashtable and will ckear the flag when it is done. When the flag is cleared, the 'version' + // will be increased. We will repeat the reading if a writer is in progress or done with the modification + // during the read. + // + // Our memory model guarantee if we pick up the change in bucket from another processor, + // we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader. + // + int spinCount = 0; + do { + // this is violate read, following memory accesses can not be moved ahead of it. + currentversion = version; + b = lbuckets[bucketNumber]; + + // The contention between reader and writer shouldn't happen frequently. + // But just in case this will burn CPU, yield the control of CPU if we spinned a few times. + // 8 is just a random number I pick. + if( (++spinCount) % 8 == 0 ) { + Thread.Sleep(1); // 1 means we are yeilding control to all threads, including low-priority ones. + } + } while ( isWriterInProgress || (currentversion != version) ); + + if (b.key == null) { + return null; + } + if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && + KeyEquals (b.key, key)) + return b.val; + bucketNumber = (int) (((long)bucketNumber + incr)% (uint)lbuckets.Length); + } while (b.hash_coll < 0 && ++ntry < lbuckets.Length); + return null; + } + + set { + Insert(key, value, false); + } + } + + // Increases the bucket count of this hashtable. This method is called from + // the Insert method when the actual load factor of the hashtable reaches + // the upper limit specified when the hashtable was constructed. The number + // of buckets in the hashtable is increased to the smallest prime number + // that is larger than twice the current number of buckets, and the entries + // in the hashtable are redistributed into the new buckets using the cached + // hashcodes. + private void expand() { + int rawsize = HashHelpers.ExpandPrime(buckets.Length); + rehash(rawsize, false); + } + + // We occationally need to rehash the table to clean up the collision bits. + private void rehash() { + rehash( buckets.Length, false ); + } + + private void UpdateVersion() { + // Version might become negative when version is Int32.MaxValue, but the oddity will be still be correct. + // So we don't need to special case this. + version++; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void rehash( int newsize, bool forceNewHashCode ) { + + // reset occupancy + occupancy=0; + + // Don't replace any internal state until we've finished adding to the + // new bucket[]. This serves two purposes: + // 1) Allow concurrent readers to see valid hashtable contents + // at all times + // 2) Protect against an OutOfMemoryException while allocating this + // new bucket[]. + bucket[] newBuckets = new bucket[newsize]; + + // rehash table into new buckets + int nb; + for (nb = 0; nb < buckets.Length; nb++){ + bucket oldb = buckets[nb]; + if ((oldb.key != null) && (oldb.key != buckets)) { + int hashcode = ((forceNewHashCode ? GetHash(oldb.key) : oldb.hash_coll) & 0x7FFFFFFF); + putEntry(newBuckets, oldb.key, oldb.val, hashcode); + } + } + + // New bucket[] is good to go - replace buckets and other internal state. +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + buckets = newBuckets; + loadsize = (int)(loadFactor * newsize); + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + // minimun size of hashtable is 3 now and maximum loadFactor is 0.72 now. + Contract.Assert(loadsize < newsize, "Our current implementaion means this is not possible."); + return; + } + + // Returns an enumerator for this hashtable. + // If modifications made to the hashtable while an enumeration is + // in progress, the MoveNext and Current methods of the + // enumerator will throw an exception. + // + IEnumerator IEnumerable.GetEnumerator() { + return new HashtableEnumerator(this, HashtableEnumerator.DictEntry); + } + + // Returns a dictionary enumerator for this hashtable. + // If modifications made to the hashtable while an enumeration is + // in progress, the MoveNext and Current methods of the + // enumerator will throw an exception. + // + public virtual IDictionaryEnumerator GetEnumerator() { + return new HashtableEnumerator(this, HashtableEnumerator.DictEntry); + } + + // Internal method to get the hash code for an Object. This will call + // GetHashCode() on each object if you haven't provided an IHashCodeProvider + // instance. Otherwise, it calls hcp.GetHashCode(obj). + protected virtual int GetHash(Object key) + { + if (_keycomparer != null) + return _keycomparer.GetHashCode(key); + return key.GetHashCode(); + } + + // Is this Hashtable read-only? + public virtual bool IsReadOnly { + get { return false; } + } + + public virtual bool IsFixedSize { + get { return false; } + } + + // Is this Hashtable synchronized? See SyncRoot property + public virtual bool IsSynchronized { + get { return false; } + } + + // Internal method to compare two keys. If you have provided an IComparer + // instance in the constructor, this method will call comparer.Compare(item, key). + // Otherwise, it will call item.Equals(key). + // + protected virtual bool KeyEquals(Object item, Object key) + { + Contract.Assert(key != null, "key can't be null here!"); + if( Object.ReferenceEquals(buckets, item)) { + return false; + } + + if (Object.ReferenceEquals(item,key)) + return true; + + if (_keycomparer != null) + return _keycomparer.Equals(item, key); + return item == null ? false : item.Equals(key); + } + + // Returns a collection representing the keys of this hashtable. The order + // in which the returned collection represents the keys is unspecified, but + // it is guaranteed to be buckets = newBuckets; the same order in which a collection returned by + // GetValues represents the values of the hashtable. + // + // The returned collection is live in the sense that any changes + // to the hash table are reflected in this collection. It is not + // a static copy of all the keys in the hash table. + // + public virtual ICollection Keys { + get { + if (keys == null) keys = new KeyCollection(this); + return keys; + } + } + + // Returns a collection representing the values of this hashtable. The + // order in which the returned collection represents the values is + // unspecified, but it is guaranteed to be the same order in which a + // collection returned by GetKeys represents the keys of the + // hashtable. + // + // The returned collection is live in the sense that any changes + // to the hash table are reflected in this collection. It is not + // a static copy of all the keys in the hash table. + // + public virtual ICollection Values { + get { + if (values == null) values = new ValueCollection(this); + return values; + } + } + + // Inserts an entry into this hashtable. This method is called from the Set + // and Add methods. If the add parameter is true and the given key already + // exists in the hashtable, an exception is thrown. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void Insert (Object key, Object nvalue, bool add) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + if (count >= loadsize) { + expand(); + } + else if(occupancy > loadsize && count > 100) { + rehash(); + } + + uint seed; + uint incr; + // Assume we only have one thread writing concurrently. Modify + // buckets to contain new data, as long as we insert in the right order. + uint hashcode = InitHash(key, buckets.Length, out seed, out incr); + int ntry = 0; + int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots + // create by remove that have the collision bit set over using up new slots. + int bucketNumber = (int) (seed % (uint)buckets.Length); + do { + + // Set emptySlot number to current bucket if it is the first available bucket that we have seen + // that once contained an entry and also has had a collision. + // We need to search this entire collision chain because we have to ensure that there are no + // duplicate entries in the table. + if (emptySlotNumber == -1 && (buckets[bucketNumber].key == buckets) && (buckets[bucketNumber].hash_coll < 0))//(((buckets[bucketNumber].hash_coll & unchecked(0x80000000))!=0))) + emptySlotNumber = bucketNumber; + + // Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry + // OR + // This bucket once contained an entry but there has never been a collision + if ((buckets[bucketNumber].key == null) || + (buckets[bucketNumber].key == buckets && ((buckets[bucketNumber].hash_coll & unchecked(0x80000000))==0))) { + + // If we have found an available bucket that has never had a collision, but we've seen an available + // bucket in the past that has the collision bit set, use the previous bucket instead + if (emptySlotNumber != -1) // Reuse slot + bucketNumber = emptySlotNumber; + + // We pretty much have to insert in this order. Don't set hash + // code until the value & key are set appropriately. +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + buckets[bucketNumber].val = nvalue; + buckets[bucketNumber].key = key; + buckets[bucketNumber].hash_coll |= (int) hashcode; + count++; + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + // coreclr has the randomized string hashing on by default so we don't need to resize at this point + + if(ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer)) + { + // PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some + // cases there may not be any strings in the hashtable and we wouldn't get any mixing. + if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer)) + { + _keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer); + rehash(buckets.Length, true); + } + } +#endif // !FEATURE_CORECLR +#endif // FEATURE_RANDOMIZED_STRING_HASHING + return; + } + + // The current bucket is in use + // OR + // it is available, but has had the collision bit set and we have already found an available bucket + if (((buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) && + KeyEquals (buckets[bucketNumber].key, key)) { + if (add) { + throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", buckets[bucketNumber].key, key)); + } +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + buckets[bucketNumber].val = nvalue; + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + if(ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer)) + { + // PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some + // cases there may not be any strings in the hashtable and we wouldn't get any mixing. + if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer)) + { + _keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer); + rehash(buckets.Length, true); + } + } +#endif // !FEATURE_CORECLR +#endif + return; + } + + // The current bucket is full, and we have therefore collided. We need to set the collision bit + // UNLESS + // we have remembered an available slot previously. + if (emptySlotNumber == -1) {// We don't need to set the collision bit here since we already have an empty slot + if( buckets[bucketNumber].hash_coll >= 0 ) { + buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000); + occupancy++; + } + } + + bucketNumber = (int) (((long)bucketNumber + incr)% (uint)buckets.Length); + } while (++ntry < buckets.Length); + + // This code is here if and only if there were no buckets without a collision bit set in the entire table + if (emptySlotNumber != -1) + { + // We pretty much have to insert in this order. Don't set hash + // code until the value & key are set appropriately. +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + buckets[emptySlotNumber].val = nvalue; + buckets[emptySlotNumber].key = key; + buckets[emptySlotNumber].hash_coll |= (int) hashcode; + count++; + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + +#if FEATURE_RANDOMIZED_STRING_HASHING +#if !FEATURE_CORECLR + if(buckets.Length > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer)) + { + // PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some + // cases there may not be any strings in the hashtable and we wouldn't get any mixing. + if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer)) + { + _keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer); + rehash(buckets.Length, true); + } + } +#endif // !FEATURE_CORECLR +#endif + return; + } + + // If you see this assert, make sure load factor & count are reasonable. + // Then verify that our double hash function (h2, described at top of file) + // meets the requirements described above. You should never see this assert. + Contract.Assert(false, "hash table insert failed! Load factor too high, or our double hashing function is incorrect."); + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_HashInsertFailed")); + } + + private void putEntry (bucket[] newBuckets, Object key, Object nvalue, int hashcode) + { + Contract.Assert(hashcode >= 0, "hashcode >= 0"); // make sure collision bit (sign bit) wasn't set. + + uint seed = (uint) hashcode; + uint incr = (uint)(1 + ((seed * HashPrime) % ((uint)newBuckets.Length - 1))); + int bucketNumber = (int) (seed % (uint)newBuckets.Length); + do { + + if ((newBuckets[bucketNumber].key == null) || (newBuckets[bucketNumber].key == buckets)) { + newBuckets[bucketNumber].val = nvalue; + newBuckets[bucketNumber].key = key; + newBuckets[bucketNumber].hash_coll |= hashcode; + return; + } + + if( newBuckets[bucketNumber].hash_coll >= 0 ) { + newBuckets[bucketNumber].hash_coll |= unchecked((int)0x80000000); + occupancy++; + } + bucketNumber = (int) (((long)bucketNumber + incr)% (uint)newBuckets.Length); + } while (true); + } + + // Removes an entry from this hashtable. If an entry with the specified + // key exists in the hashtable, it is removed. An ArgumentException is + // thrown if the key is null. + // + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public virtual void Remove(Object key) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + Contract.Assert(!isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized."); + + uint seed; + uint incr; + // Assuming only one concurrent writer, write directly into buckets. + uint hashcode = InitHash(key, buckets.Length, out seed, out incr); + int ntry = 0; + + bucket b; + int bn = (int) (seed % (uint)buckets.Length); // bucketNumber + do { + b = buckets[bn]; + if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && + KeyEquals (b.key, key)) { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + isWriterInProgress = true; + // Clear hash_coll field, then key, then value + buckets[bn].hash_coll &= unchecked((int)0x80000000); + if (buckets[bn].hash_coll != 0) { + buckets[bn].key = buckets; + } + else { + buckets[bn].key = null; + } + buckets[bn].val = null; // Free object references sooner & simplify ContainsValue. + count--; + UpdateVersion(); + isWriterInProgress = false; +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + return; + } + bn = (int) (((long)bn + incr)% (uint)buckets.Length); + } while (b.hash_coll < 0 && ++ntry < buckets.Length); + + //throw new ArgumentException(Environment.GetResourceString("Arg_RemoveArgNotFound")); + } + + // Returns the object to synchronize on for this hash table. + public virtual Object SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + // Returns the number of associations in this hashtable. + // + public virtual int Count { + get { return count; } + } + + // Returns a thread-safe wrapper for a Hashtable. + // + [HostProtection(Synchronization=true)] + public static Hashtable Synchronized(Hashtable table) { + if (table==null) + throw new ArgumentNullException("table"); + Contract.EndContractBlock(); + return new SyncHashtable(table); + } + + // + // The ISerializable Implementation + // + + [System.Security.SecurityCritical] + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { + if (info==null) { + throw new ArgumentNullException("info"); + } + Contract.EndContractBlock(); + // This is imperfect - it only works well if all other writes are + // also using our synchronized wrapper. But it's still a good idea. + lock (SyncRoot) { + // This method hasn't been fully tweaked to be safe for a concurrent writer. + int oldVersion = version; + info.AddValue(LoadFactorName, loadFactor); + info.AddValue(VersionName, version); + + // + // We need to maintain serialization compatibility with Everett and RTM. + // If the comparer is null or a compatible comparer, serialize Hashtable + // in a format that can be deserialized on Everett and RTM. + // + // Also, if the Hashtable is using randomized hashing, serialize the old + // view of the _keycomparer so perevious frameworks don't see the new types +#pragma warning disable 618 +#if FEATURE_RANDOMIZED_STRING_HASHING + IEqualityComparer keyComparerForSerilization = (IEqualityComparer) HashHelpers.GetEqualityComparerForSerialization(_keycomparer); +#else + IEqualityComparer keyComparerForSerilization = _keycomparer; +#endif + + if( keyComparerForSerilization == null) { + info.AddValue(ComparerName, null,typeof(IComparer)); + info.AddValue(HashCodeProviderName, null, typeof(IHashCodeProvider)); + } + else if(keyComparerForSerilization is CompatibleComparer) { + CompatibleComparer c = keyComparerForSerilization as CompatibleComparer; + info.AddValue(ComparerName, c.Comparer, typeof(IComparer)); + info.AddValue(HashCodeProviderName, c.HashCodeProvider, typeof(IHashCodeProvider)); + } + else { + info.AddValue(KeyComparerName, keyComparerForSerilization, typeof(IEqualityComparer)); + } +#pragma warning restore 618 + + info.AddValue(HashSizeName, buckets.Length); //This is the length of the bucket array. + Object [] serKeys = new Object[count]; + Object [] serValues = new Object[count]; + CopyKeys(serKeys, 0); + CopyValues(serValues,0); + info.AddValue(KeysName, serKeys, typeof(Object[])); + info.AddValue(ValuesName, serValues, typeof(Object[])); + + // Explicitly check to see if anyone changed the Hashtable while we + // were serializing it. That's a race condition in their code. + if (version != oldVersion) + throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + } + } + + // + // DeserializationEvent Listener + // + public virtual void OnDeserialization(Object sender) { + if (buckets!=null) { + // Somebody had a dependency on this hashtable and fixed us up before the ObjectManager got to it. + return; + } + + SerializationInfo siInfo; + HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo); + + if (siInfo==null) { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidOnDeser")); + } + + int hashsize = 0; + IComparer c = null; + +#pragma warning disable 618 + IHashCodeProvider hcp = null; +#pragma warning restore 618 + + Object [] serKeys = null; + Object [] serValues = null; + + SerializationInfoEnumerator enumerator = siInfo.GetEnumerator(); + + while( enumerator.MoveNext()) + { + switch( enumerator.Name) + { + case LoadFactorName: + loadFactor = siInfo.GetSingle(LoadFactorName); + break; + case HashSizeName: + hashsize = siInfo.GetInt32(HashSizeName); + break; + case KeyComparerName: + _keycomparer = (IEqualityComparer)siInfo.GetValue(KeyComparerName, typeof(IEqualityComparer)); + break; + case ComparerName: + c = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + break; + case HashCodeProviderName: +#pragma warning disable 618 + hcp = (IHashCodeProvider)siInfo.GetValue(HashCodeProviderName, typeof(IHashCodeProvider)); +#pragma warning restore 618 + break; + case KeysName: + serKeys = (Object[])siInfo.GetValue(KeysName, typeof(Object[])); + break; + case ValuesName: + serValues = (Object[])siInfo.GetValue(ValuesName, typeof(Object[])); + break; + } + } + + loadsize = (int)(loadFactor*hashsize); + + // V1 object doesn't has _keycomparer field. + if ( (_keycomparer == null) && ( (c != null) || (hcp != null) ) ){ + _keycomparer = new CompatibleComparer(c,hcp); + } + + buckets = new bucket[hashsize]; + + if (serKeys==null) { + throw new SerializationException(Environment.GetResourceString("Serialization_MissingKeys")); + } + if (serValues==null) { + throw new SerializationException(Environment.GetResourceString("Serialization_MissingValues")); + } + if (serKeys.Length!=serValues.Length) { + throw new SerializationException(Environment.GetResourceString("Serialization_KeyValueDifferentSizes")); + } + for (int i=0; i<serKeys.Length; i++) { + if (serKeys[i]==null) { + throw new SerializationException(Environment.GetResourceString("Serialization_NullKey")); + } + Insert(serKeys[i], serValues[i], true); + } + + version = siInfo.GetInt32(VersionName); + + HashHelpers.SerializationInfoTable.Remove(this); + } + + + // Implements a Collection for the keys of a hashtable. An instance of this + // class is created by the GetKeys method of a hashtable. + [Serializable] + private class KeyCollection : ICollection + { + private Hashtable _hashtable; + + internal KeyCollection(Hashtable hashtable) { + _hashtable = hashtable; + } + + public virtual void CopyTo(Array array, int arrayIndex) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (array.Length - arrayIndex < _hashtable.count) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall")); + _hashtable.CopyKeys(array, arrayIndex); + } + + public virtual IEnumerator GetEnumerator() { + return new HashtableEnumerator(_hashtable, HashtableEnumerator.Keys); + } + + public virtual bool IsSynchronized { + get { return _hashtable.IsSynchronized; } + } + + public virtual Object SyncRoot { + get { return _hashtable.SyncRoot; } + } + + public virtual int Count { + get { return _hashtable.count; } + } + } + + // Implements a Collection for the values of a hashtable. An instance of + // this class is created by the GetValues method of a hashtable. + [Serializable] + private class ValueCollection : ICollection + { + private Hashtable _hashtable; + + internal ValueCollection(Hashtable hashtable) { + _hashtable = hashtable; + } + + public virtual void CopyTo(Array array, int arrayIndex) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (array.Length - arrayIndex < _hashtable.count) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall")); + _hashtable.CopyValues(array, arrayIndex); + } + + public virtual IEnumerator GetEnumerator() { + return new HashtableEnumerator(_hashtable, HashtableEnumerator.Values); + } + + public virtual bool IsSynchronized { + get { return _hashtable.IsSynchronized; } + } + + public virtual Object SyncRoot { + get { return _hashtable.SyncRoot; } + } + + public virtual int Count { + get { return _hashtable.count; } + } + } + + // Synchronized wrapper for hashtable + [Serializable] + private class SyncHashtable : Hashtable, IEnumerable + { + protected Hashtable _table; + + internal SyncHashtable(Hashtable table) : base(false) { + _table = table; + } + + internal SyncHashtable(SerializationInfo info, StreamingContext context) : base (info, context) { + _table = (Hashtable)info.GetValue("ParentTable", typeof(Hashtable)); + if (_table==null) { + throw new SerializationException(Environment.GetResourceString("Serialization_InsufficientState")); + } + } + + + /*================================GetObjectData================================= + **Action: Return a serialization info containing a reference to _table. We need + ** to implement this because our parent HT does and we don't want to actually + ** serialize all of it's values (just a reference to the table, which will then + ** be serialized separately.) + **Returns: void + **Arguments: info -- the SerializationInfo into which to store the data. + ** context -- the StreamingContext for the current serialization (ignored) + **Exceptions: ArgumentNullException if info is null. + ==============================================================================*/ + [System.Security.SecurityCritical] // auto-generated + public override void GetObjectData(SerializationInfo info, StreamingContext context) { + if (info==null) { + throw new ArgumentNullException("info"); + } + Contract.EndContractBlock(); + // Our serialization code hasn't been fully tweaked to be safe + // for a concurrent writer. + lock (_table.SyncRoot) { + info.AddValue("ParentTable", _table, typeof(Hashtable)); + } + } + + public override int Count { + get { return _table.Count; } + } + + public override bool IsReadOnly { + get { return _table.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return _table.IsFixedSize; } + } + + public override bool IsSynchronized { + get { return true; } + } + + public override Object this[Object key] { + get { + return _table[key]; + } + set { + lock(_table.SyncRoot) { + _table[key] = value; + } + } + } + + public override Object SyncRoot { + get { return _table.SyncRoot; } + } + + public override void Add(Object key, Object value) { + lock(_table.SyncRoot) { + _table.Add(key, value); + } + } + + public override void Clear() { + lock(_table.SyncRoot) { + _table.Clear(); + } + } + + public override bool Contains(Object key) { + return _table.Contains(key); + } + + public override bool ContainsKey(Object key) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + return _table.ContainsKey(key); + } + + public override bool ContainsValue(Object key) { + lock(_table.SyncRoot) { + return _table.ContainsValue(key); + } + } + + public override void CopyTo(Array array, int arrayIndex) { + lock (_table.SyncRoot) { + _table.CopyTo(array, arrayIndex); + } + } + + public override Object Clone() { + lock (_table.SyncRoot) { + return Hashtable.Synchronized((Hashtable)_table.Clone()); + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return _table.GetEnumerator(); + } + + public override IDictionaryEnumerator GetEnumerator() { + return _table.GetEnumerator(); + } + + public override ICollection Keys { + get { + lock(_table.SyncRoot) { + return _table.Keys; + } + } + } + + public override ICollection Values { + get { + lock(_table.SyncRoot) { + return _table.Values; + } + } + } + + public override void Remove(Object key) { + lock(_table.SyncRoot) { + _table.Remove(key); + } + } + + /*==============================OnDeserialization=============================== + **Action: Does nothing. We have to implement this because our parent HT implements it, + ** but it doesn't do anything meaningful. The real work will be done when we + ** call OnDeserialization on our parent table. + **Returns: void + **Arguments: None + **Exceptions: None + ==============================================================================*/ + public override void OnDeserialization(Object sender) { + return; + } + + internal override KeyValuePairs[] ToKeyValuePairsArray() { + return _table.ToKeyValuePairsArray(); + } + + } + + + // Implements an enumerator for a hashtable. The enumerator uses the + // internal version number of the hashtabke to ensure that no modifications + // are made to the hashtable while an enumeration is in progress. + [Serializable] + private class HashtableEnumerator : IDictionaryEnumerator, ICloneable + { + private Hashtable hashtable; + private int bucket; + private int version; + private bool current; + private int getObjectRetType; // What should GetObject return? + private Object currentKey; + private Object currentValue; + + internal const int Keys = 1; + internal const int Values = 2; + internal const int DictEntry = 3; + + internal HashtableEnumerator(Hashtable hashtable, int getObjRetType) { + this.hashtable = hashtable; + bucket = hashtable.buckets.Length; + version = hashtable.version; + current = false; + getObjectRetType = getObjRetType; + } + + public Object Clone() { + return MemberwiseClone(); + } + + public virtual Object Key { + get { + if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + return currentKey; + } + } + + public virtual bool MoveNext() { + if (version != hashtable.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + while (bucket > 0) { + bucket--; + Object keyv = hashtable.buckets[bucket].key; + if ((keyv!= null) && (keyv != hashtable.buckets)) { + currentKey = keyv; + currentValue = hashtable.buckets[bucket].val; + current = true; + return true; + } + } + current = false; + return false; + } + + public virtual DictionaryEntry Entry { + get { + if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen)); + return new DictionaryEntry(currentKey, currentValue); + } + } + + + public virtual Object Current { + get { + if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen)); + + if (getObjectRetType==Keys) + return currentKey; + else if (getObjectRetType==Values) + return currentValue; + else + return new DictionaryEntry(currentKey, currentValue); + } + } + + public virtual Object Value { + get { + if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen)); + return currentValue; + } + } + + public virtual void Reset() { + if (version != hashtable.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + current = false; + bucket = hashtable.buckets.Length; + currentKey = null; + currentValue = null; + } + } + + // internal debug view class for hashtable + internal class HashtableDebugView { + private Hashtable hashtable; + + public HashtableDebugView( Hashtable hashtable) { + if( hashtable == null) { + throw new ArgumentNullException( "hashtable"); + } + Contract.EndContractBlock(); + + this.hashtable = hashtable; + } + + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePairs[] Items { + get { + return hashtable.ToKeyValuePairsArray(); + } + } + } + } + + [FriendAccessAllowed] + internal static class HashHelpers + { + +#if FEATURE_RANDOMIZED_STRING_HASHING + public const int HashCollisionThreshold = 100; + public static bool s_UseRandomizedStringHashing = String.UseRandomizedHashing(); +#endif + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; + + // Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo + // object until OnDeserialization is called. + private static ConditionalWeakTable<object, SerializationInfo> s_SerializationInfoTable; + + internal static ConditionalWeakTable<object, SerializationInfo> SerializationInfoTable + { + get + { + if(s_SerializationInfoTable == null) + { + ConditionalWeakTable<object, SerializationInfo> newTable = new ConditionalWeakTable<object, SerializationInfo>(); + Interlocked.CompareExchange(ref s_SerializationInfoTable, newTable, null); + } + + return s_SerializationInfoTable; + } + + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt (candidate); + for (int divisor = 3; divisor <= limit; divisor+=2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return (candidate == 2); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow")); + Contract.EndContractBlock(); + + for (int i = 0; i < primes.Length; i++) + { + int prime = primes[i]; + if (prime >= min) return prime; + } + + //outside of our predefined table. + //compute the hard way. + for (int i = (min | 1); i < Int32.MaxValue;i+=2) + { + if (IsPrime(i) && ((i - 1) % Hashtable.HashPrime != 0)) + return i; + } + return min; + } + + public static int GetMinPrime() + { + return primes[0]; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Contract.Assert( MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + +#if FEATURE_RANDOMIZED_STRING_HASHING + public static bool IsWellKnownEqualityComparer(object comparer) + { + return (comparer == null || comparer == System.Collections.Generic.EqualityComparer<string>.Default || comparer is IWellKnownStringEqualityComparer); + } + + public static IEqualityComparer GetRandomizedEqualityComparer(object comparer) + { + Contract.Assert(comparer == null || comparer == System.Collections.Generic.EqualityComparer<string>.Default || comparer is IWellKnownStringEqualityComparer); + + if(comparer == null) { + return new System.Collections.Generic.RandomizedObjectEqualityComparer(); + } + + if(comparer == System.Collections.Generic.EqualityComparer<string>.Default) { + return new System.Collections.Generic.RandomizedStringEqualityComparer(); + } + + IWellKnownStringEqualityComparer cmp = comparer as IWellKnownStringEqualityComparer; + + if(cmp != null) { + return cmp.GetRandomizedEqualityComparer(); + } + + Contract.Assert(false, "Missing case in GetRandomizedEqualityComparer!"); + + return null; + } + + public static object GetEqualityComparerForSerialization(object comparer) + { + if(comparer == null) + { + return null; + } + + IWellKnownStringEqualityComparer cmp = comparer as IWellKnownStringEqualityComparer; + + if(cmp != null) + { + return cmp.GetEqualityComparerForSerialization(); + } + + return comparer; + } + + private const int bufferSize = 1024; +#if !FEATURE_CORECLR + private static RandomNumberGenerator rng; +#endif + private static byte[] data; + private static int currentIndex = bufferSize; + private static readonly object lockObj = new Object(); + + internal static long GetEntropy() + { + lock(lockObj) { + long ret; + + if(currentIndex == bufferSize) + { + if(data == null) + { + data = new byte[bufferSize]; + Contract.Assert(bufferSize % 8 == 0, "We increment our current index by 8, so our buffer size must be a multiple of 8"); +#if !FEATURE_CORECLR + rng = RandomNumberGenerator.Create(); +#endif + + } + +#if FEATURE_CORECLR + Microsoft.Win32.Win32Native.Random(true, data, data.Length); +#else + rng.GetBytes(data); +#endif + currentIndex = 0; + } + + ret = BitConverter.ToInt64(data, currentIndex); + currentIndex += 8; + + return ret; + } + } +#endif // FEATURE_RANDOMIZED_STRING_HASHING + } +} diff --git a/src/mscorlib/src/System/Collections/ICollection.cs b/src/mscorlib/src/System/Collections/ICollection.cs new file mode 100644 index 0000000000..6d4d6e1a93 --- /dev/null +++ b/src/mscorlib/src/System/Collections/ICollection.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: ICollection +** +** +** +** +** Purpose: Base interface for all collections. +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Diagnostics.Contracts; + + // Base interface for all collections, defining enumerators, size, and + // synchronization methods. + [System.Runtime.InteropServices.ComVisible(true)] + public interface ICollection : IEnumerable + { + // Interfaces are not serialable + // CopyTo copies a collection into an Array, starting at a particular + // index into the array. + // + void CopyTo(Array array, int index); + + // Number of items in the collections. + int Count + { get; } + + + // SyncRoot will return an Object to use for synchronization + // (thread safety). You can use this object in your code to take a + // lock on the collection, even if this collection is a wrapper around + // another collection. The intent is to tunnel through to a real + // implementation of a collection, and use one of the internal objects + // found in that code. + // + // In the absense of a static Synchronized method on a collection, + // the expected usage for SyncRoot would look like this: + // + // ICollection col = ... + // lock (col.SyncRoot) { + // // Some operation on the collection, which is now thread safe. + // // This may include multiple operations. + // } + // + // + // The system-provided collections have a static method called + // Synchronized which will create a thread-safe wrapper around the + // collection. All access to the collection that you want to be + // thread-safe should go through that wrapper collection. However, if + // you need to do multiple calls on that collection (such as retrieving + // two items, or checking the count then doing something), you should + // NOT use our thread-safe wrapper since it only takes a lock for the + // duration of a single method call. Instead, use Monitor.Enter/Exit + // or your language's equivalent to the C# lock keyword as mentioned + // above. + // + // For collections with no publically available underlying store, the + // expected implementation is to simply return the this pointer. Note + // that the this pointer may not be sufficient for collections that + // wrap other collections; those should return the underlying + // collection's SyncRoot property. + Object SyncRoot + { get; } + + // Is this collection synchronized (i.e., thread-safe)? If you want a + // thread-safe collection, you can use SyncRoot as an object to + // synchronize your collection with. If you're using one of the + // collections in System.Collections, you could call the static + // Synchronized method to get a thread-safe wrapper around the + // underlying collection. + bool IsSynchronized + { get; } + } +} diff --git a/src/mscorlib/src/System/Collections/IComparer.cs b/src/mscorlib/src/System/Collections/IComparer.cs new file mode 100644 index 0000000000..d5a3448934 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IComparer.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IComparer +** +** +** +** +** Purpose: Interface for comparing two Objects. +** +** +===========================================================*/ +namespace System.Collections { + + using System; + // The IComparer interface implements a method that compares two objects. It is + // used in conjunction with the Sort and BinarySearch methods on + // the Array and List classes. + // + // Interfaces are not serializable + [System.Runtime.InteropServices.ComVisible(true)] + public interface IComparer { + // Compares two objects. An implementation of this method must return a + // value less than zero if x is less than y, zero if x is equal to y, or a + // value greater than zero if x is greater than y. + // + int Compare(Object x, Object y); + } +} diff --git a/src/mscorlib/src/System/Collections/IDictionary.cs b/src/mscorlib/src/System/Collections/IDictionary.cs new file mode 100644 index 0000000000..4da89d6983 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IDictionary.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IDictionary +** +** +** +** +** Purpose: Base interface for all dictionaries. +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Diagnostics.Contracts; + + // An IDictionary is a possibly unordered set of key-value pairs. + // Keys can be any non-null object. Values can be any object. + // You can look up a value in an IDictionary via the default indexed + // property, Items. + [System.Runtime.InteropServices.ComVisible(true)] + public interface IDictionary : ICollection + { + // Interfaces are not serializable + // The Item property provides methods to read and edit entries + // in the Dictionary. + Object this[Object key] { + get; + set; + } + + // Returns a collections of the keys in this dictionary. + ICollection Keys { + get; + } + + // Returns a collections of the values in this dictionary. + ICollection Values { + get; + } + + // Returns whether this dictionary contains a particular key. + // + bool Contains(Object key); + + // Adds a key-value pair to the dictionary. + // + void Add(Object key, Object value); + + // Removes all pairs from the dictionary. + void Clear(); + + bool IsReadOnly + { get; } + + bool IsFixedSize + { get; } + + // Returns an IDictionaryEnumerator for this dictionary. + new IDictionaryEnumerator GetEnumerator(); + + // Removes a particular key from the dictionary. + // + void Remove(Object key); + } +} diff --git a/src/mscorlib/src/System/Collections/IDictionaryEnumerator.cs b/src/mscorlib/src/System/Collections/IDictionaryEnumerator.cs new file mode 100644 index 0000000000..b995a04a0e --- /dev/null +++ b/src/mscorlib/src/System/Collections/IDictionaryEnumerator.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IDictionaryEnumerator +** +** +** +** +** Purpose: Base interface for dictionary enumerators. +** +** +===========================================================*/ +namespace System.Collections { + // Interfaces are not serializable + + using System; + // This interface represents an enumerator that allows sequential access to the + // elements of a dictionary. Upon creation, an enumerator is conceptually + // positioned before the first element of the enumeration. The first call to the + // MoveNext method brings the first element of the enumeration into view, + // and each successive call to MoveNext brings the next element into + // view until MoveNext returns false, indicating that there are no more + // elements to enumerate. Following each call to MoveNext, the + // Key and Value methods are used to obtain the key and + // value of the element currently in view. The values returned by calls to + // Key and Value are undefined before the first call to + // MoveNext and following a call to MoveNext that returned false. + // Enumerators are typically used in while loops of the form + // + // IDictionaryEnumerator e = ...; + // while (e.MoveNext()) { + // Object key = e.Key; + // Object value = e.Value; + // ... + // } + // + // The IDictionaryEnumerator interface extends the IEnumerator + // inerface and can thus be used as a regular enumerator. The Current + // method of an IDictionaryEnumerator returns a DictionaryEntry containing + // the current key and value pair. However, the GetEntry method will + // return the same DictionaryEntry and avoids boxing the DictionaryEntry (boxing + // is somewhat expensive). + // +[System.Runtime.InteropServices.ComVisible(true)] + public interface IDictionaryEnumerator : IEnumerator + { + // Returns the key of the current element of the enumeration. The returned + // value is undefined before the first call to GetNext and following + // a call to GetNext that returned false. Multiple calls to + // GetKey with no intervening calls to GetNext will return + // the same object. + // + Object Key { + get; + } + + // Returns the value of the current element of the enumeration. The + // returned value is undefined before the first call to GetNext and + // following a call to GetNext that returned false. Multiple calls + // to GetValue with no intervening calls to GetNext will + // return the same object. + // + Object Value { + get; + } + + // GetBlock will copy dictionary values into the given Array. It will either + // fill up the array, or if there aren't enough elements, it will + // copy as much as possible into the Array. The number of elements + // copied is returned. + // + DictionaryEntry Entry { + get; + } + } +} diff --git a/src/mscorlib/src/System/Collections/IEnumerable.cs b/src/mscorlib/src/System/Collections/IEnumerable.cs new file mode 100644 index 0000000000..5fa63f15d0 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IEnumerable.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IEnumerable +** +** +** +** +** Purpose: Interface for classes providing IEnumerators +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Diagnostics.Contracts; + using System.Runtime.InteropServices; + + // Implement this interface if you need to support VB's foreach semantics. + // Also, COM classes that support an enumerator will also implement this interface. + [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")] + [System.Runtime.InteropServices.ComVisible(true)] + public interface IEnumerable + { + // Interfaces are not serializable + // Returns an IEnumerator for this enumerable Object. The enumerator provides + // a simple way to access all the contents of a collection. + [Pure] + [DispId(-4)] + IEnumerator GetEnumerator(); + } +} diff --git a/src/mscorlib/src/System/Collections/IEnumerator.cs b/src/mscorlib/src/System/Collections/IEnumerator.cs new file mode 100644 index 0000000000..99509d15ea --- /dev/null +++ b/src/mscorlib/src/System/Collections/IEnumerator.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IEnumerator +** +** +** +** +** Purpose: Base interface for all enumerators. +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Runtime.InteropServices; + + // Base interface for all enumerators, providing a simple approach + // to iterating over a collection. + [Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")] + [System.Runtime.InteropServices.ComVisible(true)] + public interface IEnumerator + { + // Interfaces are not serializable + // Advances the enumerator to the next element of the enumeration and + // returns a boolean indicating whether an element is available. Upon + // creation, an enumerator is conceptually positioned before the first + // element of the enumeration, and the first call to MoveNext + // brings the first element of the enumeration into view. + // + bool MoveNext(); + + // Returns the current element of the enumeration. The returned value is + // undefined before the first call to MoveNext and following a + // call to MoveNext that returned false. Multiple calls to + // GetCurrent with no intervening calls to MoveNext + // will return the same object. + // + Object Current { + get; + } + + // Resets the enumerator to the beginning of the enumeration, starting over. + // The preferred behavior for Reset is to return the exact same enumeration. + // This means if you modify the underlying collection then call Reset, your + // IEnumerator will be invalid, just as it would have been if you had called + // MoveNext or Current. + // + void Reset(); + } +} diff --git a/src/mscorlib/src/System/Collections/IEqualityComparer.cs b/src/mscorlib/src/System/Collections/IEqualityComparer.cs new file mode 100644 index 0000000000..50c732f47c --- /dev/null +++ b/src/mscorlib/src/System/Collections/IEqualityComparer.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IEqualityComparer +** +** +** +** +** Purpose: A mechanism to expose a simplified infrastructure for +** Comparing objects in collections. +** +** +===========================================================*/ +namespace System.Collections { + + using System; + // An IEqualityComparer is a mechanism to consume custom performant comparison infrastructure + // that can be consumed by some of the common collections. + [System.Runtime.InteropServices.ComVisible(true)] + public interface IEqualityComparer { + bool Equals(Object x, Object y); + int GetHashCode(Object obj); + } +} diff --git a/src/mscorlib/src/System/Collections/IHashCodeProvider.cs b/src/mscorlib/src/System/Collections/IHashCodeProvider.cs new file mode 100644 index 0000000000..aefa15e1e5 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IHashCodeProvider.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IHashCodeProvider +** +** +** +** +** Purpose: A bunch of strings. +** +** +===========================================================*/ +namespace System.Collections { + + using System; + // Provides a mechanism for a hash table user to override the default + // GetHashCode() function on Objects, providing their own hash function. + [Obsolete("Please use IEqualityComparer instead.")] + [System.Runtime.InteropServices.ComVisible(true)] + public interface IHashCodeProvider + { + // Interfaces are not serializable + // Returns a hash code for the given object. + // + int GetHashCode (Object obj); + } +} diff --git a/src/mscorlib/src/System/Collections/IList.cs b/src/mscorlib/src/System/Collections/IList.cs new file mode 100644 index 0000000000..8bdfe4c1d0 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IList.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Interface: IList +** +** +** +** +** Purpose: Base interface for all Lists. +** +** +===========================================================*/ +namespace System.Collections { + + using System; + using System.Diagnostics.Contracts; + + // An IList is an ordered collection of objects. The exact ordering + // is up to the implementation of the list, ranging from a sorted + // order to insertion order. + [System.Runtime.InteropServices.ComVisible(true)] + public interface IList : ICollection + { + // The Item property provides methods to read and edit entries in the List. + Object this[int index] { + get; + set; + } + + // Adds an item to the list. The exact position in the list is + // implementation-dependent, so while ArrayList may always insert + // in the last available location, a SortedList most likely would not. + // The return value is the position the new element was inserted in. + int Add(Object value); + + // Returns whether the list contains a particular item. + bool Contains(Object value); + + // Removes all items from the list. + void Clear(); + + bool IsReadOnly + { get; } + + + bool IsFixedSize + { + get; + } + + + // Returns the index of a particular item, if it is in the list. + // Returns -1 if the item isn't in the list. + int IndexOf(Object value); + + // Inserts value into the list at position index. + // index must be non-negative and less than or equal to the + // number of elements in the list. If index equals the number + // of items in the list, then value is appended to the end. + void Insert(int index, Object value); + + // Removes an item from the list. + void Remove(Object value); + + // Removes the item at position index. + void RemoveAt(int index); + } +} diff --git a/src/mscorlib/src/System/Collections/IStructuralComparable.cs b/src/mscorlib/src/System/Collections/IStructuralComparable.cs new file mode 100644 index 0000000000..1b6f3aff7a --- /dev/null +++ b/src/mscorlib/src/System/Collections/IStructuralComparable.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; + +namespace System.Collections { + + public interface IStructuralComparable { + Int32 CompareTo(Object other, IComparer comparer); + } +} diff --git a/src/mscorlib/src/System/Collections/IStructuralEquatable.cs b/src/mscorlib/src/System/Collections/IStructuralEquatable.cs new file mode 100644 index 0000000000..5a5295fc38 --- /dev/null +++ b/src/mscorlib/src/System/Collections/IStructuralEquatable.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +namespace System.Collections { + + public interface IStructuralEquatable { + Boolean Equals(Object other, IEqualityComparer comparer); + int GetHashCode(IEqualityComparer comparer); + } +} diff --git a/src/mscorlib/src/System/Collections/KeyValuePairs.cs b/src/mscorlib/src/System/Collections/KeyValuePairs.cs new file mode 100644 index 0000000000..22bf92c456 --- /dev/null +++ b/src/mscorlib/src/System/Collections/KeyValuePairs.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: KeyValuePairs to display items in collection class under debugger +** +** +===========================================================*/ + +namespace System.Collections { + using System.Diagnostics; + + [DebuggerDisplay("{value}", Name = "[{key}]", Type = "" )] + internal class KeyValuePairs { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private object key; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private object value; + + public KeyValuePairs(object key, object value) { + this.value = value; + this.key = key; + } + + public object Key { + get { return key; } + } + + public object Value { + get { return value; } + } + } +} diff --git a/src/mscorlib/src/System/Collections/ListDictionaryInternal.cs b/src/mscorlib/src/System/Collections/ListDictionaryInternal.cs new file mode 100644 index 0000000000..944523c475 --- /dev/null +++ b/src/mscorlib/src/System/Collections/ListDictionaryInternal.cs @@ -0,0 +1,429 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*============================================================ +** +** +** +** +** +** Purpose: List for exceptions. +** +** +===========================================================*/ +using System.Diagnostics.Contracts; +namespace System.Collections { + /// This is a simple implementation of IDictionary using a singly linked list. This + /// will be smaller and faster than a Hashtable if the number of elements is 10 or less. + /// This should not be used if performance is important for large numbers of elements. + [Serializable] + internal class ListDictionaryInternal: IDictionary { + DictionaryNode head; + int version; + int count; + [NonSerialized] + private Object _syncRoot; + + public ListDictionaryInternal() { + } + + public Object this[Object key] { + get { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + DictionaryNode node = head; + + while (node != null) { + if ( node.key.Equals(key) ) { + return node.value; + } + node = node.next; + } + return null; + } + set { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + +#if FEATURE_SERIALIZATION + if (!key.GetType().IsSerializable) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "key"); + + if( (value != null) && (!value.GetType().IsSerializable ) ) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "value"); +#endif + + version++; + DictionaryNode last = null; + DictionaryNode node; + for (node = head; node != null; node = node.next) { + if( node.key.Equals(key) ) { + break; + } + last = node; + } + if (node != null) { + // Found it + node.value = value; + return; + } + // Not found, so add a new one + DictionaryNode newNode = new DictionaryNode(); + newNode.key = key; + newNode.value = value; + if (last != null) { + last.next = newNode; + } + else { + head = newNode; + } + count++; + } + } + + public int Count { + get { + return count; + } + } + + public ICollection Keys { + get { + return new NodeKeyValueCollection(this, true); + } + } + + public bool IsReadOnly { + get { + return false; + } + } + + public bool IsFixedSize { + get { + return false; + } + } + + public bool IsSynchronized { + get { + return false; + } + } + + public Object SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + public ICollection Values { + get { + return new NodeKeyValueCollection(this, false); + } + } + + public void Add(Object key, Object value) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + +#if FEATURE_SERIALIZATION + if (!key.GetType().IsSerializable) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "key" ); + + if( (value != null) && (!value.GetType().IsSerializable) ) + throw new ArgumentException(Environment.GetResourceString("Argument_NotSerializable"), "value"); +#endif + + version++; + DictionaryNode last = null; + DictionaryNode node; + for (node = head; node != null; node = node.next) { + if (node.key.Equals(key)) { + throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", node.key, key)); + } + last = node; + } + if (node != null) { + // Found it + node.value = value; + return; + } + // Not found, so add a new one + DictionaryNode newNode = new DictionaryNode(); + newNode.key = key; + newNode.value = value; + if (last != null) { + last.next = newNode; + } + else { + head = newNode; + } + count++; + } + + public void Clear() { + count = 0; + head = null; + version++; + } + + public bool Contains(Object key) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + for (DictionaryNode node = head; node != null; node = node.next) { + if (node.key.Equals(key)) { + return true; + } + } + return false; + } + + public void CopyTo(Array array, int index) { + if (array==null) + throw new ArgumentNullException("array"); + + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + + if ( array.Length - index < this.Count ) + throw new ArgumentException( Environment.GetResourceString("ArgumentOutOfRange_Index"), "index"); + Contract.EndContractBlock(); + + for (DictionaryNode node = head; node != null; node = node.next) { + array.SetValue(new DictionaryEntry(node.key, node.value), index); + index++; + } + } + + public IDictionaryEnumerator GetEnumerator() { + return new NodeEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() { + return new NodeEnumerator(this); + } + + public void Remove(Object key) { + if (key == null) { + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + } + Contract.EndContractBlock(); + version++; + DictionaryNode last = null; + DictionaryNode node; + for (node = head; node != null; node = node.next) { + if (node.key.Equals(key)) { + break; + } + last = node; + } + if (node == null) { + return; + } + if (node == head) { + head = node.next; + } else { + last.next = node.next; + } + count--; + } + + private class NodeEnumerator : IDictionaryEnumerator { + ListDictionaryInternal list; + DictionaryNode current; + int version; + bool start; + + + public NodeEnumerator(ListDictionaryInternal list) { + this.list = list; + version = list.version; + start = true; + current = null; + } + + public Object Current { + get { + return Entry; + } + } + + public DictionaryEntry Entry { + get { + if (current == null) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + return new DictionaryEntry(current.key, current.value); + } + } + + public Object Key { + get { + if (current == null) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + return current.key; + } + } + + public Object Value { + get { + if (current == null) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + return current.value; + } + } + + public bool MoveNext() { + if (version != list.version) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + } + if (start) { + current = list.head; + start = false; + } + else { + if( current != null ) { + current = current.next; + } + } + return (current != null); + } + + public void Reset() { + if (version != list.version) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + } + start = true; + current = null; + } + + } + + + private class NodeKeyValueCollection : ICollection { + ListDictionaryInternal list; + bool isKeys; + + public NodeKeyValueCollection(ListDictionaryInternal list, bool isKeys) { + this.list = list; + this.isKeys = isKeys; + } + + void ICollection.CopyTo(Array array, int index) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (array.Length - index < list.Count) + throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_Index"), "index"); + for (DictionaryNode node = list.head; node != null; node = node.next) { + array.SetValue(isKeys ? node.key : node.value, index); + index++; + } + } + + int ICollection.Count { + get { + int count = 0; + for (DictionaryNode node = list.head; node != null; node = node.next) { + count++; + } + return count; + } + } + + bool ICollection.IsSynchronized { + get { + return false; + } + } + + Object ICollection.SyncRoot { + get { + return list.SyncRoot; + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return new NodeKeyValueEnumerator(list, isKeys); + } + + + private class NodeKeyValueEnumerator: IEnumerator { + ListDictionaryInternal list; + DictionaryNode current; + int version; + bool isKeys; + bool start; + + public NodeKeyValueEnumerator(ListDictionaryInternal list, bool isKeys) { + this.list = list; + this.isKeys = isKeys; + this.version = list.version; + this.start = true; + this.current = null; + } + + public Object Current { + get { + if (current == null) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + } + return isKeys ? current.key : current.value; + } + } + + public bool MoveNext() { + if (version != list.version) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + } + if (start) { + current = list.head; + start = false; + } + else { + if( current != null) { + current = current.next; + } + } + return (current != null); + } + + public void Reset() { + if (version != list.version) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + } + start = true; + current = null; + } + } + } + + [Serializable] + private class DictionaryNode { + public Object key; + public Object value; + public DictionaryNode next; + } + } +} diff --git a/src/mscorlib/src/System/Collections/ObjectModel/Collection.cs b/src/mscorlib/src/System/Collections/ObjectModel/Collection.cs new file mode 100644 index 0000000000..54aa7bb09d --- /dev/null +++ b/src/mscorlib/src/System/Collections/ObjectModel/Collection.cs @@ -0,0 +1,328 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Collections.ObjectModel +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime; + + [Serializable] + [System.Runtime.InteropServices.ComVisible(false)] + [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + public class Collection<T>: IList<T>, IList, IReadOnlyList<T> + { + IList<T> items; + [NonSerialized] + private Object _syncRoot; + + public Collection() { + items = new List<T>(); + } + + public Collection(IList<T> list) { + if (list == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list); + } + items = list; + } + + public int Count { + get { return items.Count; } + } + + protected IList<T> Items { + get { return items; } + } + + public T this[int index] { + get { return items[index]; } + set { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if (index < 0 || index >= items.Count) { + ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + } + + SetItem(index, value); + } + } + + public void Add(T item) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + int index = items.Count; + InsertItem(index, item); + } + + public void Clear() { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + ClearItems(); + } + + public void CopyTo(T[] array, int index) { + items.CopyTo(array, index); + } + + public bool Contains(T item) { + return items.Contains(item); + } + + public IEnumerator<T> GetEnumerator() { + return items.GetEnumerator(); + } + + public int IndexOf(T item) { + return items.IndexOf(item); + } + + public void Insert(int index, T item) { + if (items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if (index < 0 || index > items.Count) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert); + } + + InsertItem(index, item); + } + + public bool Remove(T item) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + int index = items.IndexOf(item); + if (index < 0) return false; + RemoveItem(index); + return true; + } + + public void RemoveAt(int index) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if (index < 0 || index >= items.Count) { + ThrowHelper.ThrowArgumentOutOfRange_IndexException(); + } + + RemoveItem(index); + } + + protected virtual void ClearItems() { + items.Clear(); + } + + protected virtual void InsertItem(int index, T item) { + items.Insert(index, item); + } + + protected virtual void RemoveItem(int index) { + items.RemoveAt(index); + } + + protected virtual void SetItem(int index, T item) { + items[index] = item; + } + + bool ICollection<T>.IsReadOnly { + get { + return items.IsReadOnly; + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable)items).GetEnumerator(); + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if( _syncRoot == null) { + ICollection c = items as ICollection; + if( c != null) { + _syncRoot = c.SyncRoot; + } + else { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + } + return _syncRoot; + } + } + + void ICollection.CopyTo(Array array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if( array.GetLowerBound(0) != 0 ) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0 ) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + T[] tArray = array as T[]; + if (tArray != null) { + items.CopyTo(tArray , index); + } + else { + // + // Catch the obvious case assignment will fail. + // We can found all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType(); + Type sourceType = typeof(T); + if(!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object[] objects = array as object[]; + if( objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + int count = items.Count; + try { + for (int i = 0; i < count; i++) { + objects[index++] = items[i]; + } + } + catch(ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + object IList.this[int index] { + get { return items[index]; } + set { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value); + + try { + this[index] = (T)value; + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + } + } + + bool IList.IsReadOnly { + get { + return items.IsReadOnly; + } + } + + bool IList.IsFixedSize { + get { + // There is no IList<T>.IsFixedSize, so we must assume that only + // readonly collections are fixed size, if our internal item + // collection does not implement IList. Note that Array implements + // IList, and therefore T[] and U[] will be fixed-size. + IList list = items as IList; + if(list != null) + { + return list.IsFixedSize; + } + return items.IsReadOnly; + } + } + + int IList.Add(object value) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value); + + try { + Add((T)value); + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + return this.Count - 1; + } + + bool IList.Contains(object value) { + if(IsCompatibleObject(value)) { + return Contains((T) value); + } + return false; + } + + int IList.IndexOf(object value) { + if(IsCompatibleObject(value)) { + return IndexOf((T)value); + } + return -1; + } + + void IList.Insert(int index, object value) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(value, ExceptionArgument.value); + + try { + Insert(index, (T)value); + } + catch (InvalidCastException) { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + } + + void IList.Remove(object value) { + if( items.IsReadOnly) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if(IsCompatibleObject(value)) { + Remove((T) value); + } + } + + private static bool IsCompatibleObject(object value) { + // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>. + // Note that default(T) is not equal to null for value types except when T is Nullable<U>. + return ((value is T) || (value == null && default(T) == null)); + } + } +} diff --git a/src/mscorlib/src/System/Collections/ObjectModel/KeyedCollection.cs b/src/mscorlib/src/System/Collections/ObjectModel/KeyedCollection.cs new file mode 100644 index 0000000000..7313d71950 --- /dev/null +++ b/src/mscorlib/src/System/Collections/ObjectModel/KeyedCollection.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Collections.ObjectModel +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + [Serializable] + [System.Runtime.InteropServices.ComVisible(false)] + [DebuggerTypeProxy(typeof(Mscorlib_KeyedCollectionDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + public abstract class KeyedCollection<TKey,TItem>: Collection<TItem> + { + const int defaultThreshold = 0; + + IEqualityComparer<TKey> comparer; + Dictionary<TKey,TItem> dict; + int keyCount; + int threshold; + + protected KeyedCollection(): this(null, defaultThreshold) {} + + protected KeyedCollection(IEqualityComparer<TKey> comparer): this(comparer, defaultThreshold) {} + + + protected KeyedCollection(IEqualityComparer<TKey> comparer, int dictionaryCreationThreshold) + : base(new List<TItem>()) { // Be explicit about the use of List<T> so we can foreach over + // Items internally without enumerator allocations. + if (comparer == null) { + comparer = EqualityComparer<TKey>.Default; + } + + if (dictionaryCreationThreshold == -1) { + dictionaryCreationThreshold = int.MaxValue; + } + + if( dictionaryCreationThreshold < -1) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.dictionaryCreationThreshold, ExceptionResource.ArgumentOutOfRange_InvalidThreshold); + } + + this.comparer = comparer; + this.threshold = dictionaryCreationThreshold; + } + + /// <summary> + /// Enables the use of foreach internally without allocations using <see cref="List{T}"/>'s struct enumerator. + /// </summary> + new private List<TItem> Items { + get { + Contract.Assert(base.Items is List<TItem>); + + return (List<TItem>)base.Items; + } + } + + public IEqualityComparer<TKey> Comparer { + get { + return comparer; + } + } + + public TItem this[TKey key] { + get { + if( key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (dict != null) { + return dict[key]; + } + + foreach (TItem item in Items) { + if (comparer.Equals(GetKeyForItem(item), key)) return item; + } + + ThrowHelper.ThrowKeyNotFoundException(); + return default(TItem); + } + } + + public bool Contains(TKey key) { + if( key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (dict != null) { + return dict.ContainsKey(key); + } + + foreach (TItem item in Items) { + if (comparer.Equals(GetKeyForItem(item), key)) return true; + } + return false; + } + + private bool ContainsItem(TItem item) { + TKey key; + if( (dict == null) || ((key = GetKeyForItem(item)) == null)) { + return Items.Contains(item); + } + + TItem itemInDict; + bool exist = dict.TryGetValue(key, out itemInDict); + if( exist) { + return EqualityComparer<TItem>.Default.Equals(itemInDict, item); + } + return false; + } + + public bool Remove(TKey key) { + if( key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (dict != null) { + if (dict.ContainsKey(key)) { + return Remove(dict[key]); + } + + return false; + } + + for (int i = 0; i < Items.Count; i++) { + if (comparer.Equals(GetKeyForItem(Items[i]), key)) { + RemoveItem(i); + return true; + } + } + return false; + } + + protected IDictionary<TKey,TItem> Dictionary { + get { return dict; } + } + + protected void ChangeItemKey(TItem item, TKey newKey) { + // check if the item exists in the collection + if( !ContainsItem(item)) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_ItemNotExist); + } + + TKey oldKey = GetKeyForItem(item); + if (!comparer.Equals(oldKey, newKey)) { + if (newKey != null) { + AddKey(newKey, item); + } + + if (oldKey != null) { + RemoveKey(oldKey); + } + } + } + + protected override void ClearItems() { + base.ClearItems(); + if (dict != null) { + dict.Clear(); + } + + keyCount = 0; + } + + protected abstract TKey GetKeyForItem(TItem item); + + protected override void InsertItem(int index, TItem item) { + TKey key = GetKeyForItem(item); + if (key != null) { + AddKey(key, item); + } + base.InsertItem(index, item); + } + + protected override void RemoveItem(int index) { + TKey key = GetKeyForItem(Items[index]); + if (key != null) { + RemoveKey(key); + } + base.RemoveItem(index); + } + + protected override void SetItem(int index, TItem item) { + TKey newKey = GetKeyForItem(item); + TKey oldKey = GetKeyForItem(Items[index]); + + if (comparer.Equals(oldKey, newKey)) { + if (newKey != null && dict != null) { + dict[newKey] = item; + } + } + else { + if (newKey != null) { + AddKey(newKey, item); + } + + if (oldKey != null) { + RemoveKey(oldKey); + } + } + base.SetItem(index, item); + } + + private void AddKey(TKey key, TItem item) { + if (dict != null) { + dict.Add(key, item); + } + else if (keyCount == threshold) { + CreateDictionary(); + dict.Add(key, item); + } + else { + if (Contains(key)) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate); + } + + keyCount++; + } + } + + private void CreateDictionary() { + dict = new Dictionary<TKey,TItem>(comparer); + foreach (TItem item in Items) { + TKey key = GetKeyForItem(item); + if (key != null) { + dict.Add(key, item); + } + } + } + + private void RemoveKey(TKey key) { + Contract.Assert(key != null, "key shouldn't be null!"); + if (dict != null) { + dict.Remove(key); + } + else { + keyCount--; + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs new file mode 100644 index 0000000000..ec7149e4f5 --- /dev/null +++ b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Collections.ObjectModel +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime; + + [Serializable] + [System.Runtime.InteropServices.ComVisible(false)] + [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + public class ReadOnlyCollection<T>: IList<T>, IList, IReadOnlyList<T> + { + IList<T> list; + [NonSerialized] + private Object _syncRoot; + + public ReadOnlyCollection(IList<T> list) { + if (list == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list); + } + this.list = list; + } + + public int Count { + get { return list.Count; } + } + + public T this[int index] { + get { return list[index]; } + } + + public bool Contains(T value) { + return list.Contains(value); + } + + public void CopyTo(T[] array, int index) { + list.CopyTo(array, index); + } + + public IEnumerator<T> GetEnumerator() { + return list.GetEnumerator(); + } + + public int IndexOf(T value) { + return list.IndexOf(value); + } + + protected IList<T> Items { + get { + return list; + } + } + + bool ICollection<T>.IsReadOnly { + get { return true; } + } + + T IList<T>.this[int index] { + get { return list[index]; } + set { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + } + + void ICollection<T>.Add(T value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void ICollection<T>.Clear() { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void IList<T>.Insert(int index, T value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool ICollection<T>.Remove(T value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return false; + } + + void IList<T>.RemoveAt(int index) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable)list).GetEnumerator(); + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if( _syncRoot == null) { + ICollection c = list as ICollection; + if( c != null) { + _syncRoot = c.SyncRoot; + } + else { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + } + return _syncRoot; + } + } + + void ICollection.CopyTo(Array array, int index) { + if (array==null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if( array.GetLowerBound(0) != 0 ) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + T[] items = array as T[]; + if (items != null) { + list.CopyTo(items, index); + } + else { + // + // Catch the obvious case assignment will fail. + // We can found all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType(); + Type sourceType = typeof(T); + if(!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object[] objects = array as object[]; + if( objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + int count = list.Count; + try { + for (int i = 0; i < count; i++) { + objects[index++] = list[i]; + } + } + catch(ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + bool IList.IsFixedSize { + get { return true; } + } + + bool IList.IsReadOnly { + get { return true; } + } + + object IList.this[int index] { + get { return list[index]; } + set { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + } + + int IList.Add(object value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return -1; + } + + void IList.Clear() { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + private static bool IsCompatibleObject(object value) { + // Non-null values are fine. Only accept nulls if T is a class or Nullable<U>. + // Note that default(T) is not equal to null for value types except when T is Nullable<U>. + return ((value is T) || (value == null && default(T) == null)); + } + + bool IList.Contains(object value) { + if(IsCompatibleObject(value)) { + return Contains((T) value); + } + return false; + } + + int IList.IndexOf(object value) { + if(IsCompatibleObject(value)) { + return IndexOf((T)value); + } + return -1; + } + + void IList.Insert(int index, object value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void IList.Remove(object value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void IList.RemoveAt(int index) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + } +} diff --git a/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs new file mode 100644 index 0000000000..11833c2c1b --- /dev/null +++ b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyDictionary.cs @@ -0,0 +1,625 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** Purpose: Read-only wrapper for another generic dictionary. +** +===========================================================*/ + +namespace System.Collections.ObjectModel +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + [Serializable] + [DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue> + { + private readonly IDictionary<TKey, TValue> m_dictionary; + [NonSerialized] + private Object m_syncRoot; + [NonSerialized] + private KeyCollection m_keys; + [NonSerialized] + private ValueCollection m_values; + + public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary) { + if (dictionary == null) { + throw new ArgumentNullException("dictionary"); + } + Contract.EndContractBlock(); + m_dictionary = dictionary; + } + + protected IDictionary<TKey, TValue> Dictionary { + get { return m_dictionary; } + } + + public KeyCollection Keys { + get { + Contract.Ensures(Contract.Result<KeyCollection>() != null); + if (m_keys == null) { + m_keys = new KeyCollection(m_dictionary.Keys); + } + return m_keys; + } + } + + public ValueCollection Values { + get { + Contract.Ensures(Contract.Result<ValueCollection>() != null); + if (m_values == null) { + m_values = new ValueCollection(m_dictionary.Values); + } + return m_values; + } + } + + #region IDictionary<TKey, TValue> Members + + public bool ContainsKey(TKey key) { + return m_dictionary.ContainsKey(key); + } + + ICollection<TKey> IDictionary<TKey, TValue>.Keys { + get { + return Keys; + } + } + + public bool TryGetValue(TKey key, out TValue value) { + return m_dictionary.TryGetValue(key, out value); + } + + ICollection<TValue> IDictionary<TKey, TValue>.Values { + get { + return Values; + } + } + + public TValue this[TKey key] { + get { + return m_dictionary[key]; + } + } + + void IDictionary<TKey, TValue>.Add(TKey key, TValue value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool IDictionary<TKey, TValue>.Remove(TKey key) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return false; + } + + TValue IDictionary<TKey, TValue>.this[TKey key] { + get { + return m_dictionary[key]; + } + set { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + } + + #endregion + + #region ICollection<KeyValuePair<TKey, TValue>> Members + + public int Count { + get { return m_dictionary.Count; } + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { + return m_dictionary.Contains(item); + } + + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { + m_dictionary.CopyTo(array, arrayIndex); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { + get { return true; } + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void ICollection<KeyValuePair<TKey, TValue>>.Clear() { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return false; + } + + #endregion + + #region IEnumerable<KeyValuePair<TKey, TValue>> Members + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { + return m_dictionary.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return ((IEnumerable)m_dictionary).GetEnumerator(); + } + + #endregion + + #region IDictionary Members + + private static bool IsCompatibleKey(object key) { + if (key == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + return key is TKey; + } + + void IDictionary.Add(object key, object value) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void IDictionary.Clear() { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool IDictionary.Contains(object key) { + return IsCompatibleKey(key) && ContainsKey((TKey)key); + } + + IDictionaryEnumerator IDictionary.GetEnumerator() { + IDictionary d = m_dictionary as IDictionary; + if (d != null) { + return d.GetEnumerator(); + } + return new DictionaryEnumerator(m_dictionary); + } + + bool IDictionary.IsFixedSize { + get { return true; } + } + + bool IDictionary.IsReadOnly { + get { return true; } + } + + ICollection IDictionary.Keys { + get { + return Keys; + } + } + + void IDictionary.Remove(object key) { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + ICollection IDictionary.Values { + get { + return Values; + } + } + + object IDictionary.this[object key] { + get { + if (IsCompatibleKey(key)) { + return this[(TKey)key]; + } + return null; + } + set { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + } + + void ICollection.CopyTo(Array array, int index) { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0 || index > array.Length) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + KeyValuePair<TKey, TValue>[] pairs = array as KeyValuePair<TKey, TValue>[]; + if (pairs != null) { + m_dictionary.CopyTo(pairs, index); + } + else { + DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; + if (dictEntryArray != null) { + foreach (var item in m_dictionary) { + dictEntryArray[index++] = new DictionaryEntry(item.Key, item.Value); + } + } + else { + object[] objects = array as object[]; + if (objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try { + foreach (var item in m_dictionary) { + objects[index++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value); + } + } + catch (ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if (m_syncRoot == null) { + ICollection c = m_dictionary as ICollection; + if (c != null) { + m_syncRoot = c.SyncRoot; + } + else { + System.Threading.Interlocked.CompareExchange<Object>(ref m_syncRoot, new Object(), null); + } + } + return m_syncRoot; + } + } + + [Serializable] + private struct DictionaryEnumerator : IDictionaryEnumerator { + private readonly IDictionary<TKey, TValue> m_dictionary; + private IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; + + public DictionaryEnumerator(IDictionary<TKey, TValue> dictionary) { + m_dictionary = dictionary; + m_enumerator = m_dictionary.GetEnumerator(); + } + + public DictionaryEntry Entry { + get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); } + } + + public object Key { + get { return m_enumerator.Current.Key; } + } + + public object Value { + get { return m_enumerator.Current.Value; } + } + + public object Current { + get { return Entry; } + } + + public bool MoveNext() { + return m_enumerator.MoveNext(); + } + + public void Reset() { + m_enumerator.Reset(); + } + } + + #endregion + + #region IReadOnlyDictionary members + + IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys { + get { + return Keys; + } + } + + IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values { + get { + return Values; + } + } + + #endregion IReadOnlyDictionary members + + [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey> { + private readonly ICollection<TKey> m_collection; + [NonSerialized] + private Object m_syncRoot; + + internal KeyCollection(ICollection<TKey> collection) + { + if (collection == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + m_collection = collection; + } + + #region ICollection<T> Members + + void ICollection<TKey>.Add(TKey item) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void ICollection<TKey>.Clear() + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool ICollection<TKey>.Contains(TKey item) + { + return m_collection.Contains(item); + } + + public void CopyTo(TKey[] array, int arrayIndex) + { + m_collection.CopyTo(array, arrayIndex); + } + + public int Count { + get { return m_collection.Count; } + } + + bool ICollection<TKey>.IsReadOnly { + get { return true; } + } + + bool ICollection<TKey>.Remove(TKey item) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return false; + } + + #endregion + + #region IEnumerable<T> Members + + public IEnumerator<TKey> GetEnumerator() + { + return m_collection.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return ((IEnumerable)m_collection).GetEnumerator(); + } + + #endregion + + #region ICollection Members + + void ICollection.CopyTo(Array array, int index) { + ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper<TKey>(m_collection, array, index); + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if (m_syncRoot == null) { + ICollection c = m_collection as ICollection; + if (c != null) { + m_syncRoot = c.SyncRoot; + } + else { + System.Threading.Interlocked.CompareExchange<Object>(ref m_syncRoot, new Object(), null); + } + } + return m_syncRoot; + } + } + + #endregion + } + + [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [Serializable] + public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue> { + private readonly ICollection<TValue> m_collection; + [NonSerialized] + private Object m_syncRoot; + + internal ValueCollection(ICollection<TValue> collection) + { + if (collection == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + m_collection = collection; + } + + #region ICollection<T> Members + + void ICollection<TValue>.Add(TValue item) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + void ICollection<TValue>.Clear() + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + bool ICollection<TValue>.Contains(TValue item) + { + return m_collection.Contains(item); + } + + public void CopyTo(TValue[] array, int arrayIndex) + { + m_collection.CopyTo(array, arrayIndex); + } + + public int Count { + get { return m_collection.Count; } + } + + bool ICollection<TValue>.IsReadOnly { + get { return true; } + } + + bool ICollection<TValue>.Remove(TValue item) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + return false; + } + + #endregion + + #region IEnumerable<T> Members + + public IEnumerator<TValue> GetEnumerator() + { + return m_collection.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return ((IEnumerable)m_collection).GetEnumerator(); + } + + #endregion + + #region ICollection Members + + void ICollection.CopyTo(Array array, int index) { + ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper<TValue>(m_collection, array, index); + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { + if (m_syncRoot == null) { + ICollection c = m_collection as ICollection; + if (c != null) { + m_syncRoot = c.SyncRoot; + } + else { + System.Threading.Interlocked.CompareExchange<Object>(ref m_syncRoot, new Object(), null); + } + } + return m_syncRoot; + } + } + + #endregion ICollection Members + } + } + + // To share code when possible, use a non-generic class to get rid of irrelevant type parameters. + internal static class ReadOnlyDictionaryHelpers + { + #region Helper method for our KeyCollection and ValueCollection + + // Abstracted away to avoid redundant implementations. + internal static void CopyToNonGenericICollectionHelper<T>(ICollection<T> collection, Array array, int index) + { + if (array == null) { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < collection.Count) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + // Easy out if the ICollection<T> implements the non-generic ICollection + ICollection nonGenericCollection = collection as ICollection; + if (nonGenericCollection != null) { + nonGenericCollection.CopyTo(array, index); + return; + } + + T[] items = array as T[]; + if (items != null) { + collection.CopyTo(items, index); + } + else { + // + // Catch the obvious case assignment will fail. + // We can found all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType(); + Type sourceType = typeof(T); + if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object[] objects = array as object[]; + if (objects == null) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try { + foreach (var item in collection) { + objects[index++] = item; + } + } + catch (ArrayTypeMismatchException) { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + #endregion Helper method for our KeyCollection and ValueCollection + } +} + diff --git a/src/mscorlib/src/System/Collections/SortedList.cs b/src/mscorlib/src/System/Collections/SortedList.cs new file mode 100644 index 0000000000..8e3926af01 --- /dev/null +++ b/src/mscorlib/src/System/Collections/SortedList.cs @@ -0,0 +1,1011 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** Purpose: A sorted dictionary. +** +** +===========================================================*/ +namespace System.Collections { + using System; + using System.Security.Permissions; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + + // The SortedList class implements a sorted list of keys and values. Entries in + // a sorted list are sorted by their keys and are accessible both by key and by + // index. The keys of a sorted list can be ordered either according to a + // specific IComparer implementation given when the sorted list is + // instantiated, or according to the IComparable implementation provided + // by the keys themselves. In either case, a sorted list does not allow entries + // with duplicate keys. + // + // A sorted list internally maintains two arrays that store the keys and + // values of the entries. The capacity of a sorted list is the allocated + // length of these internal arrays. As elements are added to a sorted list, the + // capacity of the sorted list is automatically increased as required by + // reallocating the internal arrays. The capacity is never automatically + // decreased, but users can call either TrimToSize or + // Capacity explicitly. + // + // The GetKeyList and GetValueList methods of a sorted list + // provides access to the keys and values of the sorted list in the form of + // List implementations. The List objects returned by these + // methods are aliases for the underlying sorted list, so modifications + // made to those lists are directly reflected in the sorted list, and vice + // versa. + // + // The SortedList class provides a convenient way to create a sorted + // copy of another dictionary, such as a Hashtable. For example: + // + // Hashtable h = new Hashtable(); + // h.Add(...); + // h.Add(...); + // ... + // SortedList s = new SortedList(h); + // + // The last line above creates a sorted list that contains a copy of the keys + // and values stored in the hashtable. In this particular example, the keys + // will be ordered according to the IComparable interface, which they + // all must implement. To impose a different ordering, SortedList also + // has a constructor that allows a specific IComparer implementation to + // be specified. + // + [DebuggerTypeProxy(typeof(System.Collections.SortedList.SortedListDebugView))] + [DebuggerDisplay("Count = {Count}")] +[System.Runtime.InteropServices.ComVisible(true)] +#if FEATURE_CORECLR + [Obsolete("Non-generic collections have been deprecated. Please use collections in System.Collections.Generic.")] +#endif + [Serializable] + public class SortedList : IDictionary, ICloneable + { + private Object[] keys; + private Object[] values; + private int _size; + private int version; + private IComparer comparer; + private KeyList keyList; + private ValueList valueList; + [NonSerialized] + private Object _syncRoot; + + private const int _defaultCapacity = 16; + + private static Object[] emptyArray = EmptyArray<Object>.Value; + + // Constructs a new sorted list. The sorted list is initially empty and has + // a capacity of zero. Upon adding the first element to the sorted list the + // capacity is increased to 16, and then increased in multiples of two as + // required. The elements of the sorted list are ordered according to the + // IComparable interface, which must be implemented by the keys of + // all entries added to the sorted list. + public SortedList() { + Init(); + } + private void Init() + { + keys = emptyArray; + values = emptyArray; + _size = 0; + comparer = new Comparer(CultureInfo.CurrentCulture); + } + + // Constructs a new sorted list. The sorted list is initially empty and has + // a capacity of zero. Upon adding the first element to the sorted list the + // capacity is increased to 16, and then increased in multiples of two as + // required. The elements of the sorted list are ordered according to the + // IComparable interface, which must be implemented by the keys of + // all entries added to the sorted list. + // + public SortedList(int initialCapacity) { + if (initialCapacity < 0) + throw new ArgumentOutOfRangeException("initialCapacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + keys = new Object[initialCapacity]; + values = new Object[initialCapacity]; + comparer = new Comparer(CultureInfo.CurrentCulture); + } + + // Constructs a new sorted list with a given IComparer + // implementation. The sorted list is initially empty and has a capacity of + // zero. Upon adding the first element to the sorted list the capacity is + // increased to 16, and then increased in multiples of two as required. The + // elements of the sorted list are ordered according to the given + // IComparer implementation. If comparer is null, the + // elements are compared to each other using the IComparable + // interface, which in that case must be implemented by the keys of all + // entries added to the sorted list. + // + public SortedList(IComparer comparer) + : this() { + if (comparer != null) this.comparer = comparer; + } + + // Constructs a new sorted list with a given IComparer + // implementation and a given initial capacity. The sorted list is + // initially empty, but will have room for the given number of elements + // before any reallocations are required. The elements of the sorted list + // are ordered according to the given IComparer implementation. If + // comparer is null, the elements are compared to each other using + // the IComparable interface, which in that case must be implemented + // by the keys of all entries added to the sorted list. + // + public SortedList(IComparer comparer, int capacity) + : this(comparer) { + Capacity = capacity; + } + + // Constructs a new sorted list containing a copy of the entries in the + // given dictionary. The elements of the sorted list are ordered according + // to the IComparable interface, which must be implemented by the + // keys of all entries in the the given dictionary as well as keys + // subsequently added to the sorted list. + // + public SortedList(IDictionary d) + : this(d, null) { + } + + // Constructs a new sorted list containing a copy of the entries in the + // given dictionary. The elements of the sorted list are ordered according + // to the given IComparer implementation. If comparer is + // null, the elements are compared to each other using the + // IComparable interface, which in that case must be implemented + // by the keys of all entries in the the given dictionary as well as keys + // subsequently added to the sorted list. + // + public SortedList(IDictionary d, IComparer comparer) + : this(comparer, (d != null ? d.Count : 0)) { + if (d==null) + throw new ArgumentNullException("d", Environment.GetResourceString("ArgumentNull_Dictionary")); + Contract.EndContractBlock(); + d.Keys.CopyTo(keys, 0); + d.Values.CopyTo(values, 0); + Array.Sort(keys, values, comparer); + _size = d.Count; + } + + // Adds an entry with the given key and value to this sorted list. An + // ArgumentException is thrown if the key is already present in the sorted list. + // + public virtual void Add(Object key, Object value) { + if (key == null) throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + Contract.EndContractBlock(); + int i = Array.BinarySearch(keys, 0, _size, key, comparer); + if (i >= 0) + throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", GetKey(i), key)); + Insert(~i, key, value); + } + + // Returns the capacity of this sorted list. The capacity of a sorted list + // represents the allocated length of the internal arrays used to store the + // keys and values of the list, and thus also indicates the maximum number + // of entries the list can contain before a reallocation of the internal + // arrays is required. + // + public virtual int Capacity { + get { + return keys.Length; + } + set { + if (value < Count) { + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity")); + } + Contract.EndContractBlock(); + + if (value != keys.Length) { + if (value > 0) { + Object[] newKeys = new Object[value]; + Object[] newValues = new Object[value]; + if (_size > 0) { + Array.Copy(keys, 0, newKeys, 0, _size); + Array.Copy(values, 0, newValues, 0, _size); + } + keys = newKeys; + values = newValues; + } + else { + // size can only be zero here. + Contract.Assert( _size == 0, "Size is not zero"); + keys = emptyArray; + values = emptyArray; + } + } + } + } + + // Returns the number of entries in this sorted list. + // + public virtual int Count { + get { + return _size; + } + } + + // Returns a collection representing the keys of this sorted list. This + // method returns the same object as GetKeyList, but typed as an + // ICollection instead of an IList. + // + public virtual ICollection Keys { + get { + return GetKeyList(); + } + } + + // Returns a collection representing the values of this sorted list. This + // method returns the same object as GetValueList, but typed as an + // ICollection instead of an IList. + // + public virtual ICollection Values { + get { + return GetValueList(); + } + } + + // Is this SortedList read-only? + public virtual bool IsReadOnly { + get { return false; } + } + + public virtual bool IsFixedSize { + get { return false; } + } + + // Is this SortedList synchronized (thread-safe)? + public virtual bool IsSynchronized { + get { return false; } + } + + // Synchronization root for this object. + public virtual Object SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + // Removes all entries from this sorted list. + public virtual void Clear() { + // clear does not change the capacity + version++; + Array.Clear(keys, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references. + Array.Clear(values, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references. + _size = 0; + + } + + // Makes a virtually identical copy of this SortedList. This is a shallow + // copy. IE, the Objects in the SortedList are not cloned - we copy the + // references to those objects. + public virtual Object Clone() + { + SortedList sl = new SortedList(_size); + Array.Copy(keys, 0, sl.keys, 0, _size); + Array.Copy(values, 0, sl.values, 0, _size); + sl._size = _size; + sl.version = version; + sl.comparer = comparer; + // Don't copy keyList nor valueList. + return sl; + } + + + // Checks if this sorted list contains an entry with the given key. + // + public virtual bool Contains(Object key) { + return IndexOfKey(key) >= 0; + } + + // Checks if this sorted list contains an entry with the given key. + // + public virtual bool ContainsKey(Object key) { + // Yes, this is a SPEC'ed duplicate of Contains(). + return IndexOfKey(key) >= 0; + } + + // Checks if this sorted list contains an entry with the given value. The + // values of the entries of the sorted list are compared to the given value + // using the Object.Equals method. This method performs a linear + // search and is substantially slower than the Contains + // method. + // + public virtual bool ContainsValue(Object value) { + return IndexOfValue(value) >= 0; + } + + // Copies the values in this SortedList to an array. + public virtual void CopyTo(Array array, int arrayIndex) { + if (array == null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Array")); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - arrayIndex < Count) + throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall")); + Contract.EndContractBlock(); + for (int i = 0; i<Count; i++) { + DictionaryEntry entry = new DictionaryEntry(keys[i],values[i]); + array.SetValue(entry, i + arrayIndex); + } + } + + // Copies the values in this SortedList to an KeyValuePairs array. + // KeyValuePairs is different from Dictionary Entry in that it has special + // debugger attributes on its fields. + + internal virtual KeyValuePairs[] ToKeyValuePairsArray() { + KeyValuePairs[] array = new KeyValuePairs[Count]; + for (int i = 0; i < Count; i++) { + array[i] = new KeyValuePairs(keys[i],values[i]); + } + return array; + } + + // Ensures that the capacity of this sorted list is at least the given + // minimum value. If the currect capacity of the list is less than + // min, the capacity is increased to twice the current capacity or + // to min, whichever is larger. + private void EnsureCapacity(int min) { + int newCapacity = keys.Length == 0? 16: keys.Length * 2; + // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + + // Returns the value of the entry at the given index. + // + public virtual Object GetByIndex(int index) { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + return values[index]; + } + + // Returns an IEnumerator for this sorted list. If modifications + // made to the sorted list while an enumeration is in progress, + // the MoveNext and Remove methods + // of the enumerator will throw an exception. + // + IEnumerator IEnumerable.GetEnumerator() { + return new SortedListEnumerator(this, 0, _size, SortedListEnumerator.DictEntry); + } + + // Returns an IDictionaryEnumerator for this sorted list. If modifications + // made to the sorted list while an enumeration is in progress, + // the MoveNext and Remove methods + // of the enumerator will throw an exception. + // + public virtual IDictionaryEnumerator GetEnumerator() { + return new SortedListEnumerator(this, 0, _size, SortedListEnumerator.DictEntry); + } + + // Returns the key of the entry at the given index. + // + public virtual Object GetKey(int index) { + if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + return keys[index]; + } + + // Returns an IList representing the keys of this sorted list. The + // returned list is an alias for the keys of this sorted list, so + // modifications made to the returned list are directly reflected in the + // underlying sorted list, and vice versa. The elements of the returned + // list are ordered in the same way as the elements of the sorted list. The + // returned list does not support adding, inserting, or modifying elements + // (the Add, AddRange, Insert, InsertRange, + // Reverse, Set, SetRange, and Sort methods + // throw exceptions), but it does allow removal of elements (through the + // Remove and RemoveRange methods or through an enumerator). + // Null is an invalid key value. + // + public virtual IList GetKeyList() { + if (keyList == null) keyList = new KeyList(this); + return keyList; + } + + // Returns an IList representing the values of this sorted list. The + // returned list is an alias for the values of this sorted list, so + // modifications made to the returned list are directly reflected in the + // underlying sorted list, and vice versa. The elements of the returned + // list are ordered in the same way as the elements of the sorted list. The + // returned list does not support adding or inserting elements (the + // Add, AddRange, Insert and InsertRange + // methods throw exceptions), but it does allow modification and removal of + // elements (through the Remove, RemoveRange, Set and + // SetRange methods or through an enumerator). + // + public virtual IList GetValueList() { + if (valueList == null) valueList = new ValueList(this); + return valueList; + } + + // Returns the value associated with the given key. If an entry with the + // given key is not found, the returned value is null. + // + public virtual Object this[Object key] { + get { + int i = IndexOfKey(key); + if (i >= 0) return values[i]; + return null; + } + set { + if (key == null) throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + Contract.EndContractBlock(); + int i = Array.BinarySearch(keys, 0, _size, key, comparer); + if (i >= 0) { + values[i] = value; + version++; + return; + } + Insert(~i, key, value); + } + } + + // Returns the index of the entry with a given key in this sorted list. The + // key is located through a binary search, and thus the average execution + // time of this method is proportional to Log2(size), where + // size is the size of this sorted list. The returned value is -1 if + // the given key does not occur in this sorted list. Null is an invalid + // key value. + // + public virtual int IndexOfKey(Object key) { + if (key == null) + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + Contract.EndContractBlock(); + int ret = Array.BinarySearch(keys, 0, _size, key, comparer); + return ret >=0 ? ret : -1; + } + + // Returns the index of the first occurrence of an entry with a given value + // in this sorted list. The entry is located through a linear search, and + // thus the average execution time of this method is proportional to the + // size of this sorted list. The elements of the list are compared to the + // given value using the Object.Equals method. + // + public virtual int IndexOfValue(Object value) { + return Array.IndexOf(values, value, 0, _size); + } + + // Inserts an entry with a given key and value at a given index. + private void Insert(int index, Object key, Object value) { + if (_size == keys.Length) EnsureCapacity(_size + 1); + if (index < _size) { + Array.Copy(keys, index, keys, index + 1, _size - index); + Array.Copy(values, index, values, index + 1, _size - index); + } + keys[index] = key; + values[index] = value; + _size++; + version++; + } + + // Removes the entry at the given index. The size of the sorted list is + // decreased by one. + // + public virtual void RemoveAt(int index) { + if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + _size--; + if (index < _size) { + Array.Copy(keys, index + 1, keys, index, _size - index); + Array.Copy(values, index + 1, values, index, _size - index); + } + keys[_size] = null; + values[_size] = null; + version++; + } + + // Removes an entry from this sorted list. If an entry with the specified + // key exists in the sorted list, it is removed. An ArgumentException is + // thrown if the key is null. + // + public virtual void Remove(Object key) { + int i = IndexOfKey(key); + if (i >= 0) + RemoveAt(i); + } + + // Sets the value at an index to a given value. The previous value of + // the given entry is overwritten. + // + public virtual void SetByIndex(int index, Object value) { + if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); + Contract.EndContractBlock(); + values[index] = value; + version++; + } + + // Returns a thread-safe SortedList. + // + [HostProtection(Synchronization=true)] + public static SortedList Synchronized(SortedList list) { + if (list==null) + throw new ArgumentNullException("list"); + Contract.EndContractBlock(); + return new SyncSortedList(list); + } + + // Sets the capacity of this sorted list to the size of the sorted list. + // This method can be used to minimize a sorted list's memory overhead once + // it is known that no new elements will be added to the sorted list. To + // completely clear a sorted list and release all memory referenced by the + // sorted list, execute the following statements: + // + // sortedList.Clear(); + // sortedList.TrimToSize(); + // + public virtual void TrimToSize() { + Capacity = _size; + } + + [Serializable] + private class SyncSortedList : SortedList + { + private SortedList _list; + private Object _root; + + + internal SyncSortedList(SortedList list) { + _list = list; + _root = list.SyncRoot; + } + + public override int Count { + get { lock(_root) { return _list.Count; } } + } + + public override Object SyncRoot { + get { return _root; } + } + + public override bool IsReadOnly { + get { return _list.IsReadOnly; } + } + + public override bool IsFixedSize { + get { return _list.IsFixedSize; } + } + + + public override bool IsSynchronized { + get { return true; } + } + + public override Object this[Object key] { + get { + lock(_root) { + return _list[key]; + } + } + set { + lock(_root) { + _list[key] = value; + } + } + } + + public override void Add(Object key, Object value) { + lock(_root) { + _list.Add(key, value); + } + } + + public override int Capacity { + get{ lock(_root) { return _list.Capacity; } } + } + + public override void Clear() { + lock(_root) { + _list.Clear(); + } + } + + public override Object Clone() { + lock(_root) { + return _list.Clone(); + } + } + + public override bool Contains(Object key) { + lock(_root) { + return _list.Contains(key); + } + } + + public override bool ContainsKey(Object key) { + lock(_root) { + return _list.ContainsKey(key); + } + } + + public override bool ContainsValue(Object key) { + lock(_root) { + return _list.ContainsValue(key); + } + } + + public override void CopyTo(Array array, int index) { + lock(_root) { + _list.CopyTo(array, index); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override Object GetByIndex(int index) { + lock(_root) { + return _list.GetByIndex(index); + } + } + + public override IDictionaryEnumerator GetEnumerator() { + lock(_root) { + return _list.GetEnumerator(); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override Object GetKey(int index) { + lock(_root) { + return _list.GetKey(index); + } + } + + public override IList GetKeyList() { + lock(_root) { + return _list.GetKeyList(); + } + } + + public override IList GetValueList() { + lock(_root) { + return _list.GetValueList(); + } + } + + public override int IndexOfKey(Object key) { + if (key == null) + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + Contract.EndContractBlock(); + + lock(_root) { + return _list.IndexOfKey(key); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int IndexOfValue(Object value) { + lock(_root) { + return _list.IndexOfValue(value); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void RemoveAt(int index) { + lock(_root) { + _list.RemoveAt(index); + } + } + + public override void Remove(Object key) { + lock(_root) { + _list.Remove(key); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override void SetByIndex(int index, Object value) { + lock(_root) { + _list.SetByIndex(index, value); + } + } + + internal override KeyValuePairs[] ToKeyValuePairsArray() { + return _list.ToKeyValuePairsArray(); + } + + public override void TrimToSize() { + lock(_root) { + _list.TrimToSize(); + } + } + } + + + [Serializable] + private class SortedListEnumerator : IDictionaryEnumerator, ICloneable + { + private SortedList sortedList; + private Object key; + private Object value; + private int index; + private int startIndex; // Store for Reset. + private int endIndex; + private int version; + private bool current; // Is the current element valid? + private int getObjectRetType; // What should GetObject return? + + internal const int Keys = 1; + internal const int Values = 2; + internal const int DictEntry = 3; + + internal SortedListEnumerator(SortedList sortedList, int index, int count, + int getObjRetType) { + this.sortedList = sortedList; + this.index = index; + startIndex = index; + endIndex = index + count; + version = sortedList.version; + getObjectRetType = getObjRetType; + current = false; + } + + public Object Clone() + { + return MemberwiseClone(); + } + + public virtual Object Key { + get { + if (version != sortedList.version) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + if (current == false) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + return key; + } + } + + public virtual bool MoveNext() { + if (version != sortedList.version) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + if (index < endIndex) { + key = sortedList.keys[index]; + value = sortedList.values[index]; + index++; + current = true; + return true; + } + key = null; + value = null; + current = false; + return false; + } + + public virtual DictionaryEntry Entry { + get { + if (version != sortedList.version) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); + if (current == false) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + return new DictionaryEntry(key, value); + } + } + + public virtual Object Current { + get { + if (current == false) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + + if (getObjectRetType==Keys) + return key; + else if (getObjectRetType==Values) + return value; + else + return new DictionaryEntry(key, value); + } + } + + public virtual Object Value { + get { + if (version != sortedList.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + if (current == false) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumOpCantHappen")); + return value; + } + } + + public virtual void Reset() { + if (version != sortedList.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + index = startIndex; + current = false; + key = null; + value = null; + } + } + + [Serializable] + private class KeyList : IList + { + private SortedList sortedList; + + internal KeyList(SortedList sortedList) { + this.sortedList = sortedList; + } + + public virtual int Count { + get { return sortedList._size; } + } + + public virtual bool IsReadOnly { + get { return true; } + } + + public virtual bool IsFixedSize { + get { return true; } + } + + public virtual bool IsSynchronized { + get { return sortedList.IsSynchronized; } + } + + public virtual Object SyncRoot { + get { return sortedList.SyncRoot; } + } + + public virtual int Add(Object key) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + // return 0; // suppress compiler warning + } + + public virtual void Clear() { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual bool Contains(Object key) { + return sortedList.Contains(key); + } + + public virtual void CopyTo(Array array, int arrayIndex) { + if (array != null && array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + Contract.EndContractBlock(); + + // defer error checking to Array.Copy + Array.Copy(sortedList.keys, 0, array, arrayIndex, sortedList.Count); + } + + public virtual void Insert(int index, Object value) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual Object this[int index] { + get { + return sortedList.GetKey(index); + } + set { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_KeyCollectionSet")); + } + } + + public virtual IEnumerator GetEnumerator() { + return new SortedListEnumerator(sortedList, 0, sortedList.Count, SortedListEnumerator.Keys); + } + + public virtual int IndexOf(Object key) { + if (key==null) + throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); + Contract.EndContractBlock(); + + int i = Array.BinarySearch(sortedList.keys, 0, + sortedList.Count, key, sortedList.comparer); + if (i >= 0) return i; + return -1; + } + + public virtual void Remove(Object key) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + } + + [Serializable] + private class ValueList : IList + { + private SortedList sortedList; + + internal ValueList(SortedList sortedList) { + this.sortedList = sortedList; + } + + public virtual int Count { + get { return sortedList._size; } + } + + public virtual bool IsReadOnly { + get { return true; } + } + + public virtual bool IsFixedSize { + get { return true; } + } + + public virtual bool IsSynchronized { + get { return sortedList.IsSynchronized; } + } + + public virtual Object SyncRoot { + get { return sortedList.SyncRoot; } + } + + public virtual int Add(Object key) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual void Clear() { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual bool Contains(Object value) { + return sortedList.ContainsValue(value); + } + + public virtual void CopyTo(Array array, int arrayIndex) { + if (array != null && array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + Contract.EndContractBlock(); + + // defer error checking to Array.Copy + Array.Copy(sortedList.values, 0, array, arrayIndex, sortedList.Count); + } + + public virtual void Insert(int index, Object value) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual Object this[int index] { + get { + return sortedList.GetByIndex(index); + } + set { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + } + + public virtual IEnumerator GetEnumerator() { + return new SortedListEnumerator(sortedList, 0, sortedList.Count, SortedListEnumerator.Values); + } + + public virtual int IndexOf(Object value) { + return Array.IndexOf(sortedList.values, value, 0, sortedList.Count); + } + + public virtual void Remove(Object value) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + public virtual void RemoveAt(int index) { + throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_SortedListNestedWrite)); + } + + } + + // internal debug view class for sorted list + internal class SortedListDebugView { + private SortedList sortedList; + + public SortedListDebugView( SortedList sortedList) { + if( sortedList == null) { + throw new ArgumentNullException("sortedList"); + } + Contract.EndContractBlock(); + + this.sortedList = sortedList; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePairs[] Items { + get { + return sortedList.ToKeyValuePairsArray(); + } + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/Stack.cs b/src/mscorlib/src/System/Collections/Stack.cs new file mode 100644 index 0000000000..0384a4ee81 --- /dev/null +++ b/src/mscorlib/src/System/Collections/Stack.cs @@ -0,0 +1,380 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** +** +** +** Purpose: An array implementation of a stack. +** +** +=============================================================================*/ +namespace System.Collections { + using System; + using System.Security.Permissions; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + + // A simple stack of objects. Internally it is implemented as an array, + // so Push can be O(n). Pop is O(1). + [DebuggerTypeProxy(typeof(System.Collections.Stack.StackDebugView))] + [DebuggerDisplay("Count = {Count}")] +[System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public class Stack : ICollection, ICloneable { + private Object[] _array; // Storage for stack elements + [ContractPublicPropertyName("Count")] + private int _size; // Number of items in the stack. + private int _version; // Used to keep enumerator in sync w/ collection. + [NonSerialized] + private Object _syncRoot; + + private const int _defaultCapacity = 10; + + public Stack() { + _array = new Object[_defaultCapacity]; + _size = 0; + _version = 0; + } + + // Create a stack with a specific initial capacity. The initial capacity + // must be a non-negative number. + public Stack(int initialCapacity) { + if (initialCapacity < 0) + throw new ArgumentOutOfRangeException("initialCapacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (initialCapacity < _defaultCapacity) + initialCapacity = _defaultCapacity; // Simplify doubling logic in Push. + _array = new Object[initialCapacity]; + _size = 0; + _version = 0; + } + + // Fills a Stack with the contents of a particular collection. The items are + // pushed onto the stack in the same order they are read by the enumerator. + // + public Stack(ICollection col) : this((col==null ? 32 : col.Count)) + { + if (col==null) + throw new ArgumentNullException("col"); + Contract.EndContractBlock(); + IEnumerator en = col.GetEnumerator(); + while(en.MoveNext()) + Push(en.Current); + } + + public virtual int Count { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + return _size; + } + } + + public virtual bool IsSynchronized { + get { return false; } + } + + public virtual Object SyncRoot { + get { + if( _syncRoot == null) { + System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + + // Removes all Objects from the Stack. + public virtual void Clear() { + Array.Clear(_array, 0, _size); // Don't need to doc this but we clear the elements so that the gc can reclaim the references. + _size = 0; + _version++; + } + + public virtual Object Clone() { + Contract.Ensures(Contract.Result<Object>() != null); + + Stack s = new Stack(_size); + s._size = _size; + Array.Copy(_array, 0, s._array, 0, _size); + s._version = _version; + return s; + } + + public virtual bool Contains(Object obj) { + int count = _size; + + while (count-- > 0) { + if (obj == null) { + if (_array[count] == null) + return true; + } + else if (_array[count] != null && _array[count].Equals(obj)) { + return true; + } + } + return false; + } + + // Copies the stack into an array. + public virtual void CopyTo(Array array, int index) { + if (array==null) + throw new ArgumentNullException("array"); + if (array.Rank != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - index < _size) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + int i = 0; + if (array is Object[]) { + Object[] objArray = (Object[]) array; + while(i < _size) { + objArray[i+index] = _array[_size-i-1]; + i++; + } + } + else { + while(i < _size) { + array.SetValue(_array[_size-i-1], i+index); + i++; + } + } + } + + // Returns an IEnumerator for this Stack. + public virtual IEnumerator GetEnumerator() { + Contract.Ensures(Contract.Result<IEnumerator>() != null); + return new StackEnumerator(this); + } + + // Returns the top object on the stack without removing it. If the stack + // is empty, Peek throws an InvalidOperationException. + public virtual Object Peek() { + if (_size==0) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EmptyStack")); + Contract.EndContractBlock(); + return _array[_size-1]; + } + + // Pops an item from the top of the stack. If the stack is empty, Pop + // throws an InvalidOperationException. + public virtual Object Pop() { + if (_size == 0) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EmptyStack")); + //Contract.Ensures(Count == Contract.OldValue(Count) - 1); + Contract.EndContractBlock(); + _version++; + Object obj = _array[--_size]; + _array[_size] = null; // Free memory quicker. + return obj; + } + + // Pushes an item to the top of the stack. + // + public virtual void Push(Object obj) { + //Contract.Ensures(Count == Contract.OldValue(Count) + 1); + if (_size == _array.Length) { + Object[] newArray = new Object[2*_array.Length]; + Array.Copy(_array, 0, newArray, 0, _size); + _array = newArray; + } + _array[_size++] = obj; + _version++; + } + + // Returns a synchronized Stack. + // + [HostProtection(Synchronization=true)] + public static Stack Synchronized(Stack stack) { + if (stack==null) + throw new ArgumentNullException("stack"); + Contract.Ensures(Contract.Result<Stack>() != null); + Contract.EndContractBlock(); + return new SyncStack(stack); + } + + + // Copies the Stack to an array, in the same order Pop would return the items. + public virtual Object[] ToArray() + { + Contract.Ensures(Contract.Result<Object[]>() != null); + + Object[] objArray = new Object[_size]; + int i = 0; + while(i < _size) { + objArray[i] = _array[_size-i-1]; + i++; + } + return objArray; + } + + [Serializable] + private class SyncStack : Stack + { + private Stack _s; + private Object _root; + + internal SyncStack(Stack stack) { + _s = stack; + _root = stack.SyncRoot; + } + + public override bool IsSynchronized { + get { return true; } + } + + public override Object SyncRoot { + get { + return _root; + } + } + + public override int Count { + get { + lock (_root) { + return _s.Count; + } + } + } + + public override bool Contains(Object obj) { + lock (_root) { + return _s.Contains(obj); + } + } + + public override Object Clone() + { + lock (_root) { + return new SyncStack((Stack)_s.Clone()); + } + } + + public override void Clear() { + lock (_root) { + _s.Clear(); + } + } + + public override void CopyTo(Array array, int arrayIndex) { + lock (_root) { + _s.CopyTo(array, arrayIndex); + } + } + + public override void Push(Object value) { + lock (_root) { + _s.Push(value); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Thread safety problems with precondition - can't express the precondition as of Dev10. + public override Object Pop() { + lock (_root) { + return _s.Pop(); + } + } + + public override IEnumerator GetEnumerator() { + lock (_root) { + return _s.GetEnumerator(); + } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Thread safety problems with precondition - can't express the precondition as of Dev10. + public override Object Peek() { + lock (_root) { + return _s.Peek(); + } + } + + public override Object[] ToArray() { + lock (_root) { + return _s.ToArray(); + } + } + } + + + [Serializable] + private class StackEnumerator : IEnumerator, ICloneable + { + private Stack _stack; + private int _index; + private int _version; + private Object currentElement; + + internal StackEnumerator(Stack stack) { + _stack = stack; + _version = _stack._version; + _index = -2; + currentElement = null; + } + + public Object Clone() + { + return MemberwiseClone(); + } + + public virtual bool MoveNext() { + bool retval; + if (_version != _stack._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + if (_index == -2) { // First call to enumerator. + _index = _stack._size-1; + retval = ( _index >= 0); + if (retval) + currentElement = _stack._array[_index]; + return retval; + } + if (_index == -1) { // End of enumeration. + return false; + } + + retval = (--_index >= 0); + if (retval) + currentElement = _stack._array[_index]; + else + currentElement = null; + return retval; + } + + public virtual Object Current { + get { + if (_index == -2) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (_index == -1) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + return currentElement; + } + } + + public virtual void Reset() { + if (_version != _stack._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion)); + _index = -2; + currentElement = null; + } + } + + internal class StackDebugView { + private Stack stack; + + public StackDebugView( Stack stack) { + if( stack == null) + throw new ArgumentNullException("stack"); + Contract.EndContractBlock(); + + this.stack = stack; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public Object[] Items { + get { + return stack.ToArray(); + } + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/StructuralComparisons.cs b/src/mscorlib/src/System/Collections/StructuralComparisons.cs new file mode 100644 index 0000000000..685af59c4b --- /dev/null +++ b/src/mscorlib/src/System/Collections/StructuralComparisons.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +using System; + +namespace System.Collections { + public static class StructuralComparisons { + + private static volatile IComparer s_StructuralComparer; + private static volatile IEqualityComparer s_StructuralEqualityComparer; + + public static IComparer StructuralComparer { + get { + IComparer comparer = s_StructuralComparer; + if (comparer == null) { + comparer = new StructuralComparer(); + s_StructuralComparer = comparer; + } + return comparer; + } + } + + public static IEqualityComparer StructuralEqualityComparer { + get { + IEqualityComparer comparer = s_StructuralEqualityComparer; + if (comparer == null) { + comparer = new StructuralEqualityComparer(); + s_StructuralEqualityComparer = comparer; + } + return comparer; + } + } + } + + [Serializable] + internal class StructuralEqualityComparer : IEqualityComparer { + public new bool Equals(Object x, Object y) { + if (x != null) { + + IStructuralEquatable seObj = x as IStructuralEquatable; + + if (seObj != null){ + return seObj.Equals(y, this); + } + + if (y != null) { + return x.Equals(y); + } else { + return false; + } + } + if (y != null) return false; + return true; + } + + public int GetHashCode(Object obj) { + if (obj == null) return 0; + + IStructuralEquatable seObj = obj as IStructuralEquatable; + + if (seObj != null) { + return seObj.GetHashCode(this); + } + + return obj.GetHashCode(); + } + } + + [Serializable] + internal class StructuralComparer : IComparer { + public int Compare(Object x, Object y) { + + if (x == null) return y == null ? 0 : -1; + if (y == null) return 1; + + IStructuralComparable scX = x as IStructuralComparable; + + if (scX != null) { + return scX.CompareTo(y, this); + } + + return Comparer.Default.Compare(x, y); + } + } + +} |