Skip to content

Commit 251ef76

Browse files
Add TryAdd and TryGetValue overloads with out int index to OrderedDictionary (#109324)
Add TryAdd and TryGetValue overloads with out var index
1 parent 3218072 commit 251ef76

File tree

4 files changed

+142
-21
lines changed

4 files changed

+142
-21
lines changed

src/libraries/System.Collections/ref/System.Collections.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ void System.Collections.IList.Remove(object? value) { }
174174
public void TrimExcess() { }
175175
public void TrimExcess(int capacity) { }
176176
public bool TryAdd(TKey key, TValue value) { throw null; }
177+
public bool TryAdd(TKey key, TValue value, out int index) { throw null; }
177178
public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
179+
public bool TryGetValue(TKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value, out int index) { throw null; }
178180
public partial struct Enumerator : System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<TKey, TValue>>, System.Collections.IDictionaryEnumerator, System.Collections.IEnumerator, System.IDisposable
179181
{
180182
private object _dummy;

src/libraries/System.Collections/src/System/Collections/Generic/OrderedDictionary.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ public TValue this[TKey key]
377377
{
378378
ThrowIfNull(key);
379379

380-
bool modified = TryInsert(index: -1, key, value, InsertionBehavior.OverwriteExisting);
380+
bool modified = TryInsert(index: -1, key, value, InsertionBehavior.OverwriteExisting, out _);
381381
Debug.Assert(modified);
382382
}
383383
}
@@ -392,8 +392,9 @@ public TValue this[TKey key]
392392
/// - OverwriteExisting: If the key already exists, overwrites its value with the specified value, e.g. this[key] = value
393393
/// - ThrowOnExisting: If the key already exists, throws an exception, e.g. Add(key, value)
394394
/// </param>
395+
/// <param name="keyIndex">The index of the added or existing key. This is always a valid index into the dictionary.</param>
395396
/// <returns>true if the collection was updated; otherwise, false.</returns>
396-
private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior behavior)
397+
private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior behavior, out int keyIndex)
397398
{
398399
// Search for the key in the dictionary.
399400
uint hashCode = 0, collisionCount = 0;
@@ -402,6 +403,9 @@ private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior beha
402403
// Handle the case where the key already exists, based on the requested behavior.
403404
if (i >= 0)
404405
{
406+
keyIndex = i;
407+
Debug.Assert(0 <= keyIndex && keyIndex < _count);
408+
405409
Debug.Assert(_entries is not null);
406410

407411
switch (behavior)
@@ -467,6 +471,9 @@ private bool TryInsert(int index, TKey key, TValue value, InsertionBehavior beha
467471

468472
RehashIfNecessary(collisionCount, entries);
469473

474+
keyIndex = index;
475+
Debug.Assert(0 <= keyIndex && keyIndex < _count);
476+
470477
return true;
471478
}
472479

@@ -479,19 +486,27 @@ public void Add(TKey key, TValue value)
479486
{
480487
ThrowIfNull(key);
481488

482-
TryInsert(index: -1, key, value, InsertionBehavior.ThrowOnExisting);
489+
TryInsert(index: -1, key, value, InsertionBehavior.ThrowOnExisting, out _);
483490
}
484491

485492
/// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
486493
/// <param name="key">The key of the element to add.</param>
487494
/// <param name="value">The value of the element to add. The value can be null for reference types.</param>
488495
/// <exception cref="ArgumentNullException">key is null.</exception>
489496
/// <returns>true if the key didn't exist and the key and value were added to the dictionary; otherwise, false.</returns>
490-
public bool TryAdd(TKey key, TValue value)
497+
public bool TryAdd(TKey key, TValue value) => TryAdd(key, value, out _);
498+
499+
/// <summary>Adds the specified key and value to the dictionary if the key doesn't already exist.</summary>
500+
/// <param name="key">The key of the element to add.</param>
501+
/// <param name="value">The value of the element to add. The value can be null for reference types.</param>
502+
/// <param name="index">The index of the added or existing <paramref name="key"/>. This is always a valid index into the dictionary.</param>
503+
/// <exception cref="ArgumentNullException">key is null.</exception>
504+
/// <returns>true if the key didn't exist and the key and value were added to the dictionary; otherwise, false.</returns>
505+
public bool TryAdd(TKey key, TValue value, out int index)
491506
{
492507
ThrowIfNull(key);
493508

494-
return TryInsert(index: -1, key, value, InsertionBehavior.IgnoreInsertion);
509+
return TryInsert(index: -1, key, value, InsertionBehavior.IgnoreInsertion, out index);
495510
}
496511

497512
/// <summary>Adds each element of the enumerable to the dictionary.</summary>
@@ -714,7 +729,7 @@ public void Insert(int index, TKey key, TValue value)
714729

715730
ThrowIfNull(key);
716731

717-
TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting);
732+
TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting, out _);
718733
}
719734

