/// <summary> /// Adds an item to the dictionary /// </summary> /// <param name="key">The key of the item to add</param> /// <param name="value">The value of the item to add</param> /// <returns>True if the item was added, otherwise false</returns> /// <remarks>Will add if the key is not set, and replace if the value is no longer available</remarks> public bool TryAdd(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)) { // Key not found, so let's try and add it // 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(true); // Success, return the result } // Key was added concurrently, free the handle we no longer need NewHandle.Dispose(); return(false); } // Key found, is the weak reference still valid? if (MyHandle.IsAlive) { return(false); // Yes, can't add } // Target reference has vanished, we can replace it with the new value 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(true); } // Key was updated elsewhere. Could have been removed or simply updated with a valid value elsewhere // Ditch the updated value and try again NewHandle.Dispose(); } }
//**************************************** /// <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 } }
/// <summary> /// Updates an item in the dictionary /// </summary> /// <param name="key">The key of the item to update</param> /// <param name="newValue">The new value of the item</param> /// <param name="oldValue">The old value we expect to replace</param> /// <returns>True if the key exists and old value was replaced with new value, otherwise false</returns> /// <remarks>Will return false if the reference has expired</remarks> public bool TryUpdate(TKey key, TValue newValue, TValue oldValue) { //**************************************** GCReference NewHandle; TValue?OldValue; //**************************************** if (newValue == null) { throw new ArgumentNullException(nameof(newValue), "Cannot add null to a Weak Dictionary"); } if (oldValue == null) { throw new ArgumentNullException(nameof(oldValue), "Cannot have a null in a Weak Dictionary"); } for (; ;) { // Is this key already in the dictionary? if (!_Dictionary.TryGetValue(key, out var MyHandle)) { return(false); // No, update fails } try { OldValue = (TValue?)MyHandle.Target; } catch (InvalidOperationException) { // The GCHandle was disposed, try again continue; } // Yes, is it what we expected? if (!object.ReferenceEquals(oldValue, OldValue)) { return(false); // No, update fails } // Yes. If the old and new references are the same, no need to change anything // Check now, rather than earlier, so we can return true if it was 'updated' correctly if (object.ReferenceEquals(OldValue, newValue)) { return(true); } // Reference has changed, create a new GCReference NewHandle = new GCReference(newValue, _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(true); } // Key was updated elsewhere, ditch the updated value and try again NewHandle.Dispose(); } }
/// <summary> /// Updates an item if it exists in the dictionary /// </summary> /// <param name="key">The key of the item to update</param> /// <param name="updateCallback">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 or the reference expired</returns> public bool TryUpdate(TKey key, Func <TKey, TValue, TValue> updateCallback, #if !NETSTANDARD2_0 && !NET40 [MaybeNullWhen(false)] #endif out TValue newValue) { //**************************************** GCReference NewHandle; TValue?OldValue; TValue NewValue; //**************************************** for (; ;) { // Is this key already in the dictionary? if (!_Dictionary.TryGetValue(key, out var MyHandle)) { newValue = null !; return(false); // No, update fails } try { OldValue = (TValue?)MyHandle.Target; } catch (InvalidOperationException) { // The GCHandle was disposed, try again continue; } // Yes, is the reference still valid? if (OldValue == null) { newValue = null !; return(false); // No, update fails } // Yes, update it NewValue = updateCallback(key, OldValue); if (NewValue == null) { throw new InvalidOperationException("Cannot add null to a Weak Dictionary"); } // Yes. If the old and new references are the same, no need to change anything // Check now, rather than earlier, so we can return true if it was 'updated' correctly if (object.ReferenceEquals(OldValue, NewValue)) { newValue = NewValue; return(true); } // Reference has changed, create a new GCReference NewHandle = new GCReference(NewValue, _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(); newValue = NewValue; return(true); } // Key was updated elsewhere, ditch the updated value and try again NewHandle.Dispose(); } }
/// <summary> /// Adds or retrieves a value based on the key /// </summary> /// <param name="key">The key to add or retrieve</param> /// <param name="value">A value to associate if the key doesn't exist</param> /// <param name="wasAdded">Receives whether the value was added or simply retrieved</param> /// <returns>The new or existing value</returns> public TValue GetOrAdd(TKey key, TValue value, out bool wasAdded) { //**************************************** 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 MyValue = (TValue?)MyHandle.Target; // Yes, does the target still exist? if (MyValue != null) { wasAdded = false; return(MyValue); // Yes, return it } } catch (InvalidOperationException) { // The GCHandle was disposed, try again continue; } // Target reference has vanished, replace it with the new value 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(); wasAdded = true; return(value); } // Key was updated elsewhere, ditch the updated value and try again NewHandle.Dispose(); continue; } // Key not found, so let's try and add it // 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)) { wasAdded = true; return(value); // Success, return the result } // Key was added concurrently, free the handle we no longer need NewHandle.Dispose(); // Loop back and try again } }
/// <summary> /// Adds or updates a key/value pair /// </summary> /// <param name="key">The key to add or replace</param> /// <param name="valueCallback">A callback providing the new value</param> /// <param name="updateCallback">A callback updating an existing value</param> /// <returns>The added or updated value</returns> public TValue AddOrUpdate(TKey key, Func <TKey, TValue> valueCallback, Func <TKey, TValue, TValue> updateCallback) { //**************************************** GCReference NewHandle; TValue?OldValue; TValue NewValue; //**************************************** for (; ;) { // Is this key already in the dictionary? if (_Dictionary.TryGetValue(key, out var MyHandle)) { try { OldValue = (TValue?)MyHandle.Target; } catch (InvalidOperationException) { // The GCHandle was disposed, try again continue; } // Yes, does the target still exist? if (OldValue != null) { // Yes, let's try and update it NewValue = updateCallback(key, OldValue); // If the reference is the same, no need to change anything if (object.ReferenceEquals(OldValue, NewValue)) { return(NewValue); } } else { // Target reference has vanished, replace it with the new value NewValue = valueCallback(key); if (NewValue == null) { throw new InvalidOperationException("Cannot add null to a Weak Dictionary"); } } // Reference has changed, create a new GCReference NewHandle = new GCReference(NewValue, _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(NewValue); } // Key was updated elsewhere, ditch the updated value and try again NewHandle.Dispose(); continue; } // Key not found, so let's try and add it NewValue = valueCallback(key); if (NewValue == null) { throw new InvalidOperationException("Cannot add null to a Weak Dictionary"); } // Create a GC Handle to reference the object NewHandle = new GCReference(NewValue, _HandleType); // Try and add it to the dictionary if (_Dictionary.TryAdd(key, NewHandle)) { return(NewValue); // Success, return the result } // Key was added concurrently, free the handle we no longer need NewHandle.Dispose(); // Loop back and try again } }