1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 /* 6 * Ordered String/Object collection of name/value pairs with support for null key 7 * 8 * This class is intended to be used as a base class 9 * 10 */ 11 12 #pragma warning disable 618 // obsolete types, namely IHashCodeProvider 13 14 using System.Globalization; 15 using System.Runtime.Serialization; 16 17 namespace System.Collections.Specialized 18 { 19 /// <devdoc> 20 /// <para>Provides the <see langword='abstract '/>base class for a sorted collection of associated <see cref='System.String' qualify='true'/> keys 21 /// and <see cref='System.Object' qualify='true'/> values that can be accessed either with the hash code of 22 /// the key or with the index.</para> 23 /// </devdoc> 24 [Serializable] 25 public abstract class NameObjectCollectionBase : ICollection, ISerializable, IDeserializationCallback 26 { 27 private bool _readOnly = false; 28 private ArrayList _entriesArray; 29 private IEqualityComparer _keyComparer; 30 private volatile Hashtable _entriesTable; 31 private volatile NameObjectEntry _nullKeyEntry; 32 private KeysCollection _keys; 33 private int _version; 34 private Object _syncRoot; 35 36 private static readonly StringComparer s_defaultComparer = CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreCase); 37 38 /// <devdoc> 39 /// <para> Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the default initial capacity and using the default case-insensitive hash 40 /// code provider and the default case-insensitive comparer.</para> 41 /// </devdoc> NameObjectCollectionBase()42 protected NameObjectCollectionBase() : this(s_defaultComparer) 43 { 44 } 45 NameObjectCollectionBase(IEqualityComparer equalityComparer)46 protected NameObjectCollectionBase(IEqualityComparer equalityComparer) 47 { 48 _keyComparer = (equalityComparer == null) ? s_defaultComparer : equalityComparer; 49 Reset(); 50 } 51 NameObjectCollectionBase(Int32 capacity, IEqualityComparer equalityComparer)52 protected NameObjectCollectionBase(Int32 capacity, IEqualityComparer equalityComparer) : this(equalityComparer) 53 { 54 Reset(capacity); 55 } 56 57 [Obsolete("Please use NameObjectCollectionBase(IEqualityComparer) instead.")] NameObjectCollectionBase(IHashCodeProvider hashProvider, IComparer comparer)58 protected NameObjectCollectionBase(IHashCodeProvider hashProvider, IComparer comparer) { 59 _keyComparer = new CompatibleComparer(hashProvider, comparer); 60 Reset(); 61 } 62 63 [Obsolete("Please use NameObjectCollectionBase(Int32, IEqualityComparer) instead.")] NameObjectCollectionBase(int capacity, IHashCodeProvider hashProvider, IComparer comparer)64 protected NameObjectCollectionBase(int capacity, IHashCodeProvider hashProvider, IComparer comparer) { 65 _keyComparer = new CompatibleComparer(hashProvider, comparer); 66 Reset(capacity); 67 } 68 69 /// <devdoc> 70 /// <para>Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the specified 71 /// initial capacity and using the default case-insensitive hash code provider 72 /// and the default case-insensitive comparer.</para> 73 /// </devdoc> NameObjectCollectionBase(int capacity)74 protected NameObjectCollectionBase(int capacity) 75 { 76 _keyComparer = s_defaultComparer; 77 Reset(capacity); 78 } 79 NameObjectCollectionBase(SerializationInfo info, StreamingContext context)80 protected NameObjectCollectionBase(SerializationInfo info, StreamingContext context) 81 { 82 throw new PlatformNotSupportedException(); 83 } 84 GetObjectData(SerializationInfo info, StreamingContext context)85 public virtual void GetObjectData(SerializationInfo info, StreamingContext context) 86 { 87 throw new PlatformNotSupportedException(); 88 } 89 OnDeserialization(object sender)90 public virtual void OnDeserialization(object sender) 91 { 92 throw new PlatformNotSupportedException(); 93 } 94 95 // 96 // Private helpers 97 // 98 Reset()99 private void Reset() 100 { 101 _entriesArray = new ArrayList(); 102 _entriesTable = new Hashtable(_keyComparer); 103 _nullKeyEntry = null; 104 _version++; 105 } 106 Reset(int capacity)107 private void Reset(int capacity) 108 { 109 _entriesArray = new ArrayList(capacity); 110 _entriesTable = new Hashtable(capacity, _keyComparer); 111 _nullKeyEntry = null; 112 _version++; 113 } 114 FindEntry(String key)115 private NameObjectEntry FindEntry(String key) 116 { 117 if (key != null) 118 return (NameObjectEntry)_entriesTable[key]; 119 else 120 return _nullKeyEntry; 121 } 122 123 internal IEqualityComparer Comparer 124 { 125 get 126 { 127 return _keyComparer; 128 } 129 set 130 { 131 _keyComparer = value; 132 } 133 } 134 135 136 /// <devdoc> 137 /// <para>Gets or sets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance is read-only.</para> 138 /// </devdoc> 139 protected bool IsReadOnly 140 { 141 get { return _readOnly; } 142 set { _readOnly = value; } 143 } 144 145 /// <devdoc> 146 /// <para>Gets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance contains entries whose 147 /// keys are not <see langword='null'/>.</para> 148 /// </devdoc> BaseHasKeys()149 protected bool BaseHasKeys() 150 { 151 return (_entriesTable.Count > 0); // any entries with keys? 152 } 153 154 // 155 // Methods to add / remove entries 156 // 157 158 /// <devdoc> 159 /// <para>Adds an entry with the specified key and value into the 160 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 161 /// </devdoc> BaseAdd(String name, Object value)162 protected void BaseAdd(String name, Object value) 163 { 164 if (_readOnly) 165 throw new NotSupportedException(SR.CollectionReadOnly); 166 167 NameObjectEntry entry = new NameObjectEntry(name, value); 168 169 // insert entry into hashtable 170 if (name != null) 171 { 172 if (_entriesTable[name] == null) 173 _entriesTable.Add(name, entry); 174 } 175 else 176 { // null key -- special case -- hashtable doesn't like null keys 177 if (_nullKeyEntry == null) 178 _nullKeyEntry = entry; 179 } 180 181 // add entry to the list 182 _entriesArray.Add(entry); 183 184 _version++; 185 } 186 187 /// <devdoc> 188 /// <para>Removes the entries with the specified key from the 189 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 190 /// </devdoc> BaseRemove(String name)191 protected void BaseRemove(String name) 192 { 193 if (_readOnly) 194 throw new NotSupportedException(SR.CollectionReadOnly); 195 196 if (name != null) 197 { 198 // remove from hashtable 199 _entriesTable.Remove(name); 200 201 // remove from array 202 for (int i = _entriesArray.Count - 1; i >= 0; i--) 203 { 204 if (_keyComparer.Equals(name, BaseGetKey(i))) 205 _entriesArray.RemoveAt(i); 206 } 207 } 208 else 209 { // null key -- special case 210 // null out special 'null key' entry 211 _nullKeyEntry = null; 212 213 // remove from array 214 for (int i = _entriesArray.Count - 1; i >= 0; i--) 215 { 216 if (BaseGetKey(i) == null) 217 _entriesArray.RemoveAt(i); 218 } 219 } 220 221 _version++; 222 } 223 224 /// <devdoc> 225 /// <para> Removes the entry at the specified index of the 226 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 227 /// </devdoc> BaseRemoveAt(int index)228 protected void BaseRemoveAt(int index) 229 { 230 if (_readOnly) 231 throw new NotSupportedException(SR.CollectionReadOnly); 232 233 String key = BaseGetKey(index); 234 235 if (key != null) 236 { 237 // remove from hashtable 238 _entriesTable.Remove(key); 239 } 240 else 241 { // null key -- special case 242 // null out special 'null key' entry 243 _nullKeyEntry = null; 244 } 245 246 // remove from array 247 _entriesArray.RemoveAt(index); 248 249 _version++; 250 } 251 252 /// <devdoc> 253 /// <para>Removes all entries from the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 254 /// </devdoc> BaseClear()255 protected void BaseClear() 256 { 257 if (_readOnly) 258 throw new NotSupportedException(SR.CollectionReadOnly); 259 260 Reset(); 261 } 262 263 // 264 // Access by name 265 // 266 267 /// <devdoc> 268 /// <para>Gets the value of the first entry with the specified key from 269 /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 270 /// </devdoc> BaseGet(String name)271 protected Object BaseGet(String name) 272 { 273 NameObjectEntry e = FindEntry(name); 274 return (e != null) ? e.Value : null; 275 } 276 277 /// <devdoc> 278 /// <para>Sets the value of the first entry with the specified key in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> 279 /// instance, if found; otherwise, adds an entry with the specified key and value 280 /// into the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> 281 /// instance.</para> 282 /// </devdoc> BaseSet(String name, Object value)283 protected void BaseSet(String name, Object value) 284 { 285 if (_readOnly) 286 throw new NotSupportedException(SR.CollectionReadOnly); 287 288 NameObjectEntry entry = FindEntry(name); 289 if (entry != null) 290 { 291 entry.Value = value; 292 _version++; 293 } 294 else 295 { 296 BaseAdd(name, value); 297 } 298 } 299 300 // 301 // Access by index 302 // 303 304 /// <devdoc> 305 /// <para>Gets the value of the entry at the specified index of 306 /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 307 /// </devdoc> BaseGet(int index)308 protected Object BaseGet(int index) 309 { 310 NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]; 311 return entry.Value; 312 } 313 314 /// <devdoc> 315 /// <para>Gets the key of the entry at the specified index of the 316 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> 317 /// instance.</para> 318 /// </devdoc> BaseGetKey(int index)319 protected String BaseGetKey(int index) 320 { 321 NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]; 322 return entry.Key; 323 } 324 325 /// <devdoc> 326 /// <para>Sets the value of the entry at the specified index of 327 /// the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 328 /// </devdoc> BaseSet(int index, Object value)329 protected void BaseSet(int index, Object value) 330 { 331 if (_readOnly) 332 throw new NotSupportedException(SR.CollectionReadOnly); 333 334 NameObjectEntry entry = (NameObjectEntry)_entriesArray[index]; 335 entry.Value = value; 336 _version++; 337 } 338 339 // 340 // ICollection implementation 341 // 342 343 /// <devdoc> 344 /// <para>Returns an enumerator that can iterate through the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>.</para> 345 /// </devdoc> GetEnumerator()346 public virtual IEnumerator GetEnumerator() 347 { 348 return new NameObjectKeysEnumerator(this); 349 } 350 351 /// <devdoc> 352 /// <para>Gets the number of key-and-value pairs in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 353 /// </devdoc> 354 public virtual int Count 355 { 356 get 357 { 358 return _entriesArray.Count; 359 } 360 } 361 ICollection.CopyTo(Array array, int index)362 void ICollection.CopyTo(Array array, int index) 363 { 364 if (array == null) 365 { 366 throw new ArgumentNullException(nameof(array)); 367 } 368 369 if (array.Rank != 1) 370 { 371 throw new ArgumentException(SR.Arg_MultiRank, nameof(array)); 372 } 373 374 if (index < 0) 375 { 376 throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum); 377 } 378 379 if (array.Length - index < _entriesArray.Count) 380 { 381 throw new ArgumentException(SR.Arg_InsufficientSpace); 382 } 383 384 for (IEnumerator e = this.GetEnumerator(); e.MoveNext();) 385 array.SetValue(e.Current, index++); 386 } 387 388 Object ICollection.SyncRoot 389 { 390 get 391 { 392 if (_syncRoot == null) 393 { 394 System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); 395 } 396 return _syncRoot; 397 } 398 } 399 400 bool ICollection.IsSynchronized 401 { 402 get { return false; } 403 } 404 405 // 406 // Helper methods to get arrays of keys and values 407 // 408 409 /// <devdoc> 410 /// <para>Returns a <see cref='System.String' qualify='true'/> array containing all the keys in the 411 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 412 /// </devdoc> BaseGetAllKeys()413 protected String[] BaseGetAllKeys() 414 { 415 int n = _entriesArray.Count; 416 String[] allKeys = new String[n]; 417 418 for (int i = 0; i < n; i++) 419 allKeys[i] = BaseGetKey(i); 420 421 return allKeys; 422 } 423 424 /// <devdoc> 425 /// <para>Returns an <see cref='System.Object' qualify='true'/> array containing all the values in the 426 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 427 /// </devdoc> BaseGetAllValues()428 protected Object[] BaseGetAllValues() 429 { 430 int n = _entriesArray.Count; 431 Object[] allValues = new Object[n]; 432 433 for (int i = 0; i < n; i++) 434 allValues[i] = BaseGet(i); 435 436 return allValues; 437 } 438 439 /// <devdoc> 440 /// <para>Returns an array of the specified type containing 441 /// all the values in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 442 /// </devdoc> BaseGetAllValues(Type type)443 protected object[] BaseGetAllValues(Type type) 444 { 445 int n = _entriesArray.Count; 446 if (type == null) 447 { 448 throw new ArgumentNullException(nameof(type)); 449 } 450 object[] allValues = (object[])Array.CreateInstance(type, n); 451 452 for (int i = 0; i < n; i++) 453 { 454 allValues[i] = BaseGet(i); 455 } 456 457 return allValues; 458 } 459 460 // 461 // Keys property 462 // 463 464 /// <devdoc> 465 /// <para>Returns a <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/> instance containing 466 /// all the keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para> 467 /// </devdoc> 468 public virtual KeysCollection Keys 469 { 470 get 471 { 472 if (_keys == null) 473 _keys = new KeysCollection(this); 474 return _keys; 475 } 476 } 477 478 // 479 // Simple entry class to allow substitution of values and indexed access to keys 480 // 481 482 internal class NameObjectEntry 483 { NameObjectEntry(String name, Object value)484 internal NameObjectEntry(String name, Object value) 485 { 486 Key = name; 487 Value = value; 488 } 489 490 internal String Key; 491 internal Object Value; 492 } 493 494 // 495 // Enumerator over keys of NameObjectCollection 496 // 497 498 [Serializable] 499 internal class NameObjectKeysEnumerator : IEnumerator 500 { 501 private int _pos; 502 private NameObjectCollectionBase _coll; 503 private int _version; 504 NameObjectKeysEnumerator(NameObjectCollectionBase coll)505 internal NameObjectKeysEnumerator(NameObjectCollectionBase coll) 506 { 507 _coll = coll; 508 _version = _coll._version; 509 _pos = -1; 510 } 511 MoveNext()512 public bool MoveNext() 513 { 514 if (_version != _coll._version) 515 throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); 516 517 if (_pos < _coll.Count - 1) 518 { 519 _pos++; 520 return true; 521 } 522 else 523 { 524 _pos = _coll.Count; 525 return false; 526 } 527 } 528 Reset()529 public void Reset() 530 { 531 if (_version != _coll._version) 532 throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); 533 _pos = -1; 534 } 535 536 public Object Current 537 { 538 get 539 { 540 if (_pos >= 0 && _pos < _coll.Count) 541 { 542 return _coll.BaseGetKey(_pos); 543 } 544 else 545 { 546 throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); 547 } 548 } 549 } 550 } 551 552 // 553 // Keys collection 554 // 555 556 /// <devdoc> 557 /// <para>Represents a collection of the <see cref='System.String' qualify='true'/> keys of a collection.</para> 558 /// </devdoc> 559 [Serializable] 560 public class KeysCollection : ICollection 561 { 562 private NameObjectCollectionBase _coll; 563 KeysCollection(NameObjectCollectionBase coll)564 internal KeysCollection(NameObjectCollectionBase coll) 565 { 566 _coll = coll; 567 } 568 569 // Indexed access 570 571 /// <devdoc> 572 /// <para> Gets the key at the specified index of the collection.</para> 573 /// </devdoc> Get(int index)574 public virtual String Get(int index) 575 { 576 return _coll.BaseGetKey(index); 577 } 578 579 /// <devdoc> 580 /// <para>Represents the entry at the specified index of the collection.</para> 581 /// </devdoc> 582 public String this[int index] 583 { 584 get 585 { 586 return Get(index); 587 } 588 } 589 590 // ICollection implementation 591 592 /// <devdoc> 593 /// <para>Returns an enumerator that can iterate through the 594 /// <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para> 595 /// </devdoc> GetEnumerator()596 public IEnumerator GetEnumerator() 597 { 598 return new NameObjectKeysEnumerator(_coll); 599 } 600 601 /// <devdoc> 602 /// <para>Gets the number of keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para> 603 /// </devdoc> 604 public int Count 605 { 606 get 607 { 608 return _coll.Count; 609 } 610 } 611 ICollection.CopyTo(Array array, int index)612 void ICollection.CopyTo(Array array, int index) 613 { 614 if (array == null) 615 { 616 throw new ArgumentNullException(nameof(array)); 617 } 618 619 if (array.Rank != 1) 620 { 621 throw new ArgumentException(SR.Arg_MultiRank, nameof(array)); 622 } 623 624 if (index < 0) 625 { 626 throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum); 627 } 628 629 if (array.Length - index < _coll.Count) 630 { 631 throw new ArgumentException(SR.Arg_InsufficientSpace); 632 } 633 634 for (IEnumerator e = this.GetEnumerator(); e.MoveNext();) 635 array.SetValue(e.Current, index++); 636 } 637 638 Object ICollection.SyncRoot 639 { 640 get { return ((ICollection)_coll).SyncRoot; } 641 } 642 643 644 bool ICollection.IsSynchronized 645 { 646 get { return false; } 647 } 648 } 649 } 650 } 651