using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace Swan.Collections { /// /// Represents a non-thread-safe collection of key/value pairs that does not store null values. /// /// The type of keys in the dictionary. This must be a reference type. /// The type of values in the dictionary. This must be a reference type. /// public sealed class DataDictionary : IDataDictionary where TKey : class where TValue : class { #region Private data private readonly Dictionary _dictionary; #endregion #region Instance management /// /// Initializes a new instance of the class /// that is empty, has the default initial capacity, /// and uses the default comparer for . /// /// public DataDictionary() { _dictionary = new Dictionary(); } /// /// Initializes a new instance of the class /// that contains elements copied from the specified , /// has the default initial capacity, and uses the default comparer for . /// /// The whose elements are copied /// to the new . /// is . /// /// Since does not store null values, /// key/value pairs whose value is will not be copied from . /// /// public DataDictionary(IEnumerable> collection) { if (collection == null) throw new ArgumentNullException(nameof(collection)); _dictionary = new Dictionary(); foreach (var pair in collection.Where(pair => pair.Value != null)) { _dictionary.Add(pair.Key, pair.Value); } } /// /// Initializes a new instance of the class /// that is empty, has the default capacity, and uses the specified . /// /// The equality comparison implementation to use when comparing keys. /// is . /// public DataDictionary(IEqualityComparer comparer) { _dictionary = new Dictionary(comparer); } /// /// Initializes a new instance of the class /// that contains elements copied from the specified , /// has the default initial capacity, and uses the specified . /// /// The whose elements are copied /// to the new . /// The equality comparison implementation to use when comparing keys. /// /// Since does not store null values, /// key/value pairs whose value is will not be copied from . /// /// /// is . /// - or -. /// is . /// /// public DataDictionary(IEnumerable> collection, IEqualityComparer comparer) { if (collection == null) throw new ArgumentNullException(nameof(collection)); _dictionary = new Dictionary(comparer); foreach (var pair in collection.Where(pair => pair.Value != null)) { _dictionary.Add(pair.Key, pair.Value); } } /// /// Initializes a new instance of the class /// that is empty, has the specified capacity, and uses the default comparer for the key type. /// /// The initial number of elements that the can contain. /// is less than 0. /// public DataDictionary(int capacity) { _dictionary = new Dictionary(capacity); } /// /// Initializes a new instance of the class /// that contains elements copied from the specified , /// has the specified capacity, and uses the specified . /// /// The initial number of elements that the can contain. /// The whose elements are copied /// to the new . /// The equality comparison implementation to use when comparing keys. /// /// Since does not store null values, /// key/value pairs whose value is will not be copied from . /// /// /// is . /// - or -. /// is . /// /// is less than 0. /// public DataDictionary(int capacity, IEnumerable> collection, IEqualityComparer comparer) { if (collection == null) throw new ArgumentNullException(nameof(collection)); _dictionary = new Dictionary(capacity, comparer); foreach (var pair in collection.Where(pair => pair.Value != null)) { _dictionary.Add(pair.Key, pair.Value); } } #endregion #region Public APIs /// public int Count => _dictionary.Count; /// public bool IsEmpty => _dictionary.Count == 0; /// public ICollection Keys => _dictionary.Keys; /// public ICollection Values => _dictionary.Values; /// public TValue? this[TKey key] { get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null; set { if (value != null) { _dictionary[key] = value; } else { _dictionary.Remove(key); } } } /// public void Clear() => _dictionary.Clear(); /// public bool ContainsKey(TKey key) { // _dictionary.ContainsKey will take care of throwing on a null key. return _dictionary.ContainsKey(key); } /// public TValue? GetOrAdd(TKey key, TValue value) { // _dictionary.TryGetValue will take care of throwing on a null key. if (_dictionary.TryGetValue(key, out var result)) return result; if (value == null) return null; _dictionary.Add(key, value); return value; } /// public bool Remove(TKey key) { // _dictionary.Remove will take care of throwing on a null key. return _dictionary.Remove(key); } /// public bool TryAdd(TKey key, TValue value) { // _dictionary.ContainsKey will take care of throwing on a null key. if (_dictionary.ContainsKey(key)) return false; if (value != null) _dictionary.Add(key, value); return true; } /// public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value); /// public bool TryRemove(TKey key, out TValue value) { // TryGetValue will take care of throwing on a null key. if (!_dictionary.TryGetValue(key, out value)) return false; _dictionary.Remove(key); return true; } /// public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) { // TryGetValue will take care of throwing on a null key. if (!_dictionary.TryGetValue(key, out var value)) return false; if (value != comparisonValue) return false; _dictionary[key] = newValue; return true; } #endregion #region Implementation of IDictionary /// void IDictionary.Add(TKey key, TValue value) { // Validating the key seems redundant, because both Add and Remove // will throw on a null key. // This way, though, the code path on null key does not depend on value. // Without this validation, there should be two unit tests for null key, // one with a null value and one with a non-null value, // which makes no sense. if (key == null) throw new ArgumentNullException(nameof(key)); if (value != null) { _dictionary.Add(key, value); } else { _dictionary.Remove(key); } } #endregion #region Implementation of IReadOnlyDictionary /// IEnumerable IReadOnlyDictionary.Keys => _dictionary.Keys; /// IEnumerable IReadOnlyDictionary.Values => _dictionary.Values; #endregion #region Implementation of ICollection> /// /// /// This property is always for a . /// bool ICollection>.IsReadOnly => false; /// void ICollection>.Add(KeyValuePair item) { if (item.Value != null) { ((ICollection>)_dictionary).Add(item); } else { _dictionary.Remove(item.Key); } } /// bool ICollection>.Contains(KeyValuePair item) => ((ICollection>)_dictionary).Contains(item); /// void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)_dictionary).CopyTo(array, arrayIndex); /// bool ICollection>.Remove(KeyValuePair item) => ((ICollection>) _dictionary).Remove(item); #endregion #region Implementation of IEnumerable> /// IEnumerator> IEnumerable>.GetEnumerator() => _dictionary.GetEnumerator(); #endregion #region Implementation of IEnumerable /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator(); #endregion } }