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() => this._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)); } this._dictionary = new Dictionary(); foreach(KeyValuePair pair in collection.Where(pair => pair.Value != null)) { this._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) => this._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)); } this._dictionary = new Dictionary(comparer); foreach(KeyValuePair pair in collection.Where(pair => pair.Value != null)) { this._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(Int32 capacity) => this._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(Int32 capacity, IEnumerable> collection, IEqualityComparer comparer) { if(collection == null) { throw new ArgumentNullException(nameof(collection)); } this._dictionary = new Dictionary(capacity, comparer); foreach(KeyValuePair pair in collection.Where(pair => pair.Value != null)) { this._dictionary.Add(pair.Key, pair.Value); } } #endregion #region Public APIs /// public Int32 Count => this._dictionary.Count; /// public Boolean IsEmpty => this._dictionary.Count == 0; /// public ICollection Keys => this._dictionary.Keys; /// public ICollection Values => this._dictionary.Values; /// public TValue this[TKey key] { get => this._dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out TValue value) ? value : null; set { if(value != null) { this._dictionary[key] = value; } else { _ = this._dictionary.Remove(key); } } } /// public void Clear() => this._dictionary.Clear(); /// public Boolean ContainsKey(TKey key) => // _dictionary.ContainsKey will take care of throwing on a null key. this._dictionary.ContainsKey(key); /// public TValue GetOrAdd(TKey key, TValue value) { // _dictionary.TryGetValue will take care of throwing on a null key. if(this._dictionary.TryGetValue(key, out TValue result)) { return result; } if(value == null) { return null; } this._dictionary.Add(key, value); return value; } /// public Boolean Remove(TKey key) => // _dictionary.Remove will take care of throwing on a null key. this._dictionary.Remove(key); /// public Boolean TryAdd(TKey key, TValue value) { // _dictionary.ContainsKey will take care of throwing on a null key. if(this._dictionary.ContainsKey(key)) { return false; } if(value != null) { this._dictionary.Add(key, value); } return true; } /// public Boolean TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value); /// public Boolean TryRemove(TKey key, out TValue value) { // TryGetValue will take care of throwing on a null key. if(!this._dictionary.TryGetValue(key, out value)) { return false; } _ = this._dictionary.Remove(key); return true; } /// public Boolean TryUpdate(TKey key, TValue newValue, TValue comparisonValue) { // TryGetValue will take care of throwing on a null key. if(!this._dictionary.TryGetValue(key, out TValue value)) { return false; } if(value != comparisonValue) { return false; } this._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) { this._dictionary.Add(key, value); } else { _ = this._dictionary.Remove(key); } } #endregion #region Implementation of IReadOnlyDictionary /// IEnumerable IReadOnlyDictionary.Keys => this._dictionary.Keys; /// IEnumerable IReadOnlyDictionary.Values => this._dictionary.Values; #endregion #region Implementation of ICollection> /// /// /// This property is always for a . /// Boolean ICollection>.IsReadOnly => false; /// void ICollection>.Add(KeyValuePair item) { if(item.Value != null) { ((ICollection>)this._dictionary).Add(item); } else { _ = this._dictionary.Remove(item.Key); } } /// Boolean ICollection>.Contains(KeyValuePair item) => ((ICollection>)this._dictionary).Contains(item); /// void ICollection>.CopyTo(KeyValuePair[] array, Int32 arrayIndex) => ((ICollection>)this._dictionary).CopyTo(array, arrayIndex); /// Boolean ICollection>.Remove(KeyValuePair item) => ((ICollection>)this._dictionary).Remove(item); #endregion #region Implementation of IEnumerable> /// IEnumerator> IEnumerable>.GetEnumerator() => this._dictionary.GetEnumerator(); #endregion #region Implementation of IEnumerable /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._dictionary).GetEnumerator(); #endregion } }