720735
/// <summary>Removes the value with the specified key from the <see cref="OrderedDictionary{TKey, TValue}"/>.</summary>
@@ -896,12 +911,22 @@ public void TrimExcess(int capacity)
896911
/// otherwise, the default value for the type of the value parameter.
897912
/// </param>
898913
/// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
899-
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
914+
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => TryGetValue(key, out value, out _);
915+
916+
/// <summary>Gets the value associated with the specified key.</summary>
917+
/// <param name="key">The key of the value to get.</param>
918+
/// <param name="value">
919+
/// When this method returns, contains the value associated with the specified key, if the key is found;
920+
/// otherwise, the default value for the type of the value parameter.
921+
/// </param>
922+
/// <param name="index">The index of <paramref name="key"/> if found; otherwise, -1.</param>
923+
/// <returns>true if the <see cref="OrderedDictionary{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns>
924+
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value, out int index)
900925
{
901926
ThrowIfNull(key);
902927

903928
// Find the key.
904-
int index = IndexOf(key);
929+
index = IndexOf(key);
905930
if (index >= 0)
906931
{
907932
// It exists. Return its value.

src/libraries/System.Collections/tests/Generic/OrderedDictionary/OrderedDictionary.Generic.Tests.cs

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public void OrderedDictionary_Generic_Constructor_AllKeysEqualComparer()
139139
#endregion
140140

141141
#region TryAdd
142+
142143
[Fact]
143144
public void TryAdd_NullKeyThrows()
144145
{
@@ -148,29 +149,30 @@ public void TryAdd_NullKeyThrows()
148149
}
149150

150151
var dictionary = new OrderedDictionary<TKey, TValue>();
152+
int index;
153+
151154
AssertExtensions.Throws<ArgumentNullException>("key", () => dictionary.TryAdd(default(TKey), CreateTValue(0)));
155+
AssertExtensions.Throws<ArgumentNullException>("key", () => dictionary.TryAdd(default(TKey), CreateTValue(0), out index));
156+
152157
Assert.True(dictionary.TryAdd(CreateTKey(0), default));
153158
Assert.Equal(1, dictionary.Count);
159+
160+
Assert.True(dictionary.TryAdd(CreateTKey(1), default, out index));
161+
Assert.Equal(1, index);
162+
Assert.Equal(2, dictionary.Count);
154163
}
155164

156-
[Fact]
157-
public void TryAdd_AppendsItemToEndOfDictionary()
165+
[Theory]
166+
[MemberData(nameof(ValidCollectionSizes))]
167+
public void TryAdd_AppendsItemToEndOfDictionary(int count)
158168
{
159-
var dictionary = new OrderedDictionary<TKey, TValue>();
160-
AddToCollection(dictionary, 10);
169+
OrderedDictionary<TKey, TValue> dictionary = (OrderedDictionary<TKey, TValue>)GenericIDictionaryFactory(count);
161170
foreach (var entry in dictionary)
162171
{
163172
Assert.False(dictionary.TryAdd(entry.Key, entry.Value));
164173
}
165174

166-
TKey newKey;
167-
int i = 0;
168-
do
169-
{
170-
newKey = CreateTKey(i);
171-
}
172-
while (dictionary.ContainsKey(newKey));
173-
175+
TKey newKey = GetNewKey(dictionary);
174176
Assert.True(dictionary.TryAdd(newKey, CreateTValue(42)));
175177
Assert.Equal(dictionary.Count - 1, dictionary.IndexOf(newKey));
176178
}
@@ -187,6 +189,90 @@ public void TryAdd_ItemAlreadyExists_DoesNotInvalidateEnumerator()
187189

188190
Assert.True(valuesEnum.MoveNext());
189191
}
192+
193+
[Theory]
194+
[MemberData(nameof(ValidCollectionSizes))]
195+
public void TryAdd_Index_AppendsItemToEndOfDictionary(int count)
196+
{
197+
OrderedDictionary<TKey, TValue> dictionary = (OrderedDictionary<TKey, TValue>)GenericIDictionaryFactory(count);
198+
int index;
199+
200+
for (int i = 0; i < dictionary.Count; i++)
201+
{
202+
(TKey key, TValue value) = dictionary.GetAt(i);
203+
Assert.False(dictionary.TryAdd(key, value, out index));
204+
Assert.Equal(i, index);
205+
}
206+
207+
Assert.True(dictionary.TryAdd(GetNewKey(dictionary), CreateTValue(42), out index));
208+
Assert.Equal(dictionary.Count - 1, index);
209+
}
210+
211+
[Theory]
212+
[MemberData(nameof(ValidPositiveCollectionSizes))]
213+
public void TryAdd_NewItem_IndexCorrect(int count)
214+
{
215+
var dictionary = new OrderedDictionary<TKey, TValue>();
216+
217+
for (int i = 0; i < count; i++)
218+
{
219+
int index;
220+
TKey newKey = GetNewKey(dictionary);
221+
222+
Assert.True(dictionary.TryAdd(newKey, CreateTValue(i), out index));
223+
Assert.Equal(i, index);
224+
Assert.False(dictionary.TryAdd(newKey, CreateTValue(i), out index));
225+
Assert.Equal(i, index);
226+
}
227+
}
228+
229+
#endregion
230+
231+
#region TryGetValue
232+
233+
[Theory]
234+
[MemberData(nameof(ValidCollectionSizes))]
235+
public void TryGetValue_Index_NullKeyThrows(int count)
236+
{
237+
OrderedDictionary<TKey, TValue> dictionary = (OrderedDictionary<TKey, TValue>)GenericIDictionaryFactory(count);
238+
TValue outValue;
239+
int index;
240+
if (DefaultValueAllowed)
241+
{
242+
TKey missingKey = default(TKey);
243+
while (dictionary.ContainsKey(missingKey))
244+
dictionary.Remove(missingKey);
245+
Assert.False(dictionary.TryGetValue(missingKey, out outValue, out index));
246+
}
247+
else
248+
{
249+
Assert.Throws<ArgumentNullException>(() => dictionary.TryGetValue(default(TKey), out outValue, out index));
250+
}
251+
}
252+
253+
[Theory]
254+
[MemberData(nameof(ValidCollectionSizes))]
255+
public void TryGetValue_ValidKeyNotContainedInDictionary(int count)
256+
{
257+
OrderedDictionary<TKey, TValue> dictionary = (OrderedDictionary<TKey, TValue>)GenericIDictionaryFactory(count);
258+
TKey missingKey = GetNewKey(dictionary);
259+
Assert.False(dictionary.TryGetValue(missingKey, out _, out int index));
260+
Assert.Equal(-1, index);
261+
}
262+
263+
[Theory]
264+
[MemberData(nameof(ValidCollectionSizes))]
265+
public void TryGetValue_ValidKeyContainedInDictionary(int count)
266+
{
267+
OrderedDictionary<TKey, TValue>dictionary = (OrderedDictionary<TKey, TValue>)GenericIDictionaryFactory(count);
268+
TKey missingKey = GetNewKey(dictionary);
269+
TValue value = CreateTValue(5123);
270+
dictionary.TryAdd(missingKey, value);
271+
Assert.True(dictionary.TryGetValue(missingKey, out TValue outValue, out int index));
272+
Assert.Equal(value, outValue);
273+
Assert.Equal(dictionary.Count - 1, index);
274+
}
275+
190276
#endregion
191277

192278
#region ContainsValue

src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,17 @@ internal void SetItem(string propertyName, JsonNode? value)
248248

249249
OrderedDictionary<string, JsonNode?> dict = Dictionary;
250250

251-
if (!dict.TryAdd(propertyName, value))
251+
if (
252+
#if NET10_0_OR_GREATER
253+
!dict.TryAdd(propertyName, value, out int index)
254+
#else
255+
!dict.TryAdd(propertyName, value)
256+
#endif
257+
)
252258
{
259+
#if !NET10_0_OR_GREATER
253260
int index = dict.IndexOf(propertyName);
261+
#endif
254262
Debug.Assert(index >= 0);
255263
JsonNode? replacedValue = dict.GetAt(index).Value;
256264

0 commit comments

Comments
 (0)