// 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; namespace System.Collections.ObjectModel { [Serializable] [DebuggerTypeProxy(typeof(DictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] #if !MONO [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] #endif public class ReadOnlyDictionary : IDictionary, IDictionary, IReadOnlyDictionary { private readonly IDictionary m_dictionary; // Do not rename (binary serialization) [NonSerialized] private Object _syncRoot; [NonSerialized] private KeyCollection _keys; [NonSerialized] private ValueCollection _values; public ReadOnlyDictionary(IDictionary dictionary) { if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } m_dictionary = dictionary; } protected IDictionary Dictionary { get { return m_dictionary; } } public KeyCollection Keys { get { if (_keys == null) { _keys = new KeyCollection(m_dictionary.Keys); } return _keys; } } public ValueCollection Values { get { if (_values == null) { _values = new ValueCollection(m_dictionary.Values); } return _values; } } #region IDictionary Members public bool ContainsKey(TKey key) { return m_dictionary.ContainsKey(key); } ICollection IDictionary.Keys { get { return Keys; } } public bool TryGetValue(TKey key, out TValue value) { return m_dictionary.TryGetValue(key, out value); } ICollection IDictionary.Values { get { return Values; } } public TValue this[TKey key] { get { return m_dictionary[key]; } } void IDictionary.Add(TKey key, TValue value) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } bool IDictionary.Remove(TKey key) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } TValue IDictionary.this[TKey key] { get { return m_dictionary[key]; } set { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } } #endregion #region ICollection> Members public int Count { get { return m_dictionary.Count; } } bool ICollection>.Contains(KeyValuePair item) { return m_dictionary.Contains(item); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { m_dictionary.CopyTo(array, arrayIndex); } bool ICollection>.IsReadOnly { get { return true; } } void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } void ICollection>.Clear() { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } #endregion #region IEnumerable> Members public IEnumerator> 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) { throw new ArgumentNullException(nameof(key)); } return key is TKey; } void IDictionary.Add(object key, object value) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } void IDictionary.Clear() { throw new NotSupportedException(SR.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) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } ICollection IDictionary.Values { get { return Values; } } object IDictionary.this[object key] { get { if (IsCompatibleKey(key)) { return this[(TKey)key]; } return null; } set { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } } void ICollection.CopyTo(Array array, int index) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (array.Rank != 1) { throw new ArgumentException(SR.Arg_RankMultiDimNotSupported); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException(SR.Arg_NonZeroLowerBound); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); } if (array.Length - index < Count) { throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); } KeyValuePair[] pairs = array as KeyValuePair[]; 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) { throw new ArgumentException(SR.Argument_InvalidArrayType); } try { foreach (var item in m_dictionary) { objects[index++] = new KeyValuePair(item.Key, item.Value); } } catch (ArrayTypeMismatchException) { throw new ArgumentException(SR.Argument_InvalidArrayType); } } } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) { ICollection c = m_dictionary as ICollection; if (c != null) { _syncRoot = c.SyncRoot; } else { System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); } } return _syncRoot; } } [Serializable] private struct DictionaryEnumerator : IDictionaryEnumerator { private readonly IDictionary _dictionary; private IEnumerator> _enumerator; public DictionaryEnumerator(IDictionary dictionary) { _dictionary = dictionary; _enumerator = _dictionary.GetEnumerator(); } public DictionaryEntry Entry { get { return new DictionaryEntry(_enumerator.Current.Key, _enumerator.Current.Value); } } public object Key { get { return _enumerator.Current.Key; } } public object Value { get { return _enumerator.Current.Value; } } public object Current { get { return Entry; } } public bool MoveNext() { return _enumerator.MoveNext(); } public void Reset() { _enumerator.Reset(); } } #endregion #region IReadOnlyDictionary members IEnumerable IReadOnlyDictionary.Keys { get { return Keys; } } IEnumerable IReadOnlyDictionary.Values { get { return Values; } } #endregion IReadOnlyDictionary members [Serializable] [DebuggerTypeProxy(typeof(CollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection { private readonly ICollection _collection; [NonSerialized] private Object _syncRoot; internal KeyCollection(ICollection collection) { if (collection == null) { throw new ArgumentNullException(nameof(collection)); } _collection = collection; } #region ICollection Members void ICollection.Add(TKey item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } void ICollection.Clear() { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } bool ICollection.Contains(TKey item) { return _collection.Contains(item); } public void CopyTo(TKey[] array, int arrayIndex) { _collection.CopyTo(array, arrayIndex); } public int Count { get { return _collection.Count; } } bool ICollection.IsReadOnly { get { return true; } } bool ICollection.Remove(TKey item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return _collection.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((IEnumerable)_collection).GetEnumerator(); } #endregion #region ICollection Members void ICollection.CopyTo(Array array, int index) { ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper(_collection, array, index); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) { ICollection c = _collection as ICollection; if (c != null) { _syncRoot = c.SyncRoot; } else { System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); } } return _syncRoot; } } #endregion } [Serializable] [DebuggerTypeProxy(typeof(CollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection { private readonly ICollection _collection; [NonSerialized] private Object _syncRoot; internal ValueCollection(ICollection collection) { if (collection == null) { throw new ArgumentNullException(nameof(collection)); } _collection = collection; } #region ICollection Members void ICollection.Add(TValue item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } void ICollection.Clear() { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } bool ICollection.Contains(TValue item) { return _collection.Contains(item); } public void CopyTo(TValue[] array, int arrayIndex) { _collection.CopyTo(array, arrayIndex); } public int Count { get { return _collection.Count; } } bool ICollection.IsReadOnly { get { return true; } } bool ICollection.Remove(TValue item) { throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return _collection.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((IEnumerable)_collection).GetEnumerator(); } #endregion #region ICollection Members void ICollection.CopyTo(Array array, int index) { ReadOnlyDictionaryHelpers.CopyToNonGenericICollectionHelper(_collection, array, index); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) { ICollection c = _collection as ICollection; if (c != null) { _syncRoot = c.SyncRoot; } else { System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); } } return _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(ICollection collection, Array array, int index) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (array.Rank != 1) { throw new ArgumentException(SR.Arg_RankMultiDimNotSupported); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException(SR.Arg_NonZeroLowerBound); } if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); } if (array.Length - index < collection.Count) { throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall); } // Easy out if the ICollection 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 { /* FxOverRh: Type.IsAssignableNot() not an api on that platform. // // 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))) { throw new ArgumentException(SR.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) { throw new ArgumentException(SR.Argument_InvalidArrayType); } try { foreach (var item in collection) { objects[index++] = item; } } catch (ArrayTypeMismatchException) { throw new ArgumentException(SR.Argument_InvalidArrayType); } } } #endregion Helper method for our KeyCollection and ValueCollection } }