| File: System\Collections\Specialized\NameObjectCollectionBase.cs | Web Access |
| Project: src\runtime\src\libraries\System.Collections.Specialized\src\System.Collections.Specialized.csproj (System.Collections.Specialized) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. /* * Ordered String/Object collection of name/value pairs with support for null key * * This class is intended to be used as a base class * */ #pragma warning disable 618 // obsolete types, namely IHashCodeProvider using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.Serialization; namespace System.Collections.Specialized { /// <devdoc> /// <para>Provides the <see langword='abstract '/>base class for a sorted collection of associated <see cref='string' qualify='true'/> keys /// and <see cref='object' qualify='true'/> values that can be accessed either with the hash code of /// the key or with the index.</para> /// </devdoc> public abstract class NameObjectCollectionBase : ICollection, ISerializable, IDeserializationCallback { private bool _readOnly; private ArrayList _entriesArray; private IEqualityComparer _keyComparer; private volatile Hashtable _entriesTable; private volatile NameObjectEntry? _nullKeyEntry; private KeysCollection? _keys; private int _version; private static readonly StringComparer s_defaultComparer = CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreCase); /// <devdoc> /// <para> Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the default initial capacity and using the default case-insensitive hash /// code provider and the default case-insensitive comparer.</para> /// </devdoc> protected NameObjectCollectionBase() : this(s_defaultComparer) { } protected NameObjectCollectionBase(IEqualityComparer? equalityComparer) { _keyComparer = equalityComparer ?? s_defaultComparer; Reset(); } protected NameObjectCollectionBase(int capacity, IEqualityComparer? equalityComparer) : this(equalityComparer) { Reset(capacity); } [Obsolete("This constructor has been deprecated. Use NameObjectCollectionBase(IEqualityComparer) instead.")] protected NameObjectCollectionBase(IHashCodeProvider? hashProvider, IComparer? comparer) { _keyComparer = new CompatibleComparer(hashProvider, comparer); Reset(); } [Obsolete("This constructor has been deprecated. Use NameObjectCollectionBase(Int32, IEqualityComparer) instead.")] protected NameObjectCollectionBase(int capacity, IHashCodeProvider? hashProvider, IComparer? comparer) { _keyComparer = new CompatibleComparer(hashProvider, comparer); Reset(capacity); } /// <devdoc> /// <para>Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the specified /// initial capacity and using the default case-insensitive hash code provider /// and the default case-insensitive comparer.</para> /// </devdoc> protected NameObjectCollectionBase(int capacity) { _keyComparer = s_defaultComparer; Reset(capacity); } [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] protected NameObjectCollectionBase(SerializationInfo info, StreamingContext context) { throw new PlatformNotSupportedException(); } [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { throw new PlatformNotSupportedException(); } public virtual void OnDeserialization(object? sender) { throw new PlatformNotSupportedException(); } // // Private helpers // [MemberNotNull(nameof(_entriesArray))] [MemberNotNull(nameof(_entriesTable))] private void Reset() { _entriesArray = new ArrayList(); _entriesTable = new Hashtable(_keyComparer); _nullKeyEntry = null; _version++; } [MemberNotNull(nameof(_entriesArray))] [MemberNotNull(nameof(_entriesTable))] private void Reset(int capacity) { _entriesArray = new ArrayList(capacity); _entriesTable = new Hashtable(capacity, _keyComparer); _nullKeyEntry = null; _version++; } private NameObjectEntry? FindEntry(string? key) { if (key != null) return (NameObjectEntry?)_entriesTable[key]; else return _nullKeyEntry; } internal IEqualityComparer Comparer { get { return _keyComparer; } set { _keyComparer = value; } } /// <devdoc> /// <para>Gets or sets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance is read-only.</para> /// </devdoc> protected bool IsReadOnly { get { return _readOnly; } set { _readOnly = value; } } /// <devdoc> /// <para>Gets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance contains entries whose /// keys are not <see langword='null'/>.</para> /// </devdoc> protected bool BaseHasKeys() { return (_entriesTable.Count > 0); // any entries with keys? } // // Methods to add / remove entries // /// <devdoc> /// <para>Adds an entry with the specified key and value into the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected void BaseAdd(string? name, object? value) { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); NameObjectEntry entry = new NameObjectEntry(name, value); // insert entry into hashtable if (name != null) { if (_entriesTable[name] == null) _entriesTable.Add(name, entry); } else { // null key -- special case -- hashtable doesn't like null keys _nullKeyEntry ??= entry; } // add entry to the list _entriesArray.Add(entry); _version++; } /// <devdoc> /// <para>Removes the entries with the specified key from the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected void BaseRemove(string? name) { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); if (name != null) { // remove from hashtable _entriesTable.Remove(name); // remove from array for (int i = _entriesArray.Count - 1; i >= 0; i--) { if (_keyComparer.Equals(name, BaseGetKey(i))) _entriesArray.RemoveAt(i); } } else { // null key -- special case // null out special 'null key' entry _nullKeyEntry = null; // remove from array for (int i = _entriesArray.Count - 1; i >= 0; i--) { if (BaseGetKey(i) == null) _entriesArray.RemoveAt(i); } } _version++; } /// <devdoc> /// <para> Removes the entry at the specified index of the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected void BaseRemoveAt(int index) { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); string? key = BaseGetKey(index); if (key != null) { // remove from hashtable _entriesTable.Remove(key); } else { // null key -- special case // null out special 'null key' entry _nullKeyEntry = null; } // remove from array _entriesArray.RemoveAt(index); _version++; } /// <devdoc> /// <para>Removes all entries from the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected void BaseClear() { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); Reset(); } // // Access by name // /// <devdoc> /// <para>Gets the value of the first entry with the specified key from /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected object? BaseGet(string? name) { NameObjectEntry? e = FindEntry(name); return e?.Value; } /// <devdoc> /// <para>Sets the value of the first entry with the specified key in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> /// instance, if found; otherwise, adds an entry with the specified key and value /// into the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> /// instance.</para> /// </devdoc> protected void BaseSet(string? name, object? value) { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); NameObjectEntry? entry = FindEntry(name); if (entry != null) { entry.Value = value; _version++; } else { BaseAdd(name, value); } } // // Access by index // /// <devdoc> /// <para>Gets the value of the entry at the specified index of /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected object? BaseGet(int index) { NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]!; // no null entry added to the array return entry.Value; } /// <devdoc> /// <para>Gets the key of the entry at the specified index of the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> /// instance.</para> /// </devdoc> protected string? BaseGetKey(int index) { NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]!; return entry.Key; } /// <devdoc> /// <para>Sets the value of the entry at the specified index of /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected void BaseSet(int index, object? value) { if (_readOnly) throw new NotSupportedException(SR.CollectionReadOnly); NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]!; entry.Value = value; _version++; } // // ICollection implementation // /// <devdoc> /// <para>Returns an enumerator that can iterate through the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>.</para> /// </devdoc> public virtual IEnumerator GetEnumerator() { return new NameObjectKeysEnumerator(this); } /// <devdoc> /// <para>Gets the number of key-and-value pairs in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> public virtual int Count { get { return _entriesArray.Count; } } void ICollection.CopyTo(Array array, int index) { ArgumentNullException.ThrowIfNull(array); if (array.Rank != 1) { throw new ArgumentException(SR.Arg_MultiRank, nameof(array)); } ArgumentOutOfRangeException.ThrowIfNegative(index); if (array.Length - index < _entriesArray.Count) { throw new ArgumentException(SR.Arg_InsufficientSpace); } for (IEnumerator e = this.GetEnumerator(); e.MoveNext();) array.SetValue(e.Current, index++); } object ICollection.SyncRoot => this; bool ICollection.IsSynchronized { get { return false; } } // // Helper methods to get arrays of keys and values // /// <devdoc> /// <para>Returns a <see cref='string' qualify='true'/> array containing all the keys in the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected string?[] BaseGetAllKeys() { int n = _entriesArray.Count; string?[] allKeys = new string[n]; for (int i = 0; i < n; i++) allKeys[i] = BaseGetKey(i); return allKeys; } /// <devdoc> /// <para>Returns an <see cref='object' qualify='true'/> array containing all the values in the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> protected object?[] BaseGetAllValues() { int n = _entriesArray.Count; object?[] allValues = new object[n]; for (int i = 0; i < n; i++) allValues[i] = BaseGet(i); return allValues; } /// <devdoc> /// <para>Returns an array of the specified type containing /// all the values in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", Justification = "The API only works for reference type arguments and code for reference typed arrays is shareable.")] protected object?[] BaseGetAllValues(Type type) { ArgumentNullException.ThrowIfNull(type); int n = _entriesArray.Count; object?[] allValues = (object?[])Array.CreateInstance(type, n); for (int i = 0; i < n; i++) { allValues[i] = BaseGet(i); } return allValues; } // // Keys property // /// <devdoc> /// <para>Returns a <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/> instance containing /// all the keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> /// </devdoc> public virtual KeysCollection Keys => _keys ??= new KeysCollection(this); // // Simple entry class to allow substitution of values and indexed access to keys // internal sealed class NameObjectEntry { internal NameObjectEntry(string? name, object? value) { Key = name; Value = value; } internal string? Key; internal object? Value; } // // Enumerator over keys of NameObjectCollection // internal sealed class NameObjectKeysEnumerator : IEnumerator { private int _pos; private readonly NameObjectCollectionBase _coll; private readonly int _version; internal NameObjectKeysEnumerator(NameObjectCollectionBase coll) { _coll = coll; _version = _coll._version; _pos = -1; } public bool MoveNext() { if (_version != _coll._version) throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); if (_pos < _coll.Count - 1) { _pos++; return true; } else { _pos = _coll.Count; return false; } } public void Reset() { if (_version != _coll._version) throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); _pos = -1; } public object? Current { get { if (_pos >= 0 && _pos < _coll.Count) { return _coll.BaseGetKey(_pos); } else { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } } } } // // Keys collection // /// <devdoc> /// <para>Represents a collection of the <see cref='string' qualify='true'/> keys of a collection.</para> /// </devdoc> public class KeysCollection : ICollection { private readonly NameObjectCollectionBase _coll; internal KeysCollection(NameObjectCollectionBase coll) { _coll = coll; } // Indexed access /// <devdoc> /// <para> Gets the key at the specified index of the collection.</para> /// </devdoc> public virtual string? Get(int index) { return _coll.BaseGetKey(index); } /// <devdoc> /// <para>Represents the entry at the specified index of the collection.</para> /// </devdoc> public string? this[int index] { get { return Get(index); } } // ICollection implementation /// <devdoc> /// <para>Returns an enumerator that can iterate through the /// <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para> /// </devdoc> public IEnumerator GetEnumerator() { return new NameObjectKeysEnumerator(_coll); } /// <devdoc> /// <para>Gets the number of keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para> /// </devdoc> public int Count { get { return _coll.Count; } } void ICollection.CopyTo(Array array, int index) { ArgumentNullException.ThrowIfNull(array); if (array.Rank != 1) { throw new ArgumentException(SR.Arg_MultiRank, nameof(array)); } ArgumentOutOfRangeException.ThrowIfNegative(index); if (array.Length - index < _coll.Count) { throw new ArgumentException(SR.Arg_InsufficientSpace); } for (IEnumerator e = this.GetEnumerator(); e.MoveNext();) array.SetValue(e.Current, index++); } object ICollection.SyncRoot { get { return ((ICollection)_coll).SyncRoot; } } bool ICollection.IsSynchronized { get { return false; } } } } }