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