// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // From https://github.com/dotnet/corefxlab/blob/master/src/System.Collections.Generic.MultiValueDictionary/System/Collections/Generic/MultiValueDictionary.cs using System; using System.Collections; using System.Collections.Generic; namespace BTCPayServer { /// /// 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 an IReadOnlyDictionary<TKey,IReadOnlyCollection<TValue>t> /// where the is abstracted from the view of the programmer. /// /// For a read-only MultiValueDictionary. /// /// 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); 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("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("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(); multiValueDictionary.NewCollectionFactory = () => new TValueCollection(); return multiValueDictionary; } /// /// 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(capacity); multiValueDictionary.NewCollectionFactory = () => new TValueCollection(); return multiValueDictionary; } /// /// 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("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(comparer); multiValueDictionary.NewCollectionFactory = () => new TValueCollection(); return multiValueDictionary; } /// /// 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(capacity, comparer); multiValueDictionary.NewCollectionFactory = () => new TValueCollection(); return multiValueDictionary; } /// /// 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("enumerable"); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(); 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("enumerable"); if (new TValueCollection().IsReadOnly) throw new InvalidOperationException("Properties.Resources.Create_TValueCollectionReadOnly"); var multiValueDictionary = new MultiValueDictionary(comparer); multiValueDictionary.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(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(); multiValueDictionary.NewCollectionFactory = (Func>)(Delegate)collectionFactory; return multiValueDictionary; } /// /// 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(capacity); multiValueDictionary.NewCollectionFactory = (Func>)(Delegate)collectionFactory; return multiValueDictionary; } /// /// 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(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(comparer); multiValueDictionary.NewCollectionFactory = (Func>)(Delegate)collectionFactory; return multiValueDictionary; } /// /// 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("capacity", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(capacity, comparer); multiValueDictionary.NewCollectionFactory = (Func>)(Delegate)collectionFactory; return multiValueDictionary; } /// /// 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("enumerable"); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(); 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("enumerable"); if (collectionFactory().IsReadOnly) throw new InvalidOperationException(("Properties.Resources.Create_TValueCollectionReadOnly")); var multiValueDictionary = new MultiValueDictionary(comparer); multiValueDictionary.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("key"); InnerCollectionView collection; if (!dictionary.TryGetValue(key, out 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("key"); if (values == null) throw new ArgumentNullException("values"); InnerCollectionView collection; if (!dictionary.TryGetValue(key, out 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("key"); InnerCollectionView collection; if (dictionary.TryGetValue(key, out collection) && 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("key"); InnerCollectionView collection; if (dictionary.TryGetValue(key, out 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("key"); InnerCollectionView collection; return (dictionary.TryGetValue(key, out 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("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 { get { return 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("key"); InnerCollectionView collection; var success = dictionary.TryGetValue(key, out 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 { get { return 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("key"); InnerCollectionView collection; if (dictionary.TryGetValue(key, out collection)) return collection; else throw new KeyNotFoundException(); } } /// /// Returns the number of s with one or more associated /// in this . /// /// The number of s in this . public int Count { get { return dictionary.Count; } } /// /// Get an Enumerator over the - /// pairs in this . /// /// an Enumerator over the - /// pairs in this . public IEnumerator>> GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return 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 KeyValuePair> current; private Dictionary.Enumerator enumerator; private enum EnumerationState { BeforeFirst, During, AfterLast }; private EnumerationState state; /// /// Constructor for the enumerator /// /// A MultiValueDictionary to iterate over internal Enumerator(MultiValueDictionary multiValueDictionary) { this.multiValueDictionary = multiValueDictionary; this.version = multiValueDictionary.version; this.current = default(KeyValuePair>); this.enumerator = multiValueDictionary.dictionary.GetEnumerator(); this.state = EnumerationState.BeforeFirst; ; } public KeyValuePair> Current { get { return current; } } object IEnumerator.Current { get { switch (state) { case EnumerationState.BeforeFirst: throw new InvalidOperationException(("Properties.Resources.InvalidOperation_EnumNotStarted")); case EnumerationState.AfterLast: throw new InvalidOperationException(("Properties.Resources.InvalidOperation_EnumEnded")); 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("Properties.Resources.InvalidOperation_EnumFailedVersion"); } else if (enumerator.MoveNext()) { current = new KeyValuePair>(enumerator.Current.Key, enumerator.Current.Value); state = EnumerationState.During; return true; } else { current = default(KeyValuePair>); 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("Properties.Resources.InvalidOperation_EnumFailedVersion"); enumerator.Dispose(); enumerator = multiValueDictionary.dictionary.GetEnumerator(); current = default(KeyValuePair>); state = EnumerationState.BeforeFirst; } /// /// Frees resources associated with this Enumerator /// public void Dispose() { enumerator.Dispose(); } } /// /// An inner class that functions as a view of an ICollection within a MultiValueDictionary /// private class InnerCollectionView : ICollection, IReadOnlyCollection { private readonly TKey key; private readonly ICollection collection; #region Private Concrete API /*====================================================================== ** Private Concrete API ======================================================================*/ public InnerCollectionView(TKey key, ICollection collection) { this.key = key; this.collection = collection; } public void AddValue(TValue item) { collection.Add(item); } public bool RemoveValue(TValue item) { return collection.Remove(item); } #endregion #region Shared API /*====================================================================== ** Shared API ======================================================================*/ public bool Contains(TValue item) { return collection.Contains(item); } public void CopyTo(TValue[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex", "Properties.Resources.ArgumentOutOfRange_NeedNonNegNum"); if (arrayIndex > array.Length) throw new ArgumentOutOfRangeException("arrayIndex", "Properties.Resources.ArgumentOutOfRange_Index"); if (array.Length - arrayIndex < collection.Count) throw new ArgumentException("Properties.Resources.CopyTo_ArgumentsTooSmall", "arrayIndex"); collection.CopyTo(array, arrayIndex); } public int Count { get { return collection.Count; } } public bool IsReadOnly { get { return true; } } public IEnumerator GetEnumerator() { return collection.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public TKey Key { get { return key; } } #endregion #region Public-Facing API /*====================================================================== ** Public-Facing API ======================================================================*/ void ICollection.Add(TValue item) { throw new NotSupportedException("Properties.Resources.ReadOnly_Modification"); } void ICollection.Clear() { throw new NotSupportedException("Properties.Resources.ReadOnly_Modification"); } bool ICollection.Remove(TValue item) { throw new NotSupportedException("Properties.Resources.ReadOnly_Modification"); } #endregion } } public static class MultiValueDictionaryExtensions { public static MultiValueDictionary ToMultiValueDictionary(this IEnumerable collection, Func keySelector, Func valueSelector) { var dictionary = new MultiValueDictionary(); foreach (var item in collection) { dictionary.Add(keySelector(item), valueSelector(item)); } return dictionary; } } }