Skip to content

Commit 51316c2

Browse files
authored
Implement 'ConditionalWeakTable<TKey,TValue>.Remove' API (#112263)
1 parent 0517b16 commit 51316c2

File tree

3 files changed

+104
-20
lines changed

3 files changed

+104
-20
lines changed

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@ public ConditionalWeakTable()
4848
/// If the key is not found, contains default(TValue).
4949
/// </param>
5050
/// <returns>Returns "true" if key was found, "false" otherwise.</returns>
51-
/// <remarks>
52-
/// The key may get garbage collected during the TryGetValue operation. If so, TryGetValue
53-
/// may at its discretion, return "false" and set "value" to the default (as if the key was not present.)
54-
/// </remarks>
51+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
5552
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
5653
{
5754
if (key is null)
@@ -65,12 +62,8 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
6562
/// <summary>Adds a key to the table.</summary>
6663
/// <param name="key">key to add. May not be null.</param>
6764
/// <param name="value">value to associate with key.</param>
68-
/// <remarks>
69-
/// If the key is already entered into the dictionary, this method throws an exception.
70-
/// The key may get garbage collected during the Add() operation. If so, Add()
71-
/// has the right to consider any prior entries successfully removed and add a new entry without
72-
/// throwing an exception.
73-
/// </remarks>
65+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
66+
/// <exception cref="ArgumentException"><paramref name="key"/> is already entered into the dictionary.</exception>
7467
public void Add(TKey key, TValue value)
7568
{
7669
if (key is null)
@@ -94,6 +87,7 @@ public void Add(TKey key, TValue value)
9487
/// <param name="key">The key to add.</param>
9588
/// <param name="value">The key's property value.</param>
9689
/// <returns>true if the key/value pair was added; false if the table already contained the key.</returns>
90+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
9791
public bool TryAdd(TKey key, TValue value)
9892
{
9993
if (key is null)
@@ -117,6 +111,7 @@ public bool TryAdd(TKey key, TValue value)
117111
/// <summary>Adds the key and value if the key doesn't exist, or updates the existing key's value if it does exist.</summary>
118112
/// <param name="key">key to add or update. May not be null.</param>
119113
/// <param name="value">value to associate with key.</param>
114+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
120115
public void AddOrUpdate(TKey key, TValue value)
121116
{
122117
if (key is null)
@@ -141,13 +136,9 @@ public void AddOrUpdate(TKey key, TValue value)
141136
}
142137

143138
/// <summary>Removes a key and its value from the table.</summary>
144-
/// <param name="key">key to remove. May not be null.</param>
145-
/// <returns>true if the key is found and removed. Returns false if the key was not in the dictionary.</returns>
146-
/// <remarks>
147-
/// The key may get garbage collected during the Remove() operation. If so,
148-
/// Remove() will not fail or throw, however, the return value can be either true or false
149-
/// depending on who wins the race.
150-
/// </remarks>
139+
/// <param name="key">The key to remove.</param>
140+
/// <returns><see langword="true"/> if the key is found and removed; otherwise, <see langword="false"/>.</returns>
141+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
151142
public bool Remove(TKey key)
152143
{
153144
if (key is null)
@@ -157,7 +148,25 @@ public bool Remove(TKey key)
157148

158149
lock (_lock)
159150
{
160-
return _container.Remove(key);
151+
return _container.Remove(key, out _);
152+
}
153+
}
154+
155+
/// <summary>Removes a key and its value from the table, and returns the removed value if it was present.</summary>
156+
/// <param name="key">The key to remove.</param>
157+
/// <param name="value">value removed from the table, if it was present.</param>
158+
/// <returns><see langword="true"/> if the key is found and removed; otherwise, <see langword="false"/>.</returns>
159+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
160+
public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
161+
{
162+
if (key is null)
163+
{
164+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
165+
}
166+
167+
lock (_lock)
168+
{
169+
return _container.Remove(key, out value);
161170
}
162171
}
163172

@@ -692,17 +701,19 @@ internal void RemoveAllKeys()
692701
}
693702

694703
/// <summary>Removes the specified key from the table, if it exists.</summary>
695-
internal bool Remove(TKey key)
704+
internal bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
696705
{
697706
VerifyIntegrity();
698707

699-
int entryIndex = FindEntry(key, out _);
708+
int entryIndex = FindEntry(key, out object? valueObject);
700709
if (entryIndex != -1)
701710
{
702711
RemoveIndex(entryIndex);
712+
value = Unsafe.As<TValue>(valueObject);
703713
return true;
704714
}
705715

716+
value = null;
706717
return false;
707718
}
708719

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13284,6 +13284,7 @@ public void Clear() { }
1328413284
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
1328513285
public TValue GetValue(TKey key, System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback) { throw null; }
1328613286
public bool Remove(TKey key) { throw null; }
13287+
public bool Remove(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
1328713288
System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<TKey, TValue>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>.GetEnumerator() { throw null; }
1328813289
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
1328913290
public bool TryAdd(TKey key, TValue value) { throw null; }

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/ConditionalWeakTableTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static void InvalidArgs_Throws()
2222
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.Add(null, new object())); // null key
2323
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.TryGetValue(null, out ignored)); // null key
2424
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.Remove(null)); // null key
25+
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.Remove(null, out _)); // null key
2526
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.GetOrAdd(null, new object())); // null key
2627
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.GetOrAdd(null, k => new object())); // null key
2728
AssertExtensions.Throws<ArgumentNullException>("key", () => cwt.GetOrAdd(null, (k, a) => new object(), 42)); // null key
@@ -171,6 +172,39 @@ public static void AddMany_ThenRemoveAll(int numObjects)
171172
}
172173
}
173174

