/// <summary> /// Updates an item if it exists in the dictionary /// </summary> /// <param name="target">The target concurrent dictionary</param> /// <param name="key">The key of the item to the update</param> /// <param name="update">A callback that performs the update</param> /// <param name="newValue">Receives the new value if the dictionary was updated</param> /// <returns>True if the item was updated, False if it does not exist</returns> /// <remarks>Implements a loop around TryGetValue and TryUpdate</remarks> public static bool TryUpdate <TKey, TValue>(this ConcurrentDictionary <TKey, TValue> target, TKey key, Func <TKey, TValue, TValue> update, #if !NETSTANDARD2_0 && !NET40 [MaybeNullWhen(false)] #endif out TValue newValue) where TKey : notnull { while (target.TryGetValue(key, out var OldValue)) { var NewValue = update(key, OldValue); if (target.TryUpdate(key, NewValue, OldValue)) { newValue = NewValue; return(true); } } newValue = default !;
//**************************************** /// <summary> /// Adds or replaces a key/value pair /// </summary> /// <param name="key">The key to add or replace</param> /// <param name="value">The value to associate with the key</param> public void AddOrReplace(TKey key, TValue value) { //**************************************** GCReference NewHandle; //**************************************** if (value == null) { throw new InvalidOperationException("Cannot add null to a Weak Dictionary"); } for (; ;) { // Is this key already in the dictionary? if (_Dictionary.TryGetValue(key, out var MyHandle)) { try { var OldValue = (TValue?)MyHandle.Target; // Yes. If the reference is the same, no need to change anything if (object.ReferenceEquals(OldValue, value)) { return; } } catch (InvalidOperationException) { // The GCHandle was disposed, try again continue; } // Reference has changed, create a new GCReference NewHandle = new GCReference(value, _HandleType); // Try and update the dictionary with the replacement value if (_Dictionary.TryUpdate(key, NewHandle, MyHandle)) { // Success, now we can safely expire the old handle MyHandle.Dispose(); return; } // Key was updated elsewhere, ditch the updated value and try again NewHandle.Dispose(); continue; } // Create a GC Handle to reference the object NewHandle = new GCReference(value, _HandleType); // Try and add it to the dictionary if (_Dictionary.TryAdd(key, NewHandle)) { return; // Success, return the result } // Key was added concurrently, free the handle we no longer need NewHandle.Dispose(); // Loop back and try again } }