/Utilities/Collections/MultiDictionary.cs
# · C# · 2291 lines · 1653 code · 202 blank · 436 comment · 89 complexity · d951e5144ba674242e5bfff8b84ef410 MD5 · raw file
- using System;
- using System.Collections.Generic;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Collections
- {
- /// <summary>
- /// <para>The MultiDictionary class that associates values with a key.
- /// Unlike an Dictionary, each key can have multiple values associated with
- /// it. When indexing an MultiDictionary, instead of a single value
- /// associated with a key, you retrieve an enumeration of values.</para>
- /// <para>When constructed, you can chose to allow the same value to be
- /// associated with a key multiple times, or only one time. </para>
- /// </summary>
- /// <typeparam name="TKey">Key type</typeparam>
- /// <typeparam name="TValue">Value type</typeparam>
- /// <seealso cref="Dictionary{TKey,TValue}"/>
- public class MultiDictionary<TKey, TValue>
- : MultiDictionaryBase<TKey, TValue>,
- ICloneable<MultiDictionary<TKey, TValue>>
- {
- #region KeyAndValues Struct
- /// <summary>
- /// A structure to hold the key and the values associated with the key.
- /// The number of values must always be 1 or greater in a version that is
- /// stored, but can be zero in a dummy version used only for lookups.
- /// </summary>
- private struct KeyAndValues
- {
- #region Copy (Static)
- /// <summary>
- /// Make a copy of a KeyAndValues, copying the array.
- /// </summary>
- /// <param name="x">KeyAndValues to copy.</param>
- /// <returns>A copied version.</returns>
- public static KeyAndValues Copy(KeyAndValues x)
- {
- KeyAndValues result;
-
- result.Key = x.Key;
- result.Count = x.Count;
-
- if (x.Values != null)
- {
- result.Values = (TValue[])x.Values.Clone();
- }
- else
- {
- result.Values = null;
- }
-
- return result;
- }
- #endregion
-
- #region Key (Public)
- /// <summary>
- /// The key.
- /// </summary>
- public TKey Key;
- #endregion
-
- #region Count (Public)
- /// <summary>
- /// The number of values. Always at least 1 except in a dummy version
- /// for lookups.
- /// </summary>
- public int Count;
- #endregion
-
- #region Values (Public)
- /// <summary>
- /// An array of values.
- /// </summary>
- public TValue[] Values;
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create a dummy KeyAndValues with just the key, for lookups.
- /// </summary>
- /// <param name="key">The key to use.</param>
- public KeyAndValues(TKey key)
- {
- Key = key;
- Count = 0;
- Values = null;
- }
- #endregion
- }
- #endregion
-
- #region KeyAndValuesEqualityComparer Class
- /// <summary>
- /// This class implements IEqualityComparer for KeysAndValues, allowing
- /// them to be compared by their keys. An IEqualityComparer on keys is
- /// required.
- /// </summary>
- private class KeyAndValuesEqualityComparer : IEqualityComparer<KeyAndValues>
- {
- #region Private
-
- #region keyEqualityComparer (Private)
- /// <summary>
- /// Key equality comparer
- /// </summary>
- private readonly IEqualityComparer<TKey> keyEqualityComparer;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create key and values equality comparer
- /// </summary>
- /// <param name="keyEqualityComparer">Key equality comparer</param>
- public KeyAndValuesEqualityComparer(
- IEqualityComparer<TKey> keyEqualityComparer)
- {
- this.keyEqualityComparer = keyEqualityComparer;
- }
- #endregion
-
- #region IEqualityComparer<MultiDictionary<TKey,TValue>.KeyAndValues> Members
- /// <summary>
- /// Equals
- /// </summary>
- /// <param name="x">X value</param>
- /// <param name="y">Y value</param>
- /// <returns>True if x and y match.</returns>
- public bool Equals(KeyAndValues x, KeyAndValues y)
- {
- return keyEqualityComparer.Equals(x.Key, y.Key);
- }
-
- /// <summary>
- /// Get hash code
- /// </summary>
- /// <param name="obj">Object to generate hash code for</param>
- /// <returns>Hash code</returns>
- public int GetHashCode(KeyAndValues obj)
- {
- return obj.Key == null
- ? 0x1786E23C
- : keyEqualityComparer.GetHashCode(obj.Key);
- }
- #endregion
- }
- #endregion
-
- #region GetHashCode (Static)
- /// <summary>
- /// Gets the hash code for an object using a comparer. Correctly handles
- /// null.
- /// </summary>
- /// <param name="item">Item to get hash code for. Can be null.</param>
- /// <param name="equalityComparer">The comparer to use.</param>
- /// <returns>The hash code for the item.</returns>
- public static int GetHashCode<T>(T item,
- IEqualityComparer<T> equalityComparer)
- {
- if (item == null)
- {
- return 0x1786E23C;
- }
- else
- {
- return equalityComparer.GetHashCode(item);
- }
- }
- #endregion
-
- #region IsCloneableType (Static)
- /// <summary>
- /// Determine if a type is cloneable: either a value type or implementing
- /// ICloneable.
- /// </summary>
- /// <param name="type">Type to check.</param>
- /// <param name="isValue">Returns if the type is a value type, and does not
- /// implement ICloneable.</param>
- /// <returns>True if the type is cloneable.</returns>
- public static bool IsCloneableType(Type type, out bool isValue)
- {
- isValue = false;
-
- if (typeof(ICloneable<MultiDictionary<TKey, TValue>>).
- IsAssignableFrom(type))
- {
- return true;
- }
- else if (type.IsValueType)
- {
- isValue = true;
- return true;
- }
- else
- {
- return false;
- }
- }
- #endregion
-
- #region KeyComparer (Public)
- /// <summary>
- /// Returns the IEqualityComparer<T> used to compare keys in this
- /// dictionary.
- /// </summary>
- /// <value>If the dictionary was created using a comparer, that comparer
- /// is returned. Otherwise the default comparer for TKey
- /// (EqualityComparer<TKey>.Default) is returned.</value>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- public IEqualityComparer<TKey> KeyComparer
- {
- get
- {
- return keyEqualityComparer;
- }
- }
- #endregion
-
- #region ValueComparer (Public)
- /// <summary>
- /// Returns the IEqualityComparer<T> used to compare values in this
- /// dictionary.
- /// </summary>
- /// <value>If the dictionary was created using a comparer, that comparer
- /// is returned. Otherwise the default comparer for TValue
- /// (EqualityComparer<TValue>.Default) is returned.</value>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- public IEqualityComparer<TValue> ValueComparer
- {
- get
- {
- return valueEqualityComparer;
- }
- }
- #endregion
-
- #region Count (Public)
- /// <summary>
- /// Gets the number of key-value pairs in the dictionary. Each value
- /// associated with a given key is counted. If duplicate values are
- /// permitted, each duplicate value is included in the count.
- /// </summary>
- /// <value>The number of key-value pairs in the dictionary.</value>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- public override sealed int Count
- {
- get
- {
- return hash.ElementCount;
- }
- }
- #endregion
-
- #region Private
-
- #region keyEqualityComparer (Private)
- /// <summary>
- /// The comparer for comparing keys
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly IEqualityComparer<TKey> keyEqualityComparer;
- #endregion
-
- #region valueEqualityComparer (Private)
- /// <summary>
- /// The comparer for comparing values;
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly IEqualityComparer<TValue> valueEqualityComparer;
- #endregion
-
- #region equalityComparer (Private)
- /// <summary>
- /// The comparer for compaing keys and values.
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly IEqualityComparer<KeyAndValues> equalityComparer;
- #endregion
-
- #region allowDuplicateValues (Private)
- /// <summary>
- /// Whether duplicate values for the same key are allowed.
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private readonly bool allowDuplicateValues;
- #endregion
-
- #region hash (Private)
- /// <summary>
- /// The hash that holds the keys and values.
- /// </summary>
- /// <typeparam name="TKey">TKey</typeparam>
- /// <typeparam name="TValue">TValue</typeparam>
- private Hash<KeyAndValues> hash;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create a new MultiDictionary. The default ordering of keys and values
- /// are used. If duplicate values are allowed, multiple copies of the same
- /// value can be associated with the same key. For example, the key "foo"
- /// could have "a", "a", and "b" associated with it. If duplicate values
- /// are not allowed, only one copies of a given value can be associated
- /// with the same key, although different keys can have the same value.
- /// For example, the key "foo" could have "a" and "b" associated with it,
- /// which key "bar" has values "b" and "c" associated with it.
- /// </summary>
- /// <remarks>The default ordering of keys and values will be used, as
- /// defined by TKey and TValue's implementation of IComparable<T>
- /// (or IComparable if IComparable<T> is not implemented). If a
- /// different ordering should be used, other constructors allow a custom
- /// Comparer or IComparer to be passed to changed the ordering.</remarks>
- /// <param name="allowDuplicateValues">Can the same value be associated
- /// with a key multiple times?</param>
- /// <exception cref="InvalidOperationException">TKey or TValue does not
- /// implement either IComparable<T> or IComparable.</exception>
- public MultiDictionary(bool allowDuplicateValues)
- : this(allowDuplicateValues,
- EqualityComparer<TKey>.Default,
- EqualityComparer<TValue>.Default)
- {
- }
-
- /// <summary>
- /// Create a new MultiDictionary. If duplicate values are allowed,
- /// multiple copies of the same value can be associated with the same key.
- /// For example, the key "foo" could have "a", "a", and "b" associated
- /// with it. If duplicate values are not allowed, only one copies of a
- /// given value can be associated with the same key, although different
- /// keys can have the same value. For example, the key "foo" could have
- /// "a" and "b" associated with it, which key "bar" has values "b" and "c"
- /// associated with it.
- /// </summary>
- /// <param name="allowDuplicateValues">
- /// Can the same value be associated with a key multiple times?</param>
- /// <param name="keyEqualityComparer">
- /// An IEqualityComparer<TKey> instance that will be used to compare
- /// keys.</param>
- /// <exception cref="InvalidOperationException">TValue does not implement
- /// either IComparable<TValue> or IComparable.</exception>
- public MultiDictionary(bool allowDuplicateValues,
- IEqualityComparer<TKey> keyEqualityComparer)
- : this(allowDuplicateValues, keyEqualityComparer,
- EqualityComparer<TValue>.Default)
- {
- }
-
- /// <summary>
- /// Create a new MultiDictionary. If duplicate values are allowed,
- /// multiple copies of the same value can be associated with the same key.
- /// For example, the key "foo" could have "a", "a", and "b" associated
- /// with it. If duplicate values are not allowed, only one copies of a
- /// given value can be associated with the same key, although different
- /// keys can have the same value. For example, the key "foo" could have
- /// "a" and "b" associated with it, which key "bar" has values "b" and "c"
- /// associated with it.
- /// </summary>
- /// <param name="allowDuplicateValues">Can the same value be associated
- /// with a key multiple times?</param>
- /// <param name="keyEqualityComparer">An IEqualityComparer<TKey>
- /// instance that will be used to compare keys.</param>
- /// <param name="valueEqualityComparer">An IEqualityComparer<TValue>
- /// instance that will be used to compare values.</param>
- public MultiDictionary(bool allowDuplicateValues,
- IEqualityComparer<TKey> keyEqualityComparer,
- IEqualityComparer<TValue> valueEqualityComparer)
- {
- if (keyEqualityComparer == null)
- {
- throw new ArgumentNullException("keyEqualityComparer");
- }
- if (valueEqualityComparer == null)
- {
- throw new ArgumentNullException("valueEqualityComparer");
- }
-
- this.allowDuplicateValues = allowDuplicateValues;
- this.keyEqualityComparer = keyEqualityComparer;
- this.valueEqualityComparer = valueEqualityComparer;
- equalityComparer =
- new KeyAndValuesEqualityComparer(keyEqualityComparer);
- hash = new Hash<KeyAndValues>(equalityComparer);
- }
-
- /// <summary>
- /// Create a new MultiDictionary. Private constructor, for use by Clone().
- /// </summary>
- /// <param name="allowDuplicateValues">Allow duplicate values</param>
- /// <param name="equalityComparer">Equality comparer</param>
- /// <param name="hash">hash</param>
- /// <param name="keyEqualityComparer">Key equality comparer</param>
- /// <param name="valueEqualityComparer">Value equality comparer</param>
- private MultiDictionary(bool allowDuplicateValues,
- IEqualityComparer<TKey> keyEqualityComparer,
- IEqualityComparer<TValue> valueEqualityComparer,
- IEqualityComparer<KeyAndValues> equalityComparer,
- Hash<KeyAndValues> hash)
- {
- if (keyEqualityComparer == null)
- {
- throw new ArgumentNullException("keyEqualityComparer");
- }
- if (valueEqualityComparer == null)
- {
- throw new ArgumentNullException("valueEqualityComparer");
- }
-
- this.allowDuplicateValues = allowDuplicateValues;
- this.keyEqualityComparer = keyEqualityComparer;
- this.valueEqualityComparer = valueEqualityComparer;
- this.equalityComparer = equalityComparer;
- this.hash = hash;
- }
- #endregion
-
- #region ICloneable<MultiDictionary<TKey,TValue>> Members
- /// <summary>
- /// Makes a shallow clone of this dictionary; i.e., if keys or values of
- /// the dictionary are reference types, then they are not cloned. If TKey
- /// or TValue is a value type, then each element is copied as if by simple
- /// assignment.
- /// </summary>
- /// <remarks>Cloning the dictionary takes time O(N), where N is the number
- /// of key-value pairs in the dictionary.</remarks>
- /// <returns>The cloned dictionary.</returns>
- public MultiDictionary<TKey, TValue> Clone()
- {
- return new MultiDictionary<TKey, TValue>(
- allowDuplicateValues, keyEqualityComparer,
- valueEqualityComparer, equalityComparer,
- hash.Clone(KeyAndValues.Copy));
- }
- #endregion
-
- #region Add (Public)
- /// <summary>
- /// <para>Adds a new value to be associated with a key. If duplicate
- /// values are permitted, this method always adds a new key-value pair
- /// to the dictionary.</para>
- /// <para>If duplicate values are not permitted, and <paramref name="key"/>
- /// already has a value equal to <paramref name="value"/> associated with
- /// it, then that value is replaced with <paramref name="value"/>,
- /// and the number of values associate with <paramref name="key"/> is
- /// unchanged.</para>
- /// </summary>
- /// <param name="key">Key</param>
- /// <param name="value">Value</param>
- public override sealed void Add(TKey key, TValue value)
- {
- KeyAndValues keyValues = new KeyAndValues(key);
- KeyAndValues existing;
-
- if (hash.Find(keyValues, false, out existing))
- {
- // There already is an item in the hash table equal to this key.
- // Add the new value, taking into account duplicates if needed.
- int existingCount = existing.Count;
- if (!allowDuplicateValues)
- {
- int valueHash = GetHashCode(value, valueEqualityComparer);
- for (int i = 0; i < existingCount; ++i)
- {
- if (GetHashCode(existing.Values[i], valueEqualityComparer) ==
- valueHash &&
- valueEqualityComparer.Equals(existing.Values[i], value))
- {
- // Found an equal existing value. Replace it and we're done.
- existing.Values[i] = value;
- return;
- }
- }
- }
-
- // Add a new value to an existing key.
- if (existingCount == existing.Values.Length)
- {
- // Grow the array to make room.
- TValue[] newValues = new TValue[existingCount * 2];
- Array.Copy(existing.Values, newValues, existingCount);
- existing.Values = newValues;
- }
- existing.Values[existingCount] = value;
- existing.Count = existingCount + 1;
-
- // Update the hash table.
- hash.Find(existing, true, out keyValues);
- return;
- }
- else
- {
- // No item with this key. Add it.
- keyValues.Count = 1;
- keyValues.Values = new TValue[1]
- {
- value
- };
- hash.Insert(keyValues, true, out existing);
- return;
- }
- }
- #endregion
-
- #region Remove (Public)
- /// <summary>
- /// Removes a given value from the values associated with a key. If the
- /// last value is removed from a key, the key is removed also.
- /// </summary>
- /// <param name="key">A key to remove a value from.</param>
- /// <param name="value">The value to remove.</param>
- /// <returns>True if <paramref name="value"/> was associated with
- /// <paramref name="key"/> (and was therefore removed). False if
- /// <paramref name="value"/> was not associated with
- /// <paramref name="key"/>.</returns>
- public override sealed bool Remove(TKey key, TValue value)
- {
- KeyAndValues keyValues = new KeyAndValues(key);
- KeyAndValues existing;
-
- if (hash.Find(keyValues, false, out existing))
- {
- // There is an item in the hash table equal to this key.
- // Find the value.
- int existingCount = existing.Count;
- int valueHash = GetHashCode(value, valueEqualityComparer);
- int indexFound = -1;
- for (int i = 0; i < existingCount; ++i)
- {
- if (GetHashCode(existing.Values[i], valueEqualityComparer) ==
- valueHash &&
- valueEqualityComparer.Equals(existing.Values[i], value))
- {
- // Found an equal existing value
- indexFound = i;
- }
- }
-
- if (existingCount == 1)
- {
- // Removing the last value. Remove the key.
- hash.Delete(existing, out keyValues);
- return true;
- }
- else if (indexFound >= 0)
- {
- // Found a value. Remove it.
- if (indexFound < existingCount - 1)
- {
- Array.Copy(existing.Values, indexFound + 1, existing.Values,
- indexFound, existingCount - indexFound - 1);
- }
- existing.Count = existingCount - 1;
-
- // Update the hash.
- hash.Find(existing, true, out keyValues);
- return true;
- }
- else
- {
- // Value was not found.
- return false;
- }
- }
- else
- {
- return false; // key not found.
- }
- }
-
- /// <summary>
- /// Removes a key and all associated values from the dictionary. If the
- /// key is not present in the dictionary, it is unchanged and false is
- /// returned.
- /// </summary>
- /// <param name="key">The key to remove.</param>
- /// <returns>True if the key was present and was removed. Returns
- /// false if the key was not present.</returns>
- public override sealed bool Remove(TKey key)
- {
- KeyAndValues dummy;
- return hash.Delete(new KeyAndValues(key), out dummy);
- }
- #endregion
-
- #region Clear (Public)
- /// <summary>
- /// Removes all keys and values from the dictionary.
- /// </summary>
- public override sealed void Clear()
- {
- hash.StopEnumerations(); // Invalidate any enumerations.
-
- // The simplest and fastest way is simply to throw away the old hash
- // and create a new one.
- hash = new Hash<KeyAndValues>(equalityComparer);
- }
- #endregion
-
- #region Contains (Public)
- /// <summary>
- /// Checks to see if <paramref name="value"/> is associated with
- /// <paramref name="key"/> in the dictionary.
- /// </summary>
- /// <param name="key">The key to check.</param>
- /// <param name="value">The value to check.</param>
- /// <returns>True if <paramref name="value"/> is associated with
- /// <paramref name="key"/>.</returns>
- public override sealed bool Contains(TKey key, TValue value)
- {
- KeyAndValues find = new KeyAndValues(key);
- KeyAndValues item;
- if (hash.Find(find, false, out item))
- {
- int existingCount = item.Count;
- int valueHash = GetHashCode(value, valueEqualityComparer);
- for (int i = 0; i < existingCount; ++i)
- {
- if (GetHashCode(item.Values[i], valueEqualityComparer) ==
- valueHash &&
- valueEqualityComparer.Equals(item.Values[i], value))
- {
- // Found an equal existing value.
- return true;
- }
- }
- }
-
- return false;
- }
- #endregion
-
- #region ContainsKey (Public)
- /// <summary>
- /// Checks to see if the key is present in the dictionary and has
- /// at least one value associated with it.
- /// </summary>
- /// <param name="key">The key to check.</param>
- /// <returns>True if <paramref name="key"/> is present and has at least
- /// one value associated with it. Returns false otherwise.</returns>
- public override sealed bool ContainsKey(TKey key)
- {
- KeyAndValues find = new KeyAndValues(key);
- KeyAndValues temp;
- return hash.Find(find, false, out temp);
- }
- #endregion
-
- #region CloneContents (Public)
- /// <summary>
- /// Makes a deep clone of this dictionary. A new dictionary is created
- /// with a clone of each entry of this dictionary, by calling
- /// ICloneable.Clone on each element. If TKey or TValue is a value type,
- /// then each element is copied as if by simple assignment.
- /// </summary>
- /// <remarks><para>If TKey or TValue is a reference type, it must implement
- /// ICloneable. Otherwise, an InvalidOperationException is thrown.</para>
- /// <para>Cloning the dictionary takes time O(N log N), where N is the
- /// number of key-value pairs in the dictionary.</para></remarks>
- /// <returns>The cloned dictionary.</returns>
- /// <exception cref="InvalidOperationException">TKey or TValue is a
- /// reference type that does not implement ICloneable.</exception>
- public MultiDictionary<TKey, TValue> CloneContents()
- {
- bool keyIsValueType, valueIsValueType;
-
- // Make sure that TKey and TValue can be cloned.
- if (!IsCloneableType(typeof(TKey), out keyIsValueType))
- {
- NonCloneableType(typeof(TKey));
- }
-
- if (!IsCloneableType(typeof(TValue), out valueIsValueType))
- {
- NonCloneableType(typeof(TValue));
- }
-
- // It's tempting to do a more efficient cloning, utilizing the
- // hash.Clone() method. However, we can't know that
- // the cloned version of the key has the same hash value.
- MultiDictionary<TKey, TValue> newDict =
- new MultiDictionary<TKey, TValue>(
- allowDuplicateValues, keyEqualityComparer, valueEqualityComparer);
-
- foreach (KeyAndValues item in hash)
- {
- // Clone the key and values parts. Value types can be cloned
- // by just copying them, otherwise, ICloneable is used.
- TKey keyClone;
- TValue[] valuesClone;
-
- if (keyIsValueType)
- {
- keyClone = item.Key;
- }
- else
- {
- if (item.Key == null)
- {
- // Really null, because we know TKey isn't a value type.
- keyClone = default(TKey);
- }
- else
- {
- keyClone = ((ICloneable<TKey>)item.Key).Clone();
- }
- }
-
- valuesClone = new TValue[item.Count];
- if (valueIsValueType)
- {
- Array.Copy(item.Values, valuesClone, item.Count);
- }
- else
- {
- for (int i = 0; i < item.Count; ++i)
- {
- if (item.Values[i] == null)
- {
- // Really null, because we know TKey isn't a value type.
- valuesClone[i] = default(TValue);
- }
- else
- {
- valuesClone[i] = ((ICloneable<TValue>)item.Values[i]).Clone();
- }
- }
- }
-
- newDict.AddMany(keyClone, valuesClone);
- }
-
- return newDict;
- }
- #endregion
-
- #region Methods (Private)
-
- #region EqualValues
- /// <summary>
- /// Determine if two values are equal.
- /// </summary>
- /// <param name="value1">First value to compare.</param>
- /// <param name="value2">Second value to compare.</param>
- /// <returns>True if the values are equal.</returns>
- protected override sealed bool EqualValues(TValue value1, TValue value2)
- {
- return valueEqualityComparer.Equals(value1, value2);
- }
- #endregion
-
- #region EnumerateKeys
- /// <summary>
- /// Enumerate all the keys in the dictionary.
- /// </summary>
- /// <returns>
- /// An IEnumerator<TKey> that enumerates all of the keys in the
- /// dictionary that have at least one value associated with them.
- /// </returns>
- protected override sealed IEnumerator<TKey> EnumerateKeys()
- {
- foreach (KeyAndValues item in hash)
- {
- yield return item.Key;
- }
- }
- #endregion
-
- #region EnumerateValues
- /// <summary>
- /// Enumerate the values in the a KeyAndValues structure. Can't return
- /// the array directly because:
- /// a) The array might be larger than the count.
- /// b) We can't allow clients to down-cast to the array and modify it.
- /// c) We have to abort enumeration if the hash changes.
- /// </summary>
- /// <param name="keyAndValues">Item with the values to enumerate..</param>
- /// <returns>An enumerable that enumerates the items in the KeyAndValues
- /// structure.</returns>
- private IEnumerator<TValue> EnumerateValues(KeyAndValues keyAndValues)
- {
- int count = keyAndValues.Count;
- int stamp = hash.GetEnumerationStamp();
-
- for (int i = 0; i < count; ++i)
- {
- yield return keyAndValues.Values[i];
- hash.CheckEnumerationStamp(stamp);
- }
- }
- #endregion
-
- #region TryEnumerateValuesForKey
- /// <summary>
- /// Determines if this dictionary contains a key equal to
- /// <paramref name="key"/>. If so, all the values associated with that key
- /// are returned through the values parameter.
- /// </summary>
- /// <param name="key">The key to search for.</param>
- /// <param name="values">Returns all values associated with key, if true
- /// was returned.</param>
- /// <returns>True if the dictionary contains key. False if the dictionary
- /// does not contain key.</returns>
- protected override sealed bool TryEnumerateValuesForKey(
- TKey key, out IEnumerator<TValue> values)
- {
- KeyAndValues find = new KeyAndValues(key);
- KeyAndValues item;
- if (hash.Find(find, false, out item))
- {
- values = EnumerateValues(item);
- return true;
- }
- else
- {
- values = null;
- return false;
- }
- }
- #endregion
-
- #region CountValues
- /// <summary>
- /// Gets the number of values associated with a given key.
- /// </summary>
- /// <param name="key">The key to count values of.</param>
- /// <returns>The number of values associated with <paramref name="key"/>.
- /// If <paramref name="key"/> is not present in the dictionary, zero is
- /// returned.</returns>
- protected override sealed int CountValues(TKey key)
- {
- KeyAndValues find = new KeyAndValues(key);
- KeyAndValues item;
- if (hash.Find(find, false, out item))
- {
- return item.Count;
- }
- else
- {
- return 0;
- }
- }
- #endregion
-
- #region NonCloneableType
- /// <summary>
- /// Throw an InvalidOperationException indicating that this type is not
- /// cloneable.
- /// </summary>
- /// <param name="t">Type to test.</param>
- private void NonCloneableType(Type t)
- {
- throw new InvalidOperationException(
- "Type " + t.FullName + " does not implement ICloneable.");
- }
- #endregion
-
- #endregion
- }
-
- /// <summary>
- /// Multi dictionary tests, needs to be an extra class because
- /// MultiDictionary is generic.
- /// </summary>
- internal class MultiDictionaryTests
- {
- #region Helpers
-
- #region CheckMultiDictionaryContents
- /// <summary>
- /// Check the contents of a Multi-Dictionary non-destructively.
- /// Keys and Values must be in order.
- /// </summary>
- /// <param name="dict">Dictionary</param>
- /// <param name="keys">Keys</param>
- /// <param name="values">Values</param>
- /// <param name="nonKey">Non key</param>
- /// <param name="nonValue">Non value</param>
- internal static void CheckMultiDictionaryContents<TKey, TValue>(
- MultiDictionary<TKey, TValue> dict, TKey[] keys, TValue[][] values,
- TKey nonKey, TValue nonValue)
- {
- //Optimize performance of this test helper method, it is still to slow
- int iKey, iValue;
- ICollection<TValue> getValues;
-
- // Check Count.
- Assert.Equal(keys.Length, dict.Count);
-
- // Check indexer, ContainsKey, Contains, TryGetValue for each key.
- for (iKey = 0; iKey < keys.Length; ++iKey)
- {
- Assert.True(dict.ContainsKey(keys[iKey]));
- Assert.True(dict.Contains(
- new KeyValuePair<TKey, ICollection<TValue>>(
- keys[iKey], values[iKey])));
-
- bool b = ((IDictionary<TKey, ICollection<TValue>>)dict).TryGetValue(
- keys[iKey], out getValues);
- Assert.True(b);
- iValue = 0;
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- getValues, values[iKey]);
-
- iValue = 0;
- foreach (TValue val in values[iKey])
- {
- Assert.True(dict.Contains(keys[iKey], val));
- ++iValue;
- } // foreach
- Assert.True(iValue == values[iKey].Length);
-
- iValue = 0;
- getValues = dict[keys[iKey]];
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- getValues, values[iKey]);
- } // for (iKey)
-
- // Check Keys collection.
- CollectionBaseTests.CompareCollections(
- dict.Keys, keys, false);
-
- // Check Values collection
- int a = 0;
- TValue[] vals = new TValue[dict.Values.Count];
- for (iKey = 0; iKey < keys.Length; ++iKey)
- {
- for (iValue = 0; iValue < values[iKey].Length; ++iValue)
- {
- vals[a++] = values[iKey][iValue];
- } // for (iValue)
- } // for (iKey)
- Assert.Equal(dict.Values.Count, a);
- CollectionBaseTests.CompareCollections(
- dict.Values, vals, false);
-
- // Check KeyValuePairs collection.
- a = 0;
- KeyValuePair<TKey, TValue>[] pairs =
- new KeyValuePair<TKey, TValue>[dict.Values.Count];
- for (iKey = 0; iKey < keys.Length; ++iKey)
- {
- for (iValue = 0; iValue < values[iKey].Length; ++iValue)
- {
- pairs[a++] = new KeyValuePair<TKey, TValue>(
- keys[iKey], values[iKey][iValue]);
- } // for (iValue)
- } // for (iKey)
- CollectionBaseTests.CompareCollections(
- dict.KeyValuePairs, pairs, false);
-
- // Tests Contains, ContainsKey, TryGetValue for wrong values.
- Assert.False(dict.ContainsKey(nonKey));
- Assert.False(((IDictionary<TKey, ICollection<TValue>>)dict).
- TryGetValue(nonKey, out getValues));
- for (iKey = 0; iKey < keys.Length; ++iKey)
- {
- Assert.False(dict.Contains(keys[iKey], nonValue));
- Assert.False(dict.Contains(
- new KeyValuePair<TKey, ICollection<TValue>>(
- keys[iKey], new TValue[1] { nonValue })));
- }
- }
- #endregion
-
- #region FirstLetterComparer
- /// <summary>
- /// First letter comparer
- /// </summary>
- private class FirstLetterComparer : IEqualityComparer<string>
- {
- #region IEqualityComparer<string> Members
- /// <summary>
- /// Equals
- /// </summary>
- /// <param name="x">X value</param>
- /// <param name="y">Y value</param>
- /// <returns>True if both x and y match</returns>
- public bool Equals(string x, string y)
- {
- if (x == null)
- {
- return y == null;
- }
- else if (x.Length == 0)
- {
- return (y != null && y.Length == 0);
- }
- else
- {
- if (y == null ||
- y.Length == 0)
- {
- return false;
- }
- else
- {
- return x[0] == y[0];
- }
- }
- }
-
- /// <summary>
- /// Get hash code
- /// </summary>
- /// <param name="obj">Object</param>
- /// <returns>Int</returns>
- public int GetHashCode(string obj)
- {
- if (obj == null)
- {
- return 0x12383;
- }
- else if (obj.Length == 0)
- {
- return 17;
- }
- else
- {
- return obj[0].GetHashCode();
- }
- }
- #endregion
- }
- #endregion
-
- #region CompareClones
- /// <summary>
- /// Compare clones
- /// </summary>
- /// <param name="d1">D 1</param>
- /// <param name="d2">D 2</param>
- private void CompareClones<K, V>(
- MultiDictionary<K, V> d1, MultiDictionary<K, V> d2)
- {
- IEnumerable<KeyValuePair<K, V>> e1 = d1.KeyValuePairs;
- IEnumerable<KeyValuePair<K, V>> e2 = d2.KeyValuePairs;
- KeyValuePair<K, V>[]
- pairs1 = ArrayHelper.ToArray(e1),
- pairs2 = ArrayHelper.ToArray(e2);
- bool[] found = new bool[pairs2.Length];
-
- // Check that the arrays are equal, but not reference equals
- // (e.g., have been cloned).
- Assert.True(pairs1.Length == pairs2.Length);
- foreach (KeyValuePair<K, V> p1 in pairs1)
- {
- bool f = false;
- for (int i = 0; i < pairs2.Length; ++i)
- {
- if (!found[i] &&
- Equals(p1.Key, pairs2[i].Key) &&
- Equals(p1.Value, pairs2[i].Value))
- {
- found[i] = true;
- f = true;
- Assert.True(p1.Key == null ||
- !ReferenceEquals(p1.Key, pairs2[i].Key));
- Assert.True(p1.Value == null ||
- !ReferenceEquals(p1.Value, pairs2[i].Value));
- break;
- }
- }
- Assert.True(f);
- }
- }
- #endregion
-
- #endregion
-
- #region Clone (LongRunning)
- /// <summary>
- /// Clone. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Clone()
- {
- IEqualityComparer<string> firstLetterComparer =
- new FirstLetterComparer();
-
- MultiDictionary<string, string> dict1 =
- new MultiDictionary<string, string>(false,
- StringComparer.InvariantCultureIgnoreCase,
- firstLetterComparer);
-
- dict1.Add("qubert", "dinosaur");
- dict1.Add("Hello", "AAA");
- dict1.Add("Hi", "aaa");
- dict1.Add("Qubert", "hello");
- dict1.Add("queztel", "hello");
- dict1.Add("Alpha", "omega");
- dict1.Add("alpha", "oz");
- dict1.Add("qubert", "hippy");
-
- MultiDictionary<string, string> dict2 = dict1.Clone();
-
- Assert.True(dict1 != dict2);
-
- dict2.Add("qubert", "hoover");
- dict2.Remove("queztel");
- dict2.Add("hello", "banana");
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.KeyValuePairs, new[]
- {
- new KeyValuePair<string, string>("Alpha", "oz"),
- new KeyValuePair<string, string>("Hello", "AAA"),
- new KeyValuePair<string, string>("Hi", "aaa"),
- new KeyValuePair<string, string>("qubert", "hippy"),
- new KeyValuePair<string, string>("qubert", "dinosaur"),
- new KeyValuePair<string, string>("queztel", "hello")
- });
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict2.KeyValuePairs, new[]
- {
- new KeyValuePair<string, string>("Alpha", "oz"),
- new KeyValuePair<string, string>("Hello", "banana"),
- new KeyValuePair<string, string>("Hello", "AAA"),
- new KeyValuePair<string, string>("Hi", "aaa"),
- new KeyValuePair<string, string>("qubert", "hoover"),
- new KeyValuePair<string, string>("qubert", "dinosaur")
- });
-
- dict2 = dict1.Clone();
-
- Assert.True(dict1 != dict2);
-
- dict2.Add("qubert", "hoover");
- dict2.Remove("queztel");
- dict2.Add("hello", "banana");
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict2.KeyValuePairs, new[]
- {
- new KeyValuePair<string, string>("Alpha", "oz"),
- new KeyValuePair<string, string>("Hello", "banana"),
- new KeyValuePair<string, string>("Hello", "AAA"),
- new KeyValuePair<string, string>("Hi", "aaa"),
- new KeyValuePair<string, string>("qubert", "hoover"),
- new KeyValuePair<string, string>("qubert", "dinosaur")
- });
-
- MultiDictionary<string, int> dict4 =
- new MultiDictionary<string, int>(true);
- MultiDictionary<string, int> dict5;
- dict5 = dict4.Clone();
- Assert.False(dict4 == dict5);
- Assert.True(dict4.Count == 0 && dict5.Count == 0);
- dict4.Add("hello", 1);
- Assert.True(dict4.Count == 1 && dict5.Count == 0);
- dict5.Add("hi", 7);
- dict4.Clear();
- Assert.True(dict4.Count == 0 && dict5.Count == 1);
- }
- #endregion
-
- #region TestAddElements (LongRunning)
- /// <summary>
- /// Test add elements. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void TestAddElements()
- {
- // Test without duplicate values.
- MultiDictionary<string, double> dict1 =
- new MultiDictionary<string, double>(false);
-
- dict1.Add("foo", 3.5);
- dict1.Add("foo", -1.2);
- dict1.Add(null, 11.1);
- dict1.Add("foo", 8.8);
- dict1.Add(null, 11.1);
- dict1.Add("bar", 9.8);
- dict1.Add("foo", 8.8);
- dict1.Add("gib", 7.1);
- dict1.Add("S", -9);
- dict1.Add(null, 5.5);
-
- Assert.Equal("3.5, -1.2, 8.8",
- dict1["foo"].Write());
- Assert.Equal("7.1",
- dict1["gib"].Write());
- Assert.Equal("11.1, 5.5",
- dict1[null].Write());
-
- // Test with duplicate values.
- dict1 = new MultiDictionary<string, double>(true);
-
- dict1.Add("foo", 3.5);
- dict1.Add("foo", -1.2);
- dict1.Add(null, 11.1);
- dict1.Add("foo", 8.8);
- dict1.Add(null, 11.1);
- dict1.Add("bar", 9.8);
- dict1.Add("foo", 8.8);
- dict1.Add("gib", 7.1);
- dict1.Add("S", -9);
- dict1.Add(null, 5.5);
-
- Assert.Equal("3.5, -1.2, 8.8, 8.8",
- dict1["foo"].Write());
- Assert.Equal("7.1",
- dict1["gib"].Write());
- Assert.Equal("11.1, 11.1, 5.5",
- dict1[null].Write());
- }
- #endregion
-
- #region AddManyOne (LongRunning)
- /// <summary>
- /// Add many One. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void AddManyOne()
- {
- // Test without duplicate values.
- MultiDictionary<string, double> dict1 =
- new MultiDictionary<string, double>(
- false, StringComparer.InvariantCultureIgnoreCase);
-
- dict1.AddMany("foo", new[]
- {
- 9.8, 1.2, -9, 9.8, -9, 4
- });
- dict1.AddMany("hi", new double[0]);
- dict1.AddMany("FOO", new double[]
- {
- 8, -9
- });
-
- Assert.Equal(1, dict1.Count);
- Assert.True(dict1.ContainsKey("foo"));
- Assert.False(dict1.ContainsKey("hi"));
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.Keys, new[]
- {
- "foo"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["fOo"], new[]
- {
- -9, 1.2, 4, 8, 9.8
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.KeyValuePairs, new[]
- {
- new KeyValuePair<string, double>("foo", -9),
- new KeyValuePair<string, double>("foo", 1.2),
- new KeyValuePair<string, double>("foo", 4),
- new KeyValuePair<string, double>("foo", 8),
- new KeyValuePair<string, double>("foo", 9.8)
- });
-
- // Test with duplicate values
- dict1 = new MultiDictionary<string, double>(
- true, StringComparer.InvariantCultureIgnoreCase);
-
- dict1.AddMany("foo", new[]
- {
- 9.8, 1.2, -9, 9.8, -9, 4
- });
- dict1.AddMany("hi", new double[0]);
- dict1.AddMany("a", new double[]
- {
- 2, 1, 2
- });
- dict1.AddMany("FOO", new double[]
- {
- 8, -9
- });
-
- Assert.Equal(2, dict1.Count);
- Assert.True(dict1.ContainsKey("foo"));
- Assert.False(dict1.ContainsKey("hi"));
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.Keys, new[]
- {
- "a", "foo"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["fOo"], new[]
- {
- -9, -9, -9, 1.2, 4, 8, 9.8, 9.8
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.KeyValuePairs, new[]
- {
- new KeyValuePair<string, double>("a", 1),
- new KeyValuePair<string, double>("a", 2),
- new KeyValuePair<string, double>("a", 2),
- new KeyValuePair<string, double>("foo", -9),
- new KeyValuePair<string, double>("foo", -9),
- new KeyValuePair<string, double>("foo", -9),
- new KeyValuePair<string, double>("foo", 1.2),
- new KeyValuePair<string, double>("foo", 4),
- new KeyValuePair<string, double>("foo", 8),
- new KeyValuePair<string, double>("foo", 9.8),
- new KeyValuePair<string, double>("foo", 9.8)
- });
- }
- #endregion
-
- #region Replace (LongRunning)
- /// <summary>
- /// Replace. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Replace()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 3);
- dict1.Add("foo", 1);
-
- dict1.Replace("foo", 13);
- dict1.Replace("z", 19);
- dict1.Replace("hello", 193);
- dict1.Replace("foo", 123);
- dict1.Add("foo", 123);
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "foo", "hello", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- },
- new[]
- {
- 123, 123
- },
- new[]
- {
- 193
- },
- new[]
- {
- 19
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region ReplaceMany (LongRunning)
- /// <summary>
- /// Replace many. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ReplaceMany()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(false);
-
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 3);
- dict1.Add("foo", 1);
- dict1.Add("bill", 9);
-
- dict1.ReplaceMany("bill", new int[0]);
- dict1.ReplaceMany("foo", new[]
- {
- 13, 4
- });
- dict1.ReplaceMany("z", new[]
- {
- 19
- });
- dict1.ReplaceMany("hello", new[]
- {
- 193, -11, 193
- });
- dict1.ReplaceMany("goodbye", new int[0]);
- dict1.ReplaceMany("foo", new[]
- {
- 123, 0, 4
- });
- dict1.Add("foo", 29);
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "foo", "hello", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- },
- new[]
- {
- 0, 4, 29, 123
- },
- new[]
- {
- -11, 193
- },
- new[]
- {
- 19
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region RemoveKey (LongRunning)
- /// <summary>
- /// Remove key. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void RemoveKey()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 10);
- dict1.Add("z", 3);
- dict1.Add("foo", 4);
- dict1.Add("bill", 9);
-
- Assert.True(dict1.ContainsKey("bill"));
- Assert.True(dict1.ContainsKey("foo"));
- Assert.True(dict1.ContainsKey("z"));
-
- Assert.True(dict1.Remove("bill"));
- Assert.False(dict1.Remove("bill"));
- Assert.False(dict1.Remove("smell"));
- Assert.True(dict1.Remove("foo"));
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- }, new[]
- {
- 3, 3, 10
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region RemoveManyKeys (LongRunning)
- /// <summary>
- /// Remove many keys. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void RemoveManyKeys()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 10);
- dict1.Add("z", 3);
- dict1.Add("foo", 4);
- dict1.Add("bill", 9);
-
- Assert.True(dict1.ContainsKey("bill"));
- Assert.True(dict1.ContainsKey("foo"));
- Assert.True(dict1.ContainsKey("z"));
-
- Assert.Equal(2, dict1.RemoveMany(
- new[]
- {
- "bill", "smell", "foo", "bill"
- }));
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- }, new[]
- {
- 3, 3, 10
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region Remove (LongRunning)
- /// <summary>
- /// Remove. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Remove()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 10);
- dict1.Add("z", 3);
- dict1.Add("foo", 4);
- dict1.Add("bill", 9);
- dict1.Add("foo", 4);
-
- Assert.True(dict1.Remove("foo", 4));
- Assert.True(dict1.Remove("foo", 4));
- Assert.True(dict1.Remove("z", 10));
- Assert.False(dict1.Remove("z", 10));
- Assert.False(dict1.Remove("foo", 11));
- Assert.False(dict1.Remove(null, 0));
- Assert.True(dict1.Remove("bill", 9));
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "foo", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- },
- new[]
- {
- 4, 6
- },
- new[]
- {
- 3, 3
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region RemoveManyOne (LongRunning)
- /// <summary>
- /// Remove many One. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void RemoveManyOne()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("bill", 7);
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 10);
- dict1.Add("z", 3);
- dict1.Add("foo", 4);
- dict1.Add("bill", 9);
- dict1.Add("foo", 4);
-
- Assert.Equal(2, dict1.RemoveMany("foo", new[]
- {
- 4, 11, 4
- }));
- Assert.Equal(1, dict1.RemoveMany("z", new[]
- {
- 9, 2, 10
- }));
- Assert.Equal(0, dict1.RemoveMany("z", new[]
- {
- 10, 16, 144, 10
- }));
- Assert.Equal(0, dict1.RemoveMany("foo", new int[0]));
- Assert.Equal(0, dict1.RemoveMany(null, new int[2]
- {
- 1, 2
- }));
- Assert.Equal(2, dict1.RemoveMany("bill", new[]
- {
- 9, 7
- }));
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- "bar", "foo", "z"
- },
- new[]
- {
- new[]
- {
- 7, 8
- },
- new[]
- {
- 4, 6
- },
- new[]
- {
- 3, 3
- }
- },
- "sailor", 19921);
- }
- #endregion
-
- #region Clear (LongRunning)
- /// <summary>
- /// Clear. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Clear()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add("bill", 7);
- dict1.Add("foo", 4);
- dict1.Add("bar", 7);
- dict1.Add("foo", 6);
- dict1.Add("z", 3);
- dict1.Add("bar", 8);
- dict1.Add("z", 10);
- dict1.Add(null, 3);
- dict1.Add("foo", 4);
- dict1.Add("bill", 9);
- dict1.Add("foo", 4);
-
- dict1.Clear();
-
- Assert.Equal(0, dict1.Count);
- Assert.False(dict1.ContainsKey("foo"));
- Assert.False(dict1.ContainsKey("z"));
- Assert.False(dict1.ContainsKey(null));
- Assert.Equal(0, dict1.Keys.Count);
- Assert.Equal(0, dict1.Values.Count);
- Assert.Equal(0, dict1.KeyValuePairs.Count);
-
- CheckMultiDictionaryContents(dict1, new string[0],
- new int[0][], "foo", 4);
- }
- #endregion
-
- #region Count (LongRunning)
- /// <summary>
- /// Count. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Count()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add(null, 7);
- dict1.Add("bar", 11);
- dict1.Add("foo", 7);
- dict1.Add(null, 7);
- dict1.Add("hello", 11);
- dict1.Add("foo", 4);
- Assert.Equal(4, dict1.Count);
-
- MultiDictionary<string, int> dict2 =
- new MultiDictionary<string, int>(false);
-
- dict2.Add("foo", 4);
- dict2.Add(null, 7);
- dict2.Add("bar", 11);
- dict2.Add("foo", 7);
- dict2.Add(null, 7);
- dict2.Add("hello", 11);
- dict2.Add("foo", 4);
- Assert.Equal(4, dict2.Count);
-
- dict2.Remove("foo");
- Assert.Equal(3, dict2.Count);
-
- dict2.Clear();
- Assert.Equal(0, dict2.Count);
- }
- #endregion
-
- #region ContainsKey (LongRunning)
- /// <summary>
- /// Contains key. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ContainsKey()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add(null, 7);
- dict1.Add("bar", 11);
- dict1.Add("foo", 7);
- dict1.Add(null, 7);
- dict1.Add("hello", 11);
- dict1.Add("foo", 4);
-
- Assert.True(dict1.ContainsKey(null));
- Assert.True(dict1.ContainsKey("foo"));
- Assert.True(dict1.ContainsKey("bar"));
- Assert.True(dict1.ContainsKey("hello"));
- dict1.Remove("hello", 11);
- Assert.False(dict1.ContainsKey("hello"));
- dict1.Remove(null, 7);
- Assert.True(dict1.ContainsKey(null));
- dict1.Remove(null, 7);
- Assert.False(dict1.ContainsKey(null));
- }
- #endregion
-
- #region Contains (LongRunning)
- /// <summary>
- /// Contains. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Contains()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(true);
-
- dict1.Add("foo", 4);
- dict1.Add(null, 7);
- dict1.Add("bar", 11);
- dict1.Add("foo", 7);
- dict1.Add(null, 7);
- dict1.Add("hello", 11);
- dict1.Add("foo", 4);
-
- Assert.True(dict1.Contains(null, 7));
- Assert.True(dict1.Contains("foo", 4));
- Assert.True(dict1.Contains("bar", 11));
- Assert.True(dict1.Contains("hello", 11));
- Assert.False(dict1.Contains("HELLO", 11));
- Assert.False(dict1.Contains("bar", 12));
- Assert.False(dict1.Contains("foo", 0));
- dict1.Remove("hello", 11);
- Assert.False(dict1.Contains("hello", 11));
- dict1.Remove(null, 7);
- Assert.True(dict1.Contains(null, 7));
- dict1.Remove(null, 7);
- Assert.False(dict1.Contains(null, 7));
- }
- #endregion
-
- #region KeysCollection (LongRunning)
- /// <summary>
- /// Keys collection. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void KeysCollection()
- {
- MultiDictionary<string, int> dict1 =
- new MultiDictionary<string, int>(
- false, StringComparer.InvariantCultureIgnoreCase);
-
- dict1.Add("foo", 4);
- dict1.Add(null, 2);
- dict1.Add("bar", 3);
- dict1.Add("sailor", 0);
- dict1.Add("FOO", 9);
- dict1.Add("b", 7);
- dict1.Add("Foo", -1);
- dict1.Add("BAR", 3);
- dict1.Remove("b", 7);
-
- CollectionBaseTests.CompareCollections(
- dict1.Keys, new[]
- {
- null, "bar", "foo", "sailor"
- }, false);
-
- Assert.True(dict1.Keys.Contains("foo"));
- Assert.True(dict1.Keys.Contains("Foo"));
- Assert.True(dict1.Keys.Contains(null));
- Assert.True(dict1.Keys.Contains("Sailor"));
- Assert.False(dict1.Keys.Contains("banana"));
-
- MultiDictionary<string, int> dict2 =
- new MultiDictionary<string, int>(
- false, StringComparer.InvariantCultureIgnoreCase);
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict2.Keys, new string[]
- {
- });
- }
- #endregion
-
- #region ValuesCollectionTwo (LongRunning)
- /// <summary>
- /// Values collection Two. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ValuesCollectionTwo()
- {
- MultiDictionary<double, string> dict =
- new MultiDictionary<double, string>(
- true, EqualityComparer<double>.Default,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict.Add(7, "Gizzle");
- dict.Add(4, "foo");
- dict.Add(6, "Foo");
- dict.Add(3, "FOO");
- dict.Add(3, "baz");
- dict.Add(3, "bar");
- dict.Add(4, "FOo");
- dict.Add(3, "BAZ");
- dict.Add(5, "bAZ");
- dict.Add(7, "hello");
- dict.Add(7, "foo");
-
- ICollection<string> vals = dict.Values;
-
- string[] expected =
- {
- "bar", "baz", "BAZ", "FOO", "foo", "FOo", "bAZ", "Foo", "foo",
- "Gizzle", "hello"
- };
-
- CollectionBaseTests.CompareCollections(
- vals, expected, false);
-
- Assert.True(vals.Contains("gizzle"));
- Assert.True(vals.Contains("FOO"));
- Assert.True(vals.Contains("fOO"));
- Assert.True(vals.Contains("hello"));
- Assert.True(vals.Contains("bar"));
- Assert.True(vals.Contains("BAR"));
- Assert.False(vals.Contains("qatar"));
- }
- #endregion
-
- #region KeyValuesCollectionOne (LongRunning)
- /// <summary>
- /// Key values collection One. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void KeyValuesCollectionOne()
- {
- MultiDictionary<string, string> dict =
- new MultiDictionary<string, string>(
- false, StringComparer.InvariantCultureIgnoreCase,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict.Add("7A", "Gizzle");
- dict.Add("4a", "foo");
- dict.Add("6A", "Foo");
- dict.Add("3a", "FOO");
- dict.Add("3A", "baz");
- dict.Add("3a", "bar");
- dict.Add("4a", "FOo");
- dict.Add("3A", "BAZ");
- dict.Add("5a", "bAZ");
- dict.Add("7a", "hello");
- dict.Add("7A", "foo");
-
- ICollection<KeyValuePair<string, string>> pairs = dict.KeyValuePairs;
-
- string[] expectedKeys =
- {
- "3a", "3a", "3a", "4a", "5a", "6A", "7A", "7A", "7A"
- };
- string[] expectedVals =
- {
- "bar", "BAZ", "FOO", "FOo", "bAZ", "Foo", "foo", "Gizzle", "hello"
- };
- KeyValuePair<string, string>[] expectedPairs =
- new KeyValuePair<string, string>[expectedKeys.Length];
- for (int i = 0; i < expectedKeys.Length; ++i)
- {
- expectedPairs[i] = new KeyValuePair<string, string>(
- expectedKeys[i], expectedVals[i]);
- }
-
- CollectionBaseTests.CompareCollections(
- pairs, expectedPairs, false);
-
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("3a", "baz")));
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("3A", "baz")));
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("6a", "foo")));
- Assert.False(pairs.Contains(
- new KeyValuePair<string, string>("7A", "bar")));
- }
- #endregion
-
- #region KeyValuesCollectionTwo (LongRunning)
- /// <summary>
- /// Key values collection Two. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void KeyValuesCollectionTwo()
- {
- MultiDictionary<string, string> dict =
- new MultiDictionary<string, string>(
- true, StringComparer.InvariantCultureIgnoreCase,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict.Add("7A", "Gizzle");
- dict.Add("4A", "foo");
- dict.Add("6A", "Foo");
- dict.Add("3a", "FOO");
- dict.Add("3A", "baz");
- dict.Add("3a", "bar");
- dict.Add("4a", "FOo");
- dict.Add("3a", "BAZ");
- dict.Add("5a", "bAZ");
- dict.Add("7a", "hello");
- dict.Add("7A", "foo");
-
- ICollection<KeyValuePair<string, string>> pairs = dict.KeyValuePairs;
-
- string[] expectedKeys =
- {
- "3a", "3a", "3a", "3a", "4A", "4A", "5a", "6A", "7A", "7A", "7A"
- };
- string[] expectedVals =
- {
- "bar", "baz", "BAZ", "FOO", "foo", "FOo", "bAZ", "Foo", "foo",
- "Gizzle", "hello"
- };
- KeyValuePair<string, string>[] expectedPairs =
- new KeyValuePair<string, string>[expectedKeys.Length];
- for (int i = 0; i < expectedKeys.Length; ++i)
- {
- expectedPairs[i] = new KeyValuePair<string, string>(
- expectedKeys[i], expectedVals[i]);
- }
-
- CollectionBaseTests.CompareCollections(
- pairs, expectedPairs, false);
-
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("3a", "baz")));
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("3A", "baz")));
- Assert.True(pairs.Contains(
- new KeyValuePair<string, string>("6a", "foo")));
- Assert.False(pairs.Contains(
- new KeyValuePair<string, string>("7A", "bar")));
- }
- #endregion
-
- #region Indexer (LongRunning)
- /// <summary>
- /// Indexer. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void Indexer()
- {
- MultiDictionary<string, string> dict1 =
- new MultiDictionary<string, string>(
- true, StringComparer.InvariantCultureIgnoreCase,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict1.Add("foo", "BAR");
- dict1.Add(null, "hello");
- dict1.Add("Hello", "sailor");
- dict1.Add(null, "hi");
- dict1.Add("foo", "bar");
- dict1.Add("HELLO", null);
- dict1.Add("foo", "a");
- dict1.Add("Foo", "A");
- dict1.Add("trail", "mix");
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1[null], new[]
- {
- "hello", "hi"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["hELLo"], new[]
- {
- null, "sailor"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["foo"], new[]
- {
- "a", "A", "BAR", "bar"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["trail"], new[]
- {
- "mix"
- });
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1["nothing"], new string[]
- {
- });
- }
- #endregion
-
- #region GetValueCount (LongRunning)
- /// <summary>
- /// Get value count. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void GetValueCount()
- {
- MultiDictionary<string, string> dict1 =
- new MultiDictionary<string, string>(
- true, StringComparer.InvariantCultureIgnoreCase,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict1.Add("foo", "BAR");
- dict1.Add(null, "hello");
- dict1.Add("Hello", "sailor");
- dict1.Add(null, "hi");
- dict1.Add("foo", "bar");
- dict1.Add("HELLO", null);
- dict1.Add("foo", "a");
- dict1.Add("Foo", "A");
- dict1.Add("hello", null);
- dict1.Add("trail", "mix");
-
- Assert.Equal(2, dict1[null].Count);
- Assert.Equal(3, dict1["hELLo"].Count);
- Assert.Equal(4, dict1["foo"].Count);
- Assert.Equal(1, dict1["trail"].Count);
- Assert.Equal(0, dict1["nothing"].Count);
-
- dict1 = new MultiDictionary<string, string>(
- false, StringComparer.InvariantCultureIgnoreCase,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict1.Add("foo", "BAR");
- dict1.Add(null, "hello");
- dict1.Add("Hello", "sailor");
- dict1.Add(null, "hi");
- dict1.Add("foo", "bar");
- dict1.Add("HELLO", null);
- dict1.Add("foo", "a");
- dict1.Add("Foo", "A");
- dict1.Add("hello", null);
- dict1.Add("trail", "mix");
-
- Assert.Equal(2, dict1[null].Count);
- Assert.Equal(2, dict1["hELLo"].Count);
- Assert.Equal(2, dict1["foo"].Count);
- Assert.Equal(1, dict1["trail"].Count);
- Assert.Equal(0, dict1["nothing"].Count);
- }
- #endregion
-
- #region TestMultiDictionaryInterface (LongRunning)
- /// <summary>
- /// Test multi dictionary interface.
- /// Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void TestMultiDictionaryInterface()
- {
- MultiDictionary<string, string> dict1 =
- new MultiDictionary<string, string>(true);
-
- dict1.Add("foo", "bar");
- dict1.Add(null, "hello");
- dict1.Add("hello", "sailor");
- dict1.Add(null, "hi");
- dict1.Add("foo", "bar");
- dict1.Add("hello", null);
- dict1.Add("foo", "a");
- dict1.Add("foo", "a");
- dict1.Add("hello", null);
- dict1.Add("trail", "mix");
-
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- null, "foo", "hello", "trail"
- },
- new[]
- {
- new[]
- {
- "hello", "hi"
- },
- new[]
- {
- "a", "a", "bar", "bar"
- },
- new[]
- {
- null, null, "sailor"
- },
- new[]
- {
- "mix"
- }
- },
- "zippy", "pinhead");
-
- dict1 = new MultiDictionary<string, string>(false);
-
- dict1.Add("foo", "bar");
- dict1.Add(null, "hello");
- dict1.Add("hello", "sailor");
- dict1.Add(null, "hi");
- dict1.Add("foo", "bar");
- dict1.Add("hello", null);
- dict1.Add("foo", "a");
- dict1.Add("foo", "a");
- dict1.Add("hello", null);
-
- dict1.Add("trail", "mix");
- CheckMultiDictionaryContents(dict1,
- new[]
- {
- null, "foo", "hello", "trail"
- },
- new[]
- {
- new[]
- {
- "hello", "hi"
- },
- new[]
- {
- "a", "bar"
- },
- new[]
- {
- null, "sailor"
- },
- new[]
- {
- "mix"
- }
- },
- "zippy", "pinhead");
- }
- #endregion
-
- #region CustomComparer (LongRunning)
- /// <summary>
- /// Custom comparer. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void CustomComparer()
- {
- IEqualityComparer<string> firstLetterComparer =
- new FirstLetterComparer();
-
- MultiDictionary<string, string> dict1 =
- new MultiDictionary<string, string>(false, firstLetterComparer);
-
- dict1.Add("hello", "AAA");
- dict1.Add("hi", "aaa");
- dict1.Add("qubert", "hello");
- dict1.Add("queztel", "hello");
- dict1.Add("alpha", "omega");
- dict1.Add("alzabar", "oz");
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.KeyValuePairs, new[]
- {
- new KeyValuePair<string, string>("qubert", "hello"),
- new KeyValuePair<string, string>("hello", "aaa"),
- new KeyValuePair<string, string>("hello", "AAA"),
- new KeyValuePair<string, string>("alpha", "omega"),
- new KeyValuePair<string, string>("alpha", "oz")
- });
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict1.Keys, new[]
- {
- "qubert", "hello", "alpha"
- });
-
- MultiDictionary<string, string> dict2 =
- new MultiDictionary<string, string>(
- false, StringComparer.InvariantCultureIgnoreCase,
- firstLetterComparer);
-
- dict2.Add("qubert", "dinosaur");
- dict2.Add("Hello", "AAA");
- dict2.Add("Hi", "aaa");
- dict2.Add("qubert", "hello");
- dict2.Add("queztel", "hello");
- dict2.Add("alpha", "omega");
- dict2.Add("Alpha", "oz");
- dict2.Add("qubert", "hippy");
-
- CollectionBaseTests.CheckEnumerableElementsAnyOrder(
- dict2.KeyValuePairs, new[]
- {
- new KeyValuePair<string, string>("alpha", "oz"),
- new KeyValuePair<string, string>("Hello", "AAA"),
- new KeyValuePair<string, string>("Hi", "aaa"),
- new KeyValuePair<string, string>("qubert", "hippy"),
- new KeyValuePair<string, string>("qubert", "dinosaur"),
- new KeyValuePair<string, string>("queztel", "hello")
- });
- }
- #endregion
-
- #region ValuesCollectionOne (LongRunning)
- /// <summary>
- /// Values collection One. Note: Too slow for a dynamic unit test.
- /// </summary>
- [Test, Category("LongRunning")]
- public static void ValuesCollectionOne()
- {
- MultiDictionary<double, string> dict =
- new MultiDictionary<double, string>(
- false, EqualityComparer<double>.Default,
- StringComparer.InvariantCultureIgnoreCase);
-
- dict.Add(7, "Gizzle");
- dict.Add(4, "foo");
- dict.Add(6, "Foo");
- dict.Add(3, "FOO");
- dict.Add(3, "baz");
- dict.Add(3, "bar");
- dict.Add(4, "FOo");
- dict.Add(3, "BAZ");
- dict.Add(5, "bAZ");
- dict.Add(7, "hello");
- dict.Add(7, "foo");
-
- ICollection<string> vals = dict.Values;
-
- string[] expected =
- {
- "bar", "BAZ", "FOO", "FOo", "bAZ", "Foo", "foo", "Gizzle", "hello"
- };
-
- CollectionBaseTests.CompareCollections(
- vals, expected, false);
-
- Assert.True(vals.Contains("gizzle"));
- Assert.True(vals.Contains("FOO"));
- Assert.True(vals.Contains("fOO"));
- Assert.True(vals.Contains("hello"));
- Assert.True(vals.Contains("bar"));
- Assert.True(vals.Contains("BAR"));
- Assert.False(vals.Contains("qatar"));
- }
- #endregion
- }
- }