< Summary

Information
Class: SwiftCollections.SwiftBiDictionary<T1, T2>
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftBiDictionary.cs
Line coverage
97%
Covered lines: 143
Uncovered lines: 3
Coverable lines: 146
Total lines: 384
Line coverage: 97.9%
Branch coverage
92%
Covered branches: 39
Total branches: 42
Branch coverage: 92.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%22100%
.ctor(...)100%11100%
get_ReverseSyncRoot()100%22100%
get_Item(...)100%11100%
set_Item(...)90%101096.15%
get_State()100%11100%
set_State(...)100%22100%
Remove(...)100%66100%
Add(...)100%66100%
InsertIfNotExist(...)75%4490%
Remove(...)100%44100%
Clear()100%11100%
SetComparer(...)83.33%6693.33%
TryGetKey(...)100%11100%
GetKey(...)100%11100%
ContainsValue(...)100%11100%

File(s)

/home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftBiDictionary.cs

#LineLine coverage
 1using MemoryPack;
 2using System;
 3using System.Collections.Generic;
 4using System.Text.Json.Serialization;
 5
 6namespace SwiftCollections;
 7
 8/// <summary>
 9/// Represents a bidirectional dictionary that allows for efficient lookups in both directions,
 10/// mapping keys to values and values back to keys. Both keys and values must be unique to maintain
 11/// the integrity of the bidirectional relationship.
 12/// Inherits from <see cref="SwiftDictionary{TKey, TValue}"/> and maintains a reverse map for reverse lookups.
 13/// </summary>
 14/// <typeparam name="T1">The type of the keys in the forward dictionary.</typeparam>
 15/// <typeparam name="T2">The type of the values in the forward dictionary.</typeparam>
 16/// <remarks>
 17/// The comparer is not serialized. After deserialization the dictionary reverts
 18/// to the same default comparer selection used by a new instance for both the
 19/// forward and reverse maps. String keys use SwiftCollections' deterministic
 20/// default comparer. Object keys use a SwiftCollections comparer that hashes
 21/// strings deterministically, while other object-key determinism still depends
 22/// on the underlying key type's <see cref="object.GetHashCode()"/>
 23/// implementation. Other key types use <see cref="EqualityComparer{T1}.Default"/>
 24/// and <see cref="EqualityComparer{T2}.Default"/>.
 25///
 26/// If a custom comparer is required it can be reapplied using
 27/// <see cref="SetComparer(IEqualityComparer{T1}, IEqualityComparer{T2})"/>.
 28/// </remarks>
 29[Serializable]
 30[JsonConverter(typeof(SwiftStateJsonConverterFactory))]
 31[MemoryPackable]
 32public partial class SwiftBiDictionary<T1, T2> : SwiftDictionary<T1, T2>
 33{
 34    #region Fields
 35
 36    /// <summary>
 37    /// The reverse map for bidirectional lookup, mapping from <typeparamref name="T2"/> to <typeparamref name="T1"/>.
 38    /// </summary>
 39    private SwiftDictionary<T2, T1> _reverseMap;
 40
 41    /// <summary>
 42    /// The comparer used to determine equality of keys and to generate hash codes.
 43    /// </summary>
 44    [NonSerialized]
 45    private IEqualityComparer<T2> _reverseComparer;
 46
 47    /// <summary>
 48    /// An object used to synchronize access to the reverse map during serialization and deserialization.
 49    /// </summary>
 50    [NonSerialized]
 51    private object _reverseSyncRoot;
 52
 53    #endregion
 54
 55    #region Constructors
 56
 57    /// <summary>
 58    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that is empty and uses the defau
 59    /// </summary>
 5160    public SwiftBiDictionary() : this(null, null) { }
 61
 62    /// <summary>
 63    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that is empty and uses the speci
 64    /// </summary>
 65    /// <param name="comparer1">The comparer to use for the keys.</param>
 66    /// <param name="comparer2">The comparer to use for the values.</param>
 2167    public SwiftBiDictionary(IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comparer2) : base(DefaultCapacity, c
 2168    {
 2169        _reverseComparer = SwiftHashTools.GetDefaultEqualityComparer(comparer2);
 2170        _reverseMap = new SwiftDictionary<T2, T1>(DefaultCapacity, _reverseComparer);
 2171    }
 72
 73    /// <summary>
 74    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that contains elements copied fr
 75    /// </summary>
 76    /// <param name="dictionary">The dictionary whose elements are copied to the new <see cref="SwiftBiDictionary{T1, T2
 377    public SwiftBiDictionary(IDictionary<T1, T2> dictionary) : this(dictionary, null, null) { }
 78
 79    /// <summary>
 80    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that contains elements copied fr
 81    /// </summary>
 82    /// <param name="dictionary">The dictionary whose elements are copied to the new <see cref="SwiftBiDictionary{T1, T2
 83    /// <param name="comparer1">The comparer to use for the keys.</param>
 84    /// <param name="comparer2">The comparer to use for the values.</param>
 85    public SwiftBiDictionary(IDictionary<T1, T2> dictionary, IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comp
 286        : this(comparer1, comparer2)
 287    {
 288        SwiftThrowHelper.ThrowIfNull(dictionary, nameof(dictionary));
 89
 1490        foreach (var kvp in dictionary)
 491            Add(kvp.Key, kvp.Value);
 292    }
 93
 94    ///  <summary>
 95    ///  Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class with the specified <see cref="S
 96    ///  </summary>
 97    ///  <param name="state">The state containing the internal array, count, offset, and version for initialization.</pa
 98    [MemoryPackConstructor]
 599    public SwiftBiDictionary(SwiftDictionaryState<T1, T2> state)
 5100    {
 5101        State = state;
 5102    }
 103
 104    #endregion
 105
 106    #region Properties
 107
 108    [JsonIgnore]
 109    [MemoryPackIgnore]
 138110    public object ReverseSyncRoot => _reverseSyncRoot ??= new object();
 111
 112    [JsonIgnore]
 113    [MemoryPackIgnore]
 114    public new T2 this[T1 key]
 115    {
 12116        get => base[key];
 117
 118        set
 5119        {
 5120            SwiftThrowHelper.ThrowIfNull(key, nameof(key));
 121
 5122            lock (ReverseSyncRoot)
 5123            {
 5124                if (TryGetValue(key, out T2 oldValue))
 4125                {
 126                    // No change
 4127                    if (_reverseComparer.Equals(oldValue, value))
 1128                        return;
 129
 130                    // Prevent duplicate values
 3131                    if (_reverseMap.ContainsKey(value))
 1132                        throw new ArgumentException("Value already exists", nameof(value));
 133
 134                    // Remove old reverse mapping
 2135                    _reverseMap.Remove(oldValue);
 136
 137                    // Update forward value
 2138                    int index = FindEntry(key);
 2139                    _entries[index].Value = value;
 140
 141                    // Insert new reverse mapping
 2142                    _reverseMap.Add(value, key);
 143
 2144                    _version++;
 2145                }
 146                else
 1147                {
 148                    // New insert
 1149                    if (_reverseMap.ContainsKey(value))
 0150                        throw new ArgumentException("Value already exists", nameof(value));
 151
 1152                    CheckLoadThreshold();
 153
 1154                    bool added = InsertIfNotExist(key, value);
 155
 1156                    if (added)
 1157                        _reverseMap.Add(value, key);
 1158                }
 3159            }
 4160        }
 161    }
 162
 163    [JsonInclude]
 164    [MemoryPackInclude]
 165    public new SwiftDictionaryState<T1, T2> State
 166    {
 5167        get => base.State;
 168
 169        internal set
 5170        {
 5171            _reverseComparer = SwiftHashTools.GetDefaultEqualityComparer<T2>();
 5172            _reverseMap = new SwiftDictionary<T2, T1>(value.Items.Length, _reverseComparer);
 173
 5174            base.State = value;
 175
 5176            lock (ReverseSyncRoot)
 5177            {
 5178                _reverseMap.Clear();
 179
 47180                foreach (var kv in this)
 16181                    _reverseMap.Add(kv.Value, kv.Key);
 5182            }
 5183        }
 184    }
 185
 186    #endregion
 187
 188    #region Collection Manipulation
 189
 190    /// <summary>
 191    /// Removes the key-value pair from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 192    /// Also removes the corresponding value-key pair from the reverse map.
 193    /// </summary>
 194    /// <param name="key">The key of the element to remove.</param>
 195    /// <param name="value">The value of the element to remove.</param>
 196    /// <returns><c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.</returns>
 197    public bool Remove(T1 key, T2 value)
 2198    {
 2199        SwiftThrowHelper.ThrowIfNull(key, nameof(key));
 2200        SwiftThrowHelper.ThrowIfNull(value, nameof(value));
 201
 2202        if (TryGetValue(key, out T2 existingValue))
 2203        {
 2204            if (_reverseComparer.Equals(existingValue, value))
 1205            {
 1206                bool removed = base.Remove(key);
 1207                if (removed)
 1208                {
 1209                    lock (ReverseSyncRoot)
 1210                        _reverseMap.Remove(value);
 1211                }
 1212                return removed;
 213            }
 1214        }
 1215        return false;
 2216    }
 217
 218    #region Overrides
 219
 220    /// <summary>
 221    /// Adds the specified key and value to the <see cref="SwiftBiDictionary{T1, T2}"/>.
 222    /// Also adds the value-key pair to the reverse map.
 223    /// </summary>
 224    /// <param name="key">The key of the element to add.</param>
 225    /// <param name="value">The value of the element to add.</param>
 226    /// <exception cref="ArgumentException">An element with the same key or value already exists.</exception>
 227    public override bool Add(T1 key, T2 value)
 42228    {
 42229        SwiftThrowHelper.ThrowIfNull(key, nameof(key));
 42230        SwiftThrowHelper.ThrowIfNull(value, nameof(value));
 231
 42232        if (ContainsKey(key))
 1233            return false;
 234
 41235        lock (ReverseSyncRoot)
 41236        {
 41237            if (_reverseMap.ContainsKey(value))
 1238                return false;
 239
 40240            bool added = base.Add(key, value);
 241
 40242            if (added)
 40243                _reverseMap.Add(value, key);
 244
 40245            return added;
 246        }
 42247    }
 248
 249    /// <summary>
 250    /// Overrides the base class's <see cref="SwiftDictionary{TKey, TValue}.InsertIfNotExist"/> method to ensure synchro
 251    /// </summary>
 252    /// <param name="key">The key to insert.</param>
 253    /// <param name="value">The value to insert.</param>
 254    /// <returns><c>true</c> if a new entry was added; otherwise, <c>false</c>.</returns>
 255    internal override bool InsertIfNotExist(T1 key, T2 value)
 57256    {
 57257        lock (ReverseSyncRoot)
 57258        {
 57259            if (_reverseMap.ContainsKey(value))
 0260                return false;
 261
 57262            bool result = base.InsertIfNotExist(key, value);
 263
 57264            if (result)
 57265                _reverseMap.Add(value, key);
 266
 57267            return result;
 268        }
 57269    }
 270
 271    /// <summary>
 272    /// Removes the value with the specified key from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 273    /// Also removes the corresponding key from the reverse map.
 274    /// </summary>
 275    /// <param name="key">The key of the element to remove.</param>
 276    /// <returns><c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.</returns>
 277    public override bool Remove(T1 key)
 2278    {
 2279        SwiftThrowHelper.ThrowIfNull(key, nameof(key));
 280
 2281        if (TryGetValue(key, out T2 value))
 1282        {
 1283            bool removed = base.Remove(key);
 284
 1285            if (removed)
 1286            {
 1287                lock (ReverseSyncRoot)
 1288                    _reverseMap.Remove(value);
 1289            }
 290
 1291            return removed;
 292        }
 293
 1294        return false;
 2295    }
 296
 297    /// <summary>
 298    /// Removes all keys and values from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 299    /// Also clears the reverse map.
 300    /// </summary>
 301    public override void Clear()
 1302    {
 1303        base.Clear();
 1304        lock (ReverseSyncRoot)
 1305            _reverseMap.Clear();
 1306    }
 307
 308    #endregion
 309
 310    #endregion
 311
 312    #region Utility Methods
 313
 314    /// <summary>
 315    /// Sets the comparers used to determine equality for keys and values in the dictionary.
 316    /// </summary>
 317    /// <remarks>This method updates the internal comparers and rebuilds the reverse mapping using the
 318    /// specified value comparer. The operation is thread-safe and locks the internal state during the update. Changing
 319    /// comparers may affect key and value lookup behavior.</remarks>
 320    /// <param name="comparer1">The equality comparer to use for keys of type T1. Cannot be null.</param>
 321    /// <param name="comparer2">The equality comparer to use for values of type T2. Cannot be null.</param>
 322    public void SetComparer(IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comparer2)
 2323    {
 2324        SwiftThrowHelper.ThrowIfNull(comparer1, nameof(comparer1));
 2325        SwiftThrowHelper.ThrowIfNull(comparer2, nameof(comparer2));
 2326        if (ReferenceEquals(comparer1, _comparer) && ReferenceEquals(comparer2, _reverseComparer))
 0327            return;
 328
 2329        lock (ReverseSyncRoot)
 2330        {
 2331            var newReverseMap = new SwiftDictionary<T2, T1>(Count, comparer2);
 26332            foreach (var kv in this)
 10333                newReverseMap.Add(kv.Value, kv.Key);
 334
 2335            _reverseMap = newReverseMap;
 2336            _reverseComparer = comparer2;
 337
 2338            base.SetComparer(comparer1);
 2339        }
 2340    }
 341
 342    /// <summary>
 343    /// Attempts to get the key associated with the specified value.
 344    /// </summary>
 345    /// <param name="value">The value whose associated key is to be retrieved.</param>
 346    /// <param name="key">When this method returns, contains the key associated with the specified value, if the key is 
 347    /// <returns><c>true</c> if the key was found; otherwise, <c>false</c>.</returns>
 348    public bool TryGetKey(T2 value, out T1 key)
 18349    {
 18350        SwiftThrowHelper.ThrowIfNull(value, nameof(value));
 351
 18352        lock (ReverseSyncRoot)
 18353            return _reverseMap.TryGetValue(value, out key);
 18354    }
 355
 356    /// <summary>
 357    /// Gets the key associated with the specified value.
 358    /// </summary>
 359    /// <param name="value">The value whose associated key is to be retrieved.</param>
 360    /// <returns>The key associated with the specified value.</returns>
 361    /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="value"/> does not exist in 
 362    public T1 GetKey(T2 value)
 2363    {
 2364        SwiftThrowHelper.ThrowIfNull(value, nameof(value));
 365
 2366        lock (ReverseSyncRoot)
 2367            return _reverseMap[value];
 2368    }
 369
 370    /// <summary>
 371    /// Determines whether the <see cref="SwiftBiDictionary{T1, T2}"/> contains the specified value.
 372    /// </summary>
 373    /// <param name="value">The value to locate in the dictionary.</param>
 374    /// <returns><c>true</c> if the dictionary contains an element with the specified value; otherwise, <c>false</c>.</r
 375    public bool ContainsValue(T2 value)
 5376    {
 5377        SwiftThrowHelper.ThrowIfNull(value, nameof(value));
 378
 5379        lock (ReverseSyncRoot)
 5380            return _reverseMap.ContainsKey(value);
 5381    }
 382
 383    #endregion
 384}