// 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
}
}
}