// 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; namespace LibreMetaverse { /// /// A MultiValueDictionary can be viewed as a that allows multiple /// values for any given unique key. While the MultiValueDictionary API is /// mostly the same as that of a regular , there is a distinction /// in that getting the value for a key returns a of values /// rather than a single value associated with that key. Additionally, /// there is functionality to allow adding or removing more than a single /// value at once. /// /// The MultiValueDictionary can also be viewed as a IReadOnlyDictionary<TKey,IReadOnlyCollection<TValue>t> /// where the is abstracted from the view of the programmer. /// /// For a read-only MultiValueDictionary, see . /// /// The type of the key. /// The type of the value. public class MultiValueDictionary : IReadOnlyDictionary> { #region Variables /*====================================================================== ** Variables ======================================================================*/ /// /// The private dictionary that this class effectively wraps around /// private readonly Dictionary _dictionary; /// /// The function to construct a new /// /// private Func> NewCollectionFactory = () => new List(); /// /// The current version of this MultiValueDictionary used to determine MultiValueDictionary modification /// during enumeration /// private int _version; #endregion #region Constructors /*====================================================================== ** Constructors ======================================================================*/ /// /// Initializes a new instance of the /// class that is empty, has the default initial capacity, and uses the default /// for . /// public MultiValueDictionary() { _dictionary = new Dictionary(); } /// /// Initializes a new instance of the class that is /// empty, has the specified initial capacity, and uses the default /// for . /// /// Initial number of keys that the will allocate space for /// capacity must be >= 0 public MultiValueDictionary(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); _dictionary = new Dictionary(capacity); } /// /// Initializes a new instance of the class /// that is empty, has the default initial capacity, and uses the /// specified . /// /// Specified comparer to use for the s /// If is set to null, then the default for is used. public MultiValueDictionary(IEqualityComparer comparer) { _dictionary = new Dictionary(comparer); } /// /// Initializes a new instance of the class /// that is empty, has the specified initial capacity, and uses the /// specified . /// /// Initial number of keys that the will allocate space for /// Specified comparer to use for the s /// Capacity must be >= 0 /// If is set to null, then the default for is used. public MultiValueDictionary(int capacity, IEqualityComparer comparer) { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); _dictionary = new Dictionary(capacity, comparer); } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> and uses the /// default for the type. /// /// IEnumerable to copy elements into this from /// enumerable must be non-null public MultiValueDictionary(IEnumerable>> enumerable) : this(enumerable, null) { } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> and uses the /// specified . /// /// IEnumerable to copy elements into this from /// Specified comparer to use for the s /// enumerable must be non-null /// If is set to null, then the default for is used. public MultiValueDictionary(IEnumerable>> enumerable, IEqualityComparer comparer) { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); _dictionary = new Dictionary(comparer); foreach (var pair in enumerable) AddRange(pair.Key, pair.Value); } #endregion #region Static Factories /*====================================================================== ** Static Factories ======================================================================*/ /// /// Creates a new new instance of the /// class that is empty, has the default initial capacity, and uses the default /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// A new with the specified /// parameters. /// must not have /// IsReadOnly set to true by default. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create() where TValueCollection : ICollection, new() { if (new TValueCollection().IsReadOnly) throw new InvalidOperationException( "The specified TValueCollection creates collections that have IsReadOnly set to true by default. " + "TValueCollection must be a mutable ICollection."); return new MultiValueDictionary { NewCollectionFactory = () => new TValueCollection() }; } /// /// Creates a new new instance of the /// class that is empty, has the specified initial capacity, and uses the default /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Initial number of keys that the will allocate space for /// A new with the specified /// parameters. /// Capacity must be >= 0 /// must not have /// IsReadOnly set to true by default. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(int capacity) where TValueCollection : ICollection, new() { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); return new MultiValueDictionary(capacity) { NewCollectionFactory = () => new TValueCollection() }; } /// /// Creates a new new instance of the /// class that is empty, has the default initial capacity, and uses the specified /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Specified comparer to use for the s /// must not have /// IsReadOnly set to true by default. /// A new with the specified /// parameters. /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEqualityComparer comparer) where TValueCollection : ICollection, new() { if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); return new MultiValueDictionary(comparer) { NewCollectionFactory = () => new TValueCollection() }; } /// /// Creates a new new instance of the /// class that is empty, has the specified initial capacity, and uses the specified /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Initial number of keys that the will allocate space for /// Specified comparer to use for the s /// A new with the specified /// parameters. /// must not have /// IsReadOnly set to true by default. /// Capacity must be >= 0 /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(int capacity, IEqualityComparer comparer) where TValueCollection : ICollection, new() { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); return new MultiValueDictionary(capacity, comparer) { NewCollectionFactory = () => new TValueCollection() }; } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> /// and uses the default for the type. /// The internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// IEnumerable to copy elements into this from /// A new with the specified /// parameters. /// must not have /// IsReadOnly set to true by default. /// enumerable must be non-null /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEnumerable>> enumerable) where TValueCollection : ICollection, new() { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); var multiValueDictionary = new MultiValueDictionary { NewCollectionFactory = () => new TValueCollection() }; foreach (var pair in enumerable) multiValueDictionary.AddRange(pair.Key, pair.Value); return multiValueDictionary; } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> /// and uses the specified for the type. /// The internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// IEnumerable to copy elements into this from /// Specified comparer to use for the s /// A new with the specified /// parameters. /// must not have /// IsReadOnly set to true by default. /// enumerable must be non-null /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEnumerable>> enumerable, IEqualityComparer comparer) where TValueCollection : ICollection, new() { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); var multiValueDictionary = new MultiValueDictionary(comparer) { NewCollectionFactory = () => new TValueCollection() }; foreach (var pair in enumerable) multiValueDictionary.AddRange(pair.Key, pair.Value); return multiValueDictionary; } #endregion #region Static Factories with Func parameters /*====================================================================== ** Static Factories with Func parameters ======================================================================*/ /// /// Creates a new new instance of the /// class that is empty, has the default initial capacity, and uses the default /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// A function to create a new to use /// in the internal dictionary store of this . /// A new with the specified /// parameters. /// must create collections with /// IsReadOnly set to true by default. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(Func collectionFactory) where TValueCollection : ICollection { if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); return new MultiValueDictionary { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; } /// /// Creates a new new instance of the /// class that is empty, has the specified initial capacity, and uses the default /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Initial number of keys that the will allocate space for /// A function to create a new to use /// in the internal dictionary store of this . /// A new with the specified /// parameters. /// Capacity must be >= 0 /// must create collections with /// IsReadOnly set to true by default. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(int capacity, Func collectionFactory) where TValueCollection : ICollection { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); return new MultiValueDictionary(capacity) { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; } /// /// Creates a new new instance of the /// class that is empty, has the default initial capacity, and uses the specified /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Specified comparer to use for the s /// A function to create a new to use /// in the internal dictionary store of this . /// must create collections with /// IsReadOnly set to true by default. /// A new with the specified /// parameters. /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEqualityComparer comparer, Func collectionFactory) where TValueCollection : ICollection { if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); return new MultiValueDictionary(comparer) { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; } /// /// Creates a new new instance of the /// class that is empty, has the specified initial capacity, and uses the specified /// for . The /// internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// Initial number of keys that the will allocate space for /// Specified comparer to use for the s /// A function to create a new to use /// in the internal dictionary store of this . /// A new with the specified /// parameters. /// must create collections with /// IsReadOnly set to true by default. /// Capacity must be >= 0 /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(int capacity, IEqualityComparer comparer, Func collectionFactory) where TValueCollection : ICollection { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); return new MultiValueDictionary(capacity, comparer) { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> /// and uses the default for the type. /// The internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// IEnumerable to copy elements into this from /// A function to create a new to use /// in the internal dictionary store of this . /// A new with the specified /// parameters. /// must create collections with /// IsReadOnly set to true by default. /// enumerable must be non-null /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEnumerable>> enumerable, Func collectionFactory) where TValueCollection : ICollection { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); var multiValueDictionary = new MultiValueDictionary { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; foreach (var pair in enumerable) multiValueDictionary.AddRange(pair.Key, pair.Value); return multiValueDictionary; } /// /// Initializes a new instance of the class that contains /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> /// and uses the specified for the type. /// The internal dictionary will use instances of the /// class as its collection type. /// /// /// The collection type that this /// will contain in its internal dictionary. /// /// IEnumerable to copy elements into this from /// Specified comparer to use for the s /// A function to create a new to use /// in the internal dictionary store of this . /// A new with the specified /// parameters. /// must create collections with /// IsReadOnly set to true by default. /// enumerable must be non-null /// If is set to null, then the default for is used. /// /// Note that must implement /// in addition to being constructable through new(). The collection returned from the constructor /// must also not have IsReadOnly set to True by default. /// public static MultiValueDictionary Create(IEnumerable>> enumerable, IEqualityComparer comparer, Func collectionFactory) where TValueCollection : ICollection { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); var multiValueDictionary = new MultiValueDictionary(comparer) { NewCollectionFactory = (Func>)(Delegate)collectionFactory }; foreach (var pair in enumerable) multiValueDictionary.AddRange(pair.Key, pair.Value); return multiValueDictionary; } #endregion #region Concrete Methods /*====================================================================== ** Concrete Methods ======================================================================*/ /// /// Adds the specified and to the . /// /// The of the element to add. /// The of the element to add. /// is null. /// /// Unlike the Add for , the Add will not /// throw any exceptions. If the given is already in the , /// then will be added to associated with /// /// /// A call to this Add method will always invalidate any currently running enumeration regardless /// of whether the Add method actually modified the . /// public void Add(TKey key, TValue value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (!_dictionary.TryGetValue(key, out InnerCollectionView collection)) { collection = new InnerCollectionView(key, NewCollectionFactory()); _dictionary.Add(key, collection); } collection.AddValue(value); _version++; } /// /// Adds a number of key-value pairs to this , where /// the key for each value is , and the value for a pair /// is an element from /// /// The of all entries to add /// An of values to add /// and must be non-null /// /// A call to this AddRange method will always invalidate any currently running enumeration regardless /// of whether the AddRange method actually modified the . /// public void AddRange(TKey key, IEnumerable values) { if (key == null) throw new ArgumentNullException(nameof(key)); if (values == null) throw new ArgumentNullException(nameof(values)); if (!_dictionary.TryGetValue(key, out InnerCollectionView collection)) { collection = new InnerCollectionView(key, NewCollectionFactory()); _dictionary.Add(key, collection); } foreach (TValue value in values) { collection.AddValue(value); } _version++; } /// /// Removes every associated with the given /// from the . /// /// The of the elements to remove /// true if the removal was successful; otherwise false /// is null. public bool Remove(TKey key) { if (key == null) throw new ArgumentNullException(nameof(key)); if (_dictionary.TryGetValue(key, out InnerCollectionView _) && _dictionary.Remove(key)) { _version++; return true; } return false; } /// /// Removes the first instance (if any) of the given - /// pair from this . /// /// The of the element to remove /// The of the element to remove /// must be non-null /// true if the removal was successful; otherwise false /// /// If the being removed is the last one associated with its , then that /// will be removed from the and its /// associated will be freed as if a call to /// had been made. /// public bool Remove(TKey key, TValue value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (_dictionary.TryGetValue(key, out InnerCollectionView collection) && collection.RemoveValue(value)) { if (collection.Count == 0) _dictionary.Remove(key); _version++; return true; } return false; } /// /// Determines if the given - /// pair exists within this . /// /// The of the element. /// The of the element. /// true if found; otherwise false /// must be non-null public bool Contains(TKey key, TValue value) { if (key == null) throw new ArgumentNullException(nameof(key)); return (_dictionary.TryGetValue(key, out InnerCollectionView collection) && collection.Contains(value)); } /// /// Determines if the given exists within this . /// /// A to search the for /// true if the contains the ; otherwise false public bool ContainsValue(TValue value) { foreach (InnerCollectionView sublist in _dictionary.Values) if (sublist.Contains(value)) return true; return false; } /// /// Removes every and from this /// . /// public void Clear() { _dictionary.Clear(); _version++; } #endregion #region Members implemented from IReadOnlyDictionary> /*====================================================================== ** Members implemented from IReadOnlyDictionary> ======================================================================*/ /// /// Determines if the given exists within this and has /// at least one associated with it. /// /// The to search the for /// true if the contains the requested ; /// otherwise false. /// must be non-null public bool ContainsKey(TKey key) { if (key == null) throw new ArgumentNullException(nameof(key)); // Since modification to the MultiValueDictionary is only allowed through its own API, we // can ensure that if a collection is in the internal dictionary then it must have at least one // associated TValue, or else it would have been removed whenever its final TValue was removed. return _dictionary.ContainsKey(key); } /// /// Gets each in this that /// has one or more associated . /// /// /// An containing each /// in this that has one or more associated /// . /// public IEnumerable Keys => _dictionary.Keys; /// /// Attempts to get the associated with the given /// and place it into . /// /// The of the element to retrieve /// /// When this method returns, contains the associated with the specified /// if it is found; otherwise contains the default value of . /// /// /// true if the contains an element with the specified /// ; otherwise, false. /// /// must be non-null public bool TryGetValue(TKey key, out IReadOnlyCollection value) { if (key == null) throw new ArgumentNullException(nameof(key)); var success = _dictionary.TryGetValue(key, out InnerCollectionView collection); value = collection; return success; } /// /// Gets an enumerable of from this , /// where each is the collection of every associated /// with a present in the . /// /// An IEnumerable of each in this /// public IEnumerable> Values => _dictionary.Values; /// /// Get every associated with the given . If /// is not found in this , will /// throw a . /// /// The of the elements to retrieve. /// must be non-null /// does not have any associated /// s in this . /// /// An containing every /// associated with . /// /// /// Note that the returned will change alongside any changes /// to the /// public IReadOnlyCollection this[TKey key] { get { if (key == null) throw new ArgumentNullException(nameof(key)); if (_dictionary.TryGetValue(key, out InnerCollectionView collection)) return collection; throw new KeyNotFoundException(); } } /// /// Returns the number of s with one or more associated /// in this . /// /// The number of s in this . public int Count => _dictionary.Count; /// /// Get an Enumerator over the - /// pairs in this . /// /// an Enumerator over the - /// pairs in this . public IEnumerator>> GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); #endregion /// /// The Enumerator class for a /// that iterates over - /// pairs. /// private class Enumerator : IEnumerator>> { private readonly MultiValueDictionary _multiValueDictionary; private readonly int _version; private Dictionary.Enumerator _enumerator; private EnumerationState _state; /// /// Constructor for the enumerator /// /// A MultiValueDictionary to iterate over internal Enumerator(MultiValueDictionary multiValueDictionary) { _multiValueDictionary = multiValueDictionary; _version = multiValueDictionary._version; _enumerator = multiValueDictionary._dictionary.GetEnumerator(); _state = EnumerationState.BeforeFirst; Current = default; } public KeyValuePair> Current { get; private set; } object IEnumerator.Current { get { switch (_state) { case EnumerationState.BeforeFirst: throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); case EnumerationState.AfterLast: throw new InvalidOperationException("Enumeration already finished."); default: return Current; } } } /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// The collection was modified after the enumerator was created. public bool MoveNext() { if (_version != _multiValueDictionary._version) throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); if (_enumerator.MoveNext()) { Current = new KeyValuePair>(_enumerator.Current.Key, _enumerator.Current.Value); _state = EnumerationState.During; return true; } Current = default; _state = EnumerationState.AfterLast; return false; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// The collection was modified after the enumerator was created. public void Reset() { if (_version != _multiValueDictionary._version) throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); _enumerator.Dispose(); _enumerator = _multiValueDictionary._dictionary.GetEnumerator(); Current = default; _state = EnumerationState.BeforeFirst; } /// /// Frees resources associated with this Enumerator /// public void Dispose() => _enumerator.Dispose(); private enum EnumerationState { BeforeFirst, During, AfterLast } } /// /// An inner class that functions as a view of an ICollection within a MultiValueDictionary /// private class InnerCollectionView : ICollection, IReadOnlyCollection { private readonly ICollection _collection; #region Private Concrete API /*====================================================================== ** Private Concrete API ======================================================================*/ public InnerCollectionView(TKey key, ICollection collection) { Key = key; _collection = collection; } public void AddValue(TValue item) => _collection.Add(item); public bool RemoveValue(TValue item) => _collection.Remove(item); #endregion #region Shared API /*====================================================================== ** Shared API ======================================================================*/ public bool Contains(TValue item) => _collection.Contains(item); public void CopyTo(TValue[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array)); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Non-negative number required."); if (arrayIndex > array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was out of range. Must be non-negative and less than the size of the collection."); if (array.Length - arrayIndex < _collection.Count) throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length.", nameof(arrayIndex)); _collection.CopyTo(array, arrayIndex); } public int Count => _collection.Count; public bool IsReadOnly => true; public IEnumerator GetEnumerator() => _collection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public TKey Key { get; } #endregion #region Public-Facing API /*====================================================================== ** Public-Facing API ======================================================================*/ void ICollection.Add(TValue item) => throw new NotSupportedException("The collection is read-only"); void ICollection.Clear() => throw new NotSupportedException("The collection is read-only"); bool ICollection.Remove(TValue item) => throw new NotSupportedException("The collection is read-only"); #endregion } } }