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 using System; 6 using System.Collections; 7 using System.Collections.Generic; 8 using System.Diagnostics; 9 10 namespace System.Collections.ObjectModel 11 { 12 [Serializable] 13 [DebuggerTypeProxy(typeof(DictionaryDebugView<,>))] 14 [DebuggerDisplay("Count = {Count}")] 15 #if !MONO 16 [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] 17 #endif 18 public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue> 19 { 20 private readonly IDictionary<TKey, TValue> m_dictionary; // Do not rename (binary serialization) 21 [NonSerialized] 22 private Object _syncRoot; 23 [NonSerialized] 24 private KeyCollection _keys; 25 [NonSerialized] 26 private ValueCollection _values; 27 ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)28 public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary) 29 { 30 if (dictionary == null) 31 { 32 throw new ArgumentNullException(nameof(dictionary)); 33 } 34 m_dictionary = dictionary; 35 } 36 37 protected IDictionary<TKey, TValue> Dictionary 38 { 39 get { return m_dictionary; } 40 } 41 42 public KeyCollection Keys 43 { 44 get 45 { 46 if (_keys == null) 47 { 48 _keys = new KeyCollection(m_dictionary.Keys); 49 } 50 return _keys; 51 } 52 } 53 54 public ValueCollection Values 55 { 56 get 57 { 58 if (_values == null) 59 { 60 _values = new ValueCollection(m_dictionary.Values); 61 } 62 return _values; 63 } 64 } 65 66 #region IDictionary<TKey, TValue> Members 67 ContainsKey(TKey key)68 public bool ContainsKey(TKey key) 69 { 70 return m_dictionary.ContainsKey(key); 71 } 72 73 ICollection<TKey> IDictionary<TKey, TValue>.Keys 74 { 75 get 76 { 77 return Keys; 78 } 79 } 80 TryGetValue(TKey key, out TValue value)81 public bool TryGetValue(TKey key, out TValue value) 82 { 83 return m_dictionary.TryGetValue(key, out value); 84 } 85 86 ICollection<TValue> IDictionary<TKey, TValue>.Values 87 { 88 get 89 { 90 return Values; 91 } 92 } 93 94 public TValue this[TKey key] 95 { 96 get 97 { 98 return m_dictionary[key]; 99 } 100 } 101 Add(TKey key, TValue value)102 void IDictionary<TKey, TValue>.Add(TKey key, TValue value) 103 { 104 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 105 } 106 Remove(TKey key)107 bool IDictionary<TKey, TValue>.Remove(TKey key) 108 { 109 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 110 } 111 112 TValue IDictionary<TKey, TValue>.this[TKey key] 113 { 114 get 115 { 116 return m_dictionary[key]; 117 } 118 set 119 { 120 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 121 } 122 } 123 124 #endregion 125 126 #region ICollection<KeyValuePair<TKey, TValue>> Members 127 128 public int Count 129 { 130 get { return m_dictionary.Count; } 131 } 132 Contains(KeyValuePair<TKey, TValue> item)133 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) 134 { 135 return m_dictionary.Contains(item); 136 } 137 CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)138 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) 139 { 140 m_dictionary.CopyTo(array, arrayIndex); 141 } 142 143 bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly 144 { 145 get { return true; } 146 } 147 Add(KeyValuePair<TKey, TValue> item)148 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) 149 { 150 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 151 } 152 Clear()153 void ICollection<KeyValuePair<TKey, TValue>>.Clear() 154 { 155 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 156 } 157 Remove(KeyValuePair<TKey, TValue> item)158 bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) 159 { 160 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 161 } 162 163 #endregion 164 165 #region IEnumerable<KeyValuePair<TKey, TValue>> Members 166 GetEnumerator()167 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 168 { 169 return m_dictionary.GetEnumerator(); 170 } 171 172 #endregion 173 174 #region IEnumerable Members 175 System.Collections.IEnumerable.GetEnumerator()176 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 177 { 178 return ((IEnumerable)m_dictionary).GetEnumerator(); 179 } 180 181 #endregion 182 183 #region IDictionary Members 184 IsCompatibleKey(object key)185 private static bool IsCompatibleKey(object key) 186 { 187 if (key == null) 188 { 189 throw new ArgumentNullException(nameof(key)); 190 } 191 return key is TKey; 192 } 193 IDictionary.Add(object key, object value)194 void IDictionary.Add(object key, object value) 195 { 196 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 197 } 198 IDictionary.Clear()199 void IDictionary.Clear() 200 { 201 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 202 } 203 IDictionary.Contains(object key)204 bool IDictionary.Contains(object key) 205 { 206 return IsCompatibleKey(key) && ContainsKey((TKey)key); 207 } 208 IDictionary.GetEnumerator()209 IDictionaryEnumerator IDictionary.GetEnumerator() 210 { 211 IDictionary d = m_dictionary as IDictionary; 212 if (d != null) 213 { 214 return d.GetEnumerator(); 215 } 216 return new DictionaryEnumerator(m_dictionary); 217 } 218 219 bool IDictionary.IsFixedSize 220 { 221 get { return true; } 222 } 223 224 bool IDictionary.IsReadOnly 225 { 226 get { return true; } 227 } 228 229 ICollection IDictionary.Keys 230 { 231 get 232 { 233 return Keys; 234 } 235 } 236 IDictionary.Remove(object key)237 void IDictionary.Remove(object key) 238 { 239 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 240 } 241 242 ICollection IDictionary.Values 243 { 244 get 245 { 246 return Values; 247 } 248 } 249 250 object IDictionary.this[object key] 251 { 252 get 253 { 254 if (IsCompatibleKey(key)) 255 { 256 return this[(TKey)key]; 257 } 258 return null; 259 } 260 set 261 { 262 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 263 } 264 } 265 ICollection.CopyTo(Array array, int index)266 void ICollection.CopyTo(Array array, int index) 267 { 268 if (array == null) 269 { 270 throw new ArgumentNullException(nameof(array)); 271 } 272 273 if (array.Rank != 1) 274 { 275 throw new ArgumentException(SR.Arg_RankMultiDimNotSupported); 276 } 277 278 if (array.GetLowerBound(0) != 0) 279 { 280 throw new ArgumentException(SR.Arg_NonZeroLowerBound); 281 } 282 283 if (index < 0 || index > array.Length) 284 { 285 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); 286 } 287 288 if (array.Length - index < Count) 289 { 290 throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); 291 } 292 293 KeyValuePair<TKey, TValue>[] pairs = array as KeyValuePair<TKey, TValue>[]; 294 if (pairs != null) 295 { 296 m_dictionary.CopyTo(pairs, index); 297 } 298 else 299 { 300 DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; 301 if (dictEntryArray != null) 302 { 303 foreach (var item in m_dictionary) 304 { 305 dictEntryArray[index++] = new DictionaryEntry(item.Key, item.Value); 306 } 307 } 308 else 309 { 310 object[] objects = array as object[]; 311 if (objects == null) 312 { 313 throw new ArgumentException(SR.Argument_InvalidArrayType); 314 } 315 316 try 317 { 318 foreach (var item in m_dictionary) 319 { 320 objects[index++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value); 321 } 322 } 323 catch (ArrayTypeMismatchException) 324 { 325 throw new ArgumentException(SR.Argument_InvalidArrayType); 326 } 327 } 328 } 329 } 330 331 bool ICollection.IsSynchronized 332 { 333 get { return false; } 334 } 335 336 object ICollection.SyncRoot 337 { 338 get 339 { 340 if (_syncRoot == null) 341 { 342 ICollection c = m_dictionary as ICollection; 343 if (c != null) 344 { 345 _syncRoot = c.SyncRoot; 346 } 347 else 348 { 349 System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); 350 } 351 } 352 return _syncRoot; 353 } 354 } 355 356 [Serializable] 357 private struct DictionaryEnumerator : IDictionaryEnumerator 358 { 359 private readonly IDictionary<TKey, TValue> _dictionary; 360 private IEnumerator<KeyValuePair<TKey, TValue>> _enumerator; 361 DictionaryEnumeratorSystem.Collections.ObjectModel.ReadOnlyDictionary.DictionaryEnumerator362 public DictionaryEnumerator(IDictionary<TKey, TValue> dictionary) 363 { 364 _dictionary = dictionary; 365 _enumerator = _dictionary.GetEnumerator(); 366 } 367 368 public DictionaryEntry Entry 369 { 370 get { return new DictionaryEntry(_enumerator.Current.Key, _enumerator.Current.Value); } 371 } 372 373 public object Key 374 { 375 get { return _enumerator.Current.Key; } 376 } 377 378 public object Value 379 { 380 get { return _enumerator.Current.Value; } 381 } 382 383 public object Current 384 { 385 get { return Entry; } 386 } 387 MoveNextSystem.Collections.ObjectModel.ReadOnlyDictionary.DictionaryEnumerator388 public bool MoveNext() 389 { 390 return _enumerator.MoveNext(); 391 } 392 ResetSystem.Collections.ObjectModel.ReadOnlyDictionary.DictionaryEnumerator393 public void Reset() 394 { 395 _enumerator.Reset(); 396 } 397 } 398 399 #endregion 400 401 #region IReadOnlyDictionary members 402 403 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys 404 { 405 get 406 { 407 return Keys; 408 } 409 } 410 411 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values 412 { 413 get 414 { 415 return Values; 416 } 417 } 418 419 #endregion IReadOnlyDictionary members 420 421 [Serializable] 422 [DebuggerTypeProxy(typeof(CollectionDebugView<>))] 423 [DebuggerDisplay("Count = {Count}")] 424 public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey> 425 { 426 private readonly ICollection<TKey> _collection; 427 [NonSerialized] 428 private Object _syncRoot; 429 KeyCollection(ICollection<TKey> collection)430 internal KeyCollection(ICollection<TKey> collection) 431 { 432 if (collection == null) 433 { 434 throw new ArgumentNullException(nameof(collection)); 435 } 436 _collection = collection; 437 } 438 439 #region ICollection<T> Members 440 Add(TKey item)441 void ICollection<TKey>.Add(TKey item) 442 { 443 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 444 } 445 Clear()446 void ICollection<TKey>.Clear() 447 { 448 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 449 } 450 Contains(TKey item)451 bool ICollection<TKey>.Contains(TKey item) 452 { 453 return _collection.Contains(item); 454 } 455 CopyTo(TKey[] array, int arrayIndex)456 public void CopyTo(TKey[] array, int arrayIndex) 457 { 458 _collection.CopyTo(array, arrayIndex); 459 } 460 461 public int Count 462 { 463 get { return _collection.Count; } 464 } 465 466 bool ICollection<TKey>.IsReadOnly 467 { 468 get { return true; } 469 } 470 Remove(TKey item)471 bool ICollection<TKey>.Remove(TKey item) 472 { 473 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 474 } 475 476 #endregion 477 478 #region IEnumerable<T> Members 479 GetEnumerator()480 public IEnumerator<TKey> GetEnumerator() 481 { 482 return _collection.GetEnumerator(); 483 } 484 485 #endregion 486 487 #region IEnumerable Members 488 System.Collections.IEnumerable.GetEnumerator()489 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 490 { 491 return ((IEnumerable)_collection).GetEnumerator(); 492 } 493 494 #endregion 495 496 #region ICollection Members 497 ICollection.CopyTo(Array array, int index)498 void ICollection.CopyTo(Array array, int index) 499 { 500 ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper<TKey>(_collection, array, index); 501 } 502 503 bool ICollection.IsSynchronized 504 { 505 get { return false; } 506 } 507 508 object ICollection.SyncRoot 509 { 510 get 511 { 512 if (_syncRoot == null) 513 { 514 ICollection c = _collection as ICollection; 515 if (c != null) 516 { 517 _syncRoot = c.SyncRoot; 518 } 519 else 520 { 521 System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); 522 } 523 } 524 return _syncRoot; 525 } 526 } 527 #endregion 528 } 529 530 [Serializable] 531 [DebuggerTypeProxy(typeof(CollectionDebugView<>))] 532 [DebuggerDisplay("Count = {Count}")] 533 public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue> 534 { 535 private readonly ICollection<TValue> _collection; 536 [NonSerialized] 537 private Object _syncRoot; 538 ValueCollection(ICollection<TValue> collection)539 internal ValueCollection(ICollection<TValue> collection) 540 { 541 if (collection == null) 542 { 543 throw new ArgumentNullException(nameof(collection)); 544 } 545 _collection = collection; 546 } 547 548 #region ICollection<T> Members 549 Add(TValue item)550 void ICollection<TValue>.Add(TValue item) 551 { 552 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 553 } 554 Clear()555 void ICollection<TValue>.Clear() 556 { 557 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 558 } 559 Contains(TValue item)560 bool ICollection<TValue>.Contains(TValue item) 561 { 562 return _collection.Contains(item); 563 } 564 CopyTo(TValue[] array, int arrayIndex)565 public void CopyTo(TValue[] array, int arrayIndex) 566 { 567 _collection.CopyTo(array, arrayIndex); 568 } 569 570 public int Count 571 { 572 get { return _collection.Count; } 573 } 574 575 bool ICollection<TValue>.IsReadOnly 576 { 577 get { return true; } 578 } 579 Remove(TValue item)580 bool ICollection<TValue>.Remove(TValue item) 581 { 582 throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); 583 } 584 585 #endregion 586 587 #region IEnumerable<T> Members 588 GetEnumerator()589 public IEnumerator<TValue> GetEnumerator() 590 { 591 return _collection.GetEnumerator(); 592 } 593 594 #endregion 595 596 #region IEnumerable Members 597 System.Collections.IEnumerable.GetEnumerator()598 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 599 { 600 return ((IEnumerable)_collection).GetEnumerator(); 601 } 602 603 #endregion 604 605 #region ICollection Members 606 ICollection.CopyTo(Array array, int index)607 void ICollection.CopyTo(Array array, int index) 608 { 609 ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper<TValue>(_collection, array, index); 610 } 611 612 bool ICollection.IsSynchronized 613 { 614 get { return false; } 615 } 616 617 object ICollection.SyncRoot 618 { 619 get 620 { 621 if (_syncRoot == null) 622 { 623 ICollection c = _collection as ICollection; 624 if (c != null) 625 { 626 _syncRoot = c.SyncRoot; 627 } 628 else 629 { 630 System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null); 631 } 632 } 633 return _syncRoot; 634 } 635 } 636 #endregion ICollection Members 637 } 638 } 639 640 // To share code when possible, use a non-generic class to get rid of irrelevant type parameters. 641 internal static class ReadOnlyDictionaryHelpers 642 { 643 #region Helper method for our KeyCollection and ValueCollection 644 645 // Abstracted away to avoid redundant implementations. CopyToNonGenericICollectionHelper(ICollection<T> collection, Array array, int index)646 internal static void CopyToNonGenericICollectionHelper<T>(ICollection<T> collection, Array array, int index) 647 { 648 if (array == null) 649 { 650 throw new ArgumentNullException(nameof(array)); 651 } 652 653 if (array.Rank != 1) 654 { 655 throw new ArgumentException(SR.Arg_RankMultiDimNotSupported); 656 } 657 658 if (array.GetLowerBound(0) != 0) 659 { 660 throw new ArgumentException(SR.Arg_NonZeroLowerBound); 661 } 662 663 if (index < 0) 664 { 665 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); 666 } 667 668 if (array.Length - index < collection.Count) 669 { 670 throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); 671 } 672 673 // Easy out if the ICollection<T> implements the non-generic ICollection 674 ICollection nonGenericCollection = collection as ICollection; 675 if (nonGenericCollection != null) 676 { 677 nonGenericCollection.CopyTo(array, index); 678 return; 679 } 680 681 T[] items = array as T[]; 682 if (items != null) 683 { 684 collection.CopyTo(items, index); 685 } 686 else 687 { 688 /* 689 FxOverRh: Type.IsAssignableNot() not an api on that platform. 690 691 // 692 // Catch the obvious case assignment will fail. 693 // We can found all possible problems by doing the check though. 694 // For example, if the element type of the Array is derived from T, 695 // we can't figure out if we can successfully copy the element beforehand. 696 // 697 Type targetType = array.GetType().GetElementType(); 698 Type sourceType = typeof(T); 699 if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) { 700 throw new ArgumentException(SR.Argument_InvalidArrayType); 701 } 702 */ 703 704 // 705 // We can't cast array of value type to object[], so we don't support 706 // widening of primitive types here. 707 // 708 object[] objects = array as object[]; 709 if (objects == null) 710 { 711 throw new ArgumentException(SR.Argument_InvalidArrayType); 712 } 713 714 try 715 { 716 foreach (var item in collection) 717 { 718 objects[index++] = item; 719 } 720 } 721 catch (ArrayTypeMismatchException) 722 { 723 throw new ArgumentException(SR.Argument_InvalidArrayType); 724 } 725 } 726 } 727 #endregion Helper method for our KeyCollection and ValueCollection 728 } 729 } 730 731