< Summary

Information
Class: SwiftCollections.SwiftBiDictionary<T1, T2>
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftBiDictionary.cs
Line coverage
98%
Covered lines: 110
Uncovered lines: 2
Coverable lines: 112
Total lines: 412
Line coverage: 98.2%
Branch coverage
92%
Covered branches: 35
Total branches: 38
Branch coverage: 92.1%
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(...)100%66100%
get_State()100%11100%
set_State(...)100%22100%
Remove(...)100%66100%
Add(...)100%66100%
InsertIfNotExist(...)75%4487.5%
Remove(...)100%44100%
Clear()100%11100%
SetComparer(...)66.66%6692.3%
TryGetKey(...)100%11100%
GetKey(...)100%11100%
ContainsValue(...)100%11100%

File(s)

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

#LineLine coverage
 1using Chronicler;
 2using MemoryPack;
 3using System;
 4using System.Collections.Generic;
 5using System.Text.Json.Serialization;
 6
 7namespace SwiftCollections;
 8
 9/// <summary>
 10/// Represents a bidirectional dictionary that allows for efficient lookups in both directions,
 11/// mapping keys to values and values back to keys. Both keys and values must be unique to maintain
 12/// the integrity of the bidirectional relationship.
 13/// Inherits from <see cref="SwiftDictionary{TKey, TValue}"/> and maintains a reverse map for reverse lookups.
 14/// </summary>
 15/// <typeparam name="T1">The type of the keys in the forward dictionary.</typeparam>
 16/// <typeparam name="T2">The type of the values in the forward dictionary.</typeparam>
 17/// <remarks>
 18/// The comparer is not serialized. After deserialization the dictionary reverts
 19/// to the same default comparer selection used by a new instance for both the
 20/// forward and reverse maps. String keys use SwiftCollections' deterministic
 21/// default comparer. Object keys use a SwiftCollections comparer that hashes
 22/// strings deterministically, while other object-key determinism still depends
 23/// on the underlying key type's <see cref="object.GetHashCode()"/>
 24/// implementation. Other key types use <see cref="EqualityComparer{T1}.Default"/>
 25/// and <see cref="EqualityComparer{T2}.Default"/>.
 26///
 27/// If a custom comparer is required it can be reapplied using
 28/// <see cref="SetComparer(IEqualityComparer{T1}, IEqualityComparer{T2})"/>.
 29/// </remarks>
 30[Serializable]
 31[JsonConverter(typeof(StateJsonConverterFactory))]
 32[MemoryPackable]
 33public partial class SwiftBiDictionary<T1, T2> : SwiftDictionary<T1, T2>, IStateBacked<SwiftDictionaryState<T1, T2>>
 34    where T1 : notnull
 35    where T2 : notnull
 36{
 37    #region Fields
 38
 39    /// <summary>
 40    /// The reverse map for bidirectional lookup, mapping from <typeparamref name="T2"/> to <typeparamref name="T1"/>.
 41    /// </summary>
 42    private SwiftDictionary<T2, T1> _reverseMap;
 43
 44    /// <summary>
 45    /// The comparer used to determine equality of keys and to generate hash codes.
 46    /// </summary>
 47    [NonSerialized]
 48    private IEqualityComparer<T2> _reverseComparer;
 49
 50    /// <summary>
 51    /// An object used to synchronize access to the reverse map during serialization and deserialization.
 52    /// </summary>
 53    [NonSerialized]
 54    private object? _reverseSyncRoot;
 55
 56    #endregion
 57
 58    #region Constructors
 59
 60    /// <summary>
 61    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that is empty and uses the defau
 62    /// </summary>
 3463    public SwiftBiDictionary() : this(null, null) { }
 64
 65    /// <summary>
 66    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that is empty and uses the speci
 67    /// </summary>
 68    /// <param name="comparer1">The comparer to use for the keys.</param>
 69    /// <param name="comparer2">The comparer to use for the values.</param>
 2170    public SwiftBiDictionary(IEqualityComparer<T1>? comparer1, IEqualityComparer<T2>? comparer2) : base(DefaultCapacity,
 71    {
 2172        _reverseComparer = SwiftHashTools.GetDefaultEqualityComparer(comparer2);
 2173        _reverseMap = new SwiftDictionary<T2, T1>(DefaultCapacity, _reverseComparer);
 2174    }
 75
 76    /// <summary>
 77    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that contains elements copied fr
 78    /// </summary>
 79    /// <param name="dictionary">The dictionary whose elements are copied to the new <see cref="SwiftBiDictionary{T1, T2
 280    public SwiftBiDictionary(IDictionary<T1, T2> dictionary) : this(dictionary, null, null) { }
 81
 82    /// <summary>
 83    /// Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class that contains elements copied fr
 84    /// </summary>
 85    /// <param name="dictionary">The dictionary whose elements are copied to the new <see cref="SwiftBiDictionary{T1, T2
 86    /// <param name="comparer1">The comparer to use for the keys.</param>
 87    /// <param name="comparer2">The comparer to use for the values.</param>
 88    public SwiftBiDictionary(IDictionary<T1, T2> dictionary, IEqualityComparer<T1>? comparer1, IEqualityComparer<T2>? co
 289        : this(comparer1, comparer2)
 90    {
 291        SwiftThrowHelper.ThrowIfNull(dictionary, nameof(dictionary));
 92
 1293        foreach (var kvp in dictionary)
 494            Add(kvp.Key, kvp.Value);
 295    }
 96
 97    ///  <summary>
 98    ///  Initializes a new instance of the <see cref="SwiftBiDictionary{T1, T2}"/> class with the specified <see cref="S
 99    ///  </summary>
 100    ///  <param name="state">The state containing the internal array, count, offset, and version for initialization.</pa
 101    [MemoryPackConstructor]
 5102    public SwiftBiDictionary(SwiftDictionaryState<T1, T2> state)
 103    {
 5104        State = state;
 105
 5106        SwiftThrowHelper.ThrowIfNull(_reverseMap, nameof(_reverseMap));
 5107        SwiftThrowHelper.ThrowIfNull(_reverseComparer, nameof(_reverseComparer));
 5108    }
 109
 110    #endregion
 111
 112    #region Properties
 113
 114    /// <summary>
 115    /// Gets an object that can be used to synchronize access to the reverse collection.
 116    /// </summary>
 117    /// <remarks>
 118    /// Use this object to lock the reverse collection during multithreaded operations to ensure thread safety.
 119    /// This property is intended for advanced scenarios where manual synchronization is required.
 120    /// </remarks>
 121    [JsonIgnore]
 122    [MemoryPackIgnore]
 138123    public object ReverseSyncRoot => _reverseSyncRoot ??= new object();
 124
 125    /// <summary>
 126    /// Gets or sets the value associated with the specified key. Ensures that each value is unique within the collectio
 127    /// </summary>
 128    /// <remarks>
 129    /// Setting a value that already exists in the collection will result in an exception, as each value must be unique.
 130    /// If the key does not exist, a new key-value pair is added.
 131    /// If the key exists and the value is unchanged, no operation is performed.
 132    /// </remarks>
 133    /// <param name="key">The key whose value to get or set. Cannot be null.</param>
 134    /// <returns>The value associated with the specified key.</returns>
 135    /// <exception cref="ArgumentException">Thrown when the specified value already exists in the collection.</exception
 136    [JsonIgnore]
 137    [MemoryPackIgnore]
 138    public new T2 this[T1 key]
 139    {
 12140        get => base[key];
 141        set
 142        {
 5143            SwiftThrowHelper.ThrowIfNullGeneric(key, nameof(key));
 144
 5145            lock (ReverseSyncRoot)
 146            {
 5147                if (TryGetValue(key, out T2 oldValue))
 148                {
 149                    // No change
 4150                    if (_reverseComparer.Equals(oldValue, value))
 1151                        return;
 152
 153                    // Prevent duplicate values
 3154                    SwiftThrowHelper.ThrowIfArgument(_reverseMap.ContainsKey(value), nameof(value), "Value already exist
 155
 156                    // Remove old reverse mapping
 2157                    _reverseMap.Remove(oldValue);
 158
 159                    // Update forward value
 2160                    int index = FindEntry(key);
 2161                    _entries[index].Value = value;
 162
 163                    // Insert new reverse mapping
 2164                    _reverseMap.Add(value, key);
 165
 2166                    _version++;
 167                }
 168                else
 169                {
 170                    // New insert
 1171                    SwiftThrowHelper.ThrowIfArgument(_reverseMap.ContainsKey(value), nameof(value), "Value already exist
 172
 1173                    CheckLoadThreshold();
 174
 1175                    bool added = InsertIfNotExist(key, value);
 176
 1177                    if (added)
 1178                        _reverseMap.Add(value, key);
 179                }
 1180            }
 4181        }
 182    }
 183
 184    /// <summary>
 185    /// Gets or sets the current state of the dictionary, including its items and configuration.
 186    /// </summary>
 187    /// <remarks>
 188    /// Setting this property updates the internal reverse mapping and comparer to reflect the new state.
 189    /// The reverse mapping is rebuilt to ensure consistency with the updated state.
 190    /// This property is intended for serialization and deserialization scenarios.
 191    /// </remarks>
 192    [JsonInclude]
 193    [MemoryPackInclude]
 194    public new SwiftDictionaryState<T1, T2> State
 195    {
 5196        get => base.State;
 197        internal set
 198        {
 5199            _reverseComparer = SwiftHashTools.GetDefaultEqualityComparer<T2>();
 5200            _reverseMap = new SwiftDictionary<T2, T1>(value.Items.Length, _reverseComparer);
 201
 5202            base.State = value;
 203
 5204            lock (ReverseSyncRoot)
 205            {
 5206                _reverseMap.Clear();
 207
 42208                foreach (var kv in this)
 16209                    _reverseMap.Add(kv.Value, kv.Key);
 210            }
 5211        }
 212    }
 213
 214    #endregion
 215
 216    #region Collection Manipulation
 217
 218    /// <summary>
 219    /// Removes the key-value pair from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 220    /// Also removes the corresponding value-key pair from the reverse map.
 221    /// </summary>
 222    /// <param name="key">The key of the element to remove.</param>
 223    /// <param name="value">The value of the element to remove.</param>
 224    /// <returns><c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.</returns>
 225    public bool Remove(T1 key, T2 value)
 226    {
 2227        SwiftThrowHelper.ThrowIfNullGeneric(key, nameof(key));
 2228        SwiftThrowHelper.ThrowIfNullGeneric(value, nameof(value));
 229
 2230        if (TryGetValue(key, out T2 existingValue))
 231        {
 2232            if (_reverseComparer.Equals(existingValue, value))
 233            {
 1234                bool removed = base.Remove(key);
 1235                if (removed)
 236                {
 1237                    lock (ReverseSyncRoot)
 1238                        _reverseMap.Remove(value);
 239                }
 1240                return removed;
 241            }
 242        }
 1243        return false;
 244    }
 245
 246    #region Overrides
 247
 248    /// <summary>
 249    /// Adds the specified key and value to the <see cref="SwiftBiDictionary{T1, T2}"/>.
 250    /// Also adds the value-key pair to the reverse map.
 251    /// </summary>
 252    /// <param name="key">The key of the element to add.</param>
 253    /// <param name="value">The value of the element to add.</param>
 254    /// <exception cref="ArgumentException">An element with the same key or value already exists.</exception>
 255    public override bool Add(T1 key, T2 value)
 256    {
 42257        SwiftThrowHelper.ThrowIfNullGeneric(key, nameof(key));
 42258        SwiftThrowHelper.ThrowIfNullGeneric(value, nameof(value));
 259
 42260        if (ContainsKey(key))
 1261            return false;
 262
 41263        lock (ReverseSyncRoot)
 264        {
 41265            if (_reverseMap.ContainsKey(value))
 1266                return false;
 267
 40268            bool added = base.Add(key, value);
 269
 40270            if (added)
 40271                _reverseMap.Add(value, key);
 272
 40273            return added;
 274        }
 41275    }
 276
 277    /// <summary>
 278    /// Overrides the base class's <see cref="SwiftDictionary{TKey, TValue}.InsertIfNotExist"/> method to ensure synchro
 279    /// </summary>
 280    /// <param name="key">The key to insert.</param>
 281    /// <param name="value">The value to insert.</param>
 282    /// <returns><c>true</c> if a new entry was added; otherwise, <c>false</c>.</returns>
 283    internal override bool InsertIfNotExist(T1 key, T2 value)
 284    {
 57285        lock (ReverseSyncRoot)
 286        {
 57287            if (_reverseMap.ContainsKey(value))
 0288                return false;
 289
 57290            bool result = base.InsertIfNotExist(key, value);
 291
 57292            if (result)
 57293                _reverseMap.Add(value, key);
 294
 57295            return result;
 296        }
 57297    }
 298
 299    /// <summary>
 300    /// Removes the value with the specified key from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 301    /// Also removes the corresponding key from the reverse map.
 302    /// </summary>
 303    /// <param name="key">The key of the element to remove.</param>
 304    /// <returns><c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.</returns>
 305    public override bool Remove(T1 key)
 306    {
 2307        SwiftThrowHelper.ThrowIfNullGeneric(key, nameof(key));
 308
 2309        if (TryGetValue(key, out T2 value))
 310        {
 1311            bool removed = base.Remove(key);
 312
 1313            if (removed)
 314            {
 1315                lock (ReverseSyncRoot)
 1316                    _reverseMap.Remove(value);
 317            }
 318
 1319            return removed;
 320        }
 321
 1322        return false;
 323    }
 324
 325    /// <summary>
 326    /// Removes all keys and values from the <see cref="SwiftBiDictionary{T1, T2}"/>.
 327    /// Also clears the reverse map.
 328    /// </summary>
 329    public override void Clear()
 330    {
 1331        base.Clear();
 1332        lock (ReverseSyncRoot)
 1333            _reverseMap.Clear();
 1334    }
 335
 336    #endregion
 337
 338    #endregion
 339
 340    #region Utility Methods
 341
 342    /// <summary>
 343    /// Sets the comparers used to determine equality for keys and values in the dictionary.
 344    /// </summary>
 345    /// <remarks>This method updates the internal comparers and rebuilds the reverse mapping using the
 346    /// specified value comparer. The operation is thread-safe and locks the internal state during the update. Changing
 347    /// comparers may affect key and value lookup behavior.</remarks>
 348    /// <param name="comparer1">The equality comparer to use for keys of type T1. Cannot be null.</param>
 349    /// <param name="comparer2">The equality comparer to use for values of type T2. Cannot be null.</param>
 350    public void SetComparer(IEqualityComparer<T1> comparer1, IEqualityComparer<T2> comparer2)
 351    {
 2352        SwiftThrowHelper.ThrowIfNull(comparer1, nameof(comparer1));
 2353        SwiftThrowHelper.ThrowIfNull(comparer2, nameof(comparer2));
 2354        if (ReferenceEquals(comparer1, _comparer) && ReferenceEquals(comparer2, _reverseComparer))
 0355            return;
 356
 2357        lock (ReverseSyncRoot)
 358        {
 2359            var newReverseMap = new SwiftDictionary<T2, T1>(Count, comparer2);
 24360            foreach (var kv in this)
 10361                newReverseMap.Add(kv.Value, kv.Key);
 362
 2363            _reverseMap = newReverseMap;
 2364            _reverseComparer = comparer2;
 365
 2366            base.SetComparer(comparer1);
 2367        }
 2368    }
 369
 370    /// <summary>
 371    /// Attempts to get the key associated with the specified value.
 372    /// </summary>
 373    /// <param name="value">The value whose associated key is to be retrieved.</param>
 374    /// <param name="key">When this method returns, contains the key associated with the specified value, if the key is 
 375    /// <returns><c>true</c> if the key was found; otherwise, <c>false</c>.</returns>
 376    public bool TryGetKey(T2 value, out T1 key)
 377    {
 18378        SwiftThrowHelper.ThrowIfNullGeneric(value, nameof(value));
 379
 18380        lock (ReverseSyncRoot)
 18381            return _reverseMap.TryGetValue(value, out key);
 18382    }
 383
 384    /// <summary>
 385    /// Gets the key associated with the specified value.
 386    /// </summary>
 387    /// <param name="value">The value whose associated key is to be retrieved.</param>
 388    /// <returns>The key associated with the specified value.</returns>
 389    /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="value"/> does not exist in 
 390    public T1 GetKey(T2 value)
 391    {
 2392        SwiftThrowHelper.ThrowIfNullGeneric(value, nameof(value));
 393
 2394        lock (ReverseSyncRoot)
 2395            return _reverseMap[value];
 2396    }
 397
 398    /// <summary>
 399    /// Determines whether the <see cref="SwiftBiDictionary{T1, T2}"/> contains the specified value.
 400    /// </summary>
 401    /// <param name="value">The value to locate in the dictionary.</param>
 402    /// <returns><c>true</c> if the dictionary contains an element with the specified value; otherwise, <c>false</c>.</r
 403    public bool ContainsValue(T2 value)
 404    {
 5405        SwiftThrowHelper.ThrowIfNullGeneric(value, nameof(value));
 406
 5407        lock (ReverseSyncRoot)
 5408            return _reverseMap.ContainsKey(value);
 5409    }
 410
 411    #endregion
 412}