175+
[Theory]
176+
[InlineData(1)]
177+
[InlineData(100)]
178+
public static void AddMany_ThenRemoveAll_ValidateRemovedValue(int numObjects)
179+
{
180+
object[] keys = Enumerable.Range(0, numObjects).Select(_ => new object()).ToArray();
181+
object[] values = Enumerable.Range(0, numObjects).Select(_ => new object()).ToArray();
182+
var cwt = new ConditionalWeakTable<object, object>();
183+
184+
for (int i = 0; i < numObjects; i++)
185+
{
186+
cwt.Add(keys[i], values[i]);
187+
}
188+
189+
for (int i = 0; i < numObjects; i++)
190+
{
191+
Assert.Same(values[i], cwt.GetValue(keys[i], _ => new object()));
192+
}
193+
194+
for (int i = 0; i < numObjects; i++)
195+
{
196+
Assert.True(cwt.Remove(keys[i], out var value));
197+
Assert.False(cwt.Remove(keys[i], out _));
198+
Assert.Same(values[i], value);
199+
}
200+
201+
for (int i = 0; i < numObjects; i++)
202+
{
203+
object ignored;
204+
Assert.False(cwt.TryGetValue(keys[i], out ignored));
205+
}
206+
}
207+
174208
[Theory]
175209
[InlineData(100)]
176210
public static void AddRemoveIteratively(int numObjects)
@@ -188,6 +222,24 @@ public static void AddRemoveIteratively(int numObjects)
188222
}
189223
}
190224

225+
[Theory]
226+
[InlineData(100)]
227+
public static void AddRemoveIteratively_ValidateRemovedValue(int numObjects)
228+
{
229+
object[] keys = Enumerable.Range(0, numObjects).Select(_ => new object()).ToArray();
230+
object[] values = Enumerable.Range(0, numObjects).Select(_ => new object()).ToArray();
231+
var cwt = new ConditionalWeakTable<object, object>();
232+
233+
for (int i = 0; i < numObjects; i++)
234+
{
235+
cwt.Add(keys[i], values[i]);
236+
Assert.Same(values[i], cwt.GetValue(keys[i], _ => new object()));
237+
Assert.True(cwt.Remove(keys[i], out var value));
238+
Assert.False(cwt.Remove(keys[i], out _));
239+
Assert.Same(values[i], value);
240+
}
241+
}
242+
191243
[Fact]
192244
public static void Concurrent_AddMany_DropReferences() // no asserts, just making nothing throws
193245
{
@@ -218,6 +270,26 @@ public static void Concurrent_Add_Read_Remove_DifferentObjects()
218270
});
219271
}
220272

273+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
274+
public static void Concurrent_Add_Read_Remove_ValidateRemovedValue_DifferentObjects()
275+
{
276+
var cwt = new ConditionalWeakTable<object, object>();
277+
DateTime end = DateTime.UtcNow + TimeSpan.FromSeconds(0.25);
278+
Parallel.For(0, Environment.ProcessorCount, i =>
279+
{
280+
while (DateTime.UtcNow < end)
281+
{
282+
object key = new object();
283+
object value = new object();
284+
cwt.Add(key, value);
285+
Assert.Same(value, cwt.GetValue(key, _ => new object()));
286+
Assert.True(cwt.Remove(key, out var removedValue));
287+
Assert.False(cwt.Remove(key, out _));
288+
Assert.Same(value, removedValue);
289+
}
290+
});
291+
}
292+
221293
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
222294
public static void Concurrent_GetOrAdd_Add_Read_Remove_DifferentObjects()
223295
{

0 commit comments

Comments
 (0)