Skip to content

Commit ab061a3

Browse files
committed
Add CollectionsMarshal.GetValueRef API
1 parent e9f101c commit ab061a3

File tree

3 files changed

+174
-0
lines changed

3 files changed

+174
-0
lines changed

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CollectionsMarshal.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using Internal.Runtime.CompilerServices;
56

67
namespace System.Runtime.InteropServices
78
{
@@ -18,6 +19,25 @@ public static class CollectionsMarshal
1819
public static Span<T> AsSpan<T>(List<T>? list)
1920
=> list is null ? default : new Span<T>(list._items, 0, list._size);
2021

22+
/// <summary>
23+
/// Gets a ref to a <typeparamref name="TValue"/> in the <see cref="Dictionary{TKey, TValue}"/>.
24+
/// </summary>
25+
/// <param name="dictionary">The dictionary to get the ref to <typeparamref name="TValue"/> from.</param>
26+
/// <param name="key">The key used for lookup.</param>
27+
/// <remarks>Items should not be added or removed from the <see cref="Dictionary{TKey, TValue}"/> while the ref <typeparamref name="TValue"/> is in use.</remarks>
28+
/// <exception cref="KeyNotFoundException">Thrown when <paramref name="key"/> does not exist in the <paramref name="dictionary"/>.</exception>
29+
public static ref TValue GetValueRef<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull
30+
{
31+
ref TValue valueRef = ref dictionary.FindValue(key);
32+
33+
if (Unsafe.IsNullRef(ref valueRef))
34+
{
35+
ThrowHelper.ThrowKeyNotFoundException(key);
36+
}
37+
38+
return ref valueRef;
39+
}
40+
2141
/// <summary>
2242
/// Gets either a ref to a <typeparamref name="TValue"/> in the <see cref="Dictionary{TKey, TValue}"/> or a ref null if it does not exist in the <paramref name="dictionary"/>.
2343
/// </summary>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public CoClassAttribute(System.Type coClass) { }
176176
public static partial class CollectionsMarshal
177177
{
178178
public static System.Span<T> AsSpan<T>(System.Collections.Generic.List<T>? list) { throw null; }
179+
public static ref TValue GetValueRef<TKey, TValue>(System.Collections.Generic.Dictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull { throw null; }
179180
public static ref TValue GetValueRefOrNullRef<TKey, TValue>(System.Collections.Generic.Dictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull { throw null; }
180181
}
181182
[System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue, Inherited=false)]

src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/CollectionsMarshalTests.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,159 @@ public void ListAsSpanLinkBreaksOnResize()
144144
}
145145
}
146146

147+
[Fact]
148+
public void GetValueRefValueType()
149+
{
150+
var dict = new Dictionary<int, Struct>
151+
{
152+
{ 1, default },
153+
{ 2, default }
154+
};
155+
156+
Assert.Equal(2, dict.Count);
157+
158+
Assert.Equal(0, dict[1].Value);
159+
Assert.Equal(0, dict[1].Property);
160+
161+
var itemVal = dict[1];
162+
itemVal.Value = 1;
163+
itemVal.Property = 2;
164+
165+
// Does not change values in dictionary
166+
Assert.Equal(0, dict[1].Value);
167+
Assert.Equal(0, dict[1].Property);
168+
169+
CollectionsMarshal.GetValueRef(dict, 1).Value = 3;
170+
CollectionsMarshal.GetValueRef(dict, 1).Property = 4;
171+
172+
Assert.Equal(3, dict[1].Value);
173+
Assert.Equal(4, dict[1].Property);
174+
175+
ref var itemRef = ref CollectionsMarshal.GetValueRef(dict, 2);
176+
177+
Assert.Equal(0, itemRef.Value);
178+
Assert.Equal(0, itemRef.Property);
179+
180+
itemRef.Value = 5;
181+
itemRef.Property = 6;
182+
183+
Assert.Equal(5, itemRef.Value);
184+
Assert.Equal(6, itemRef.Property);
185+
Assert.Equal(dict[2].Value, itemRef.Value);
186+
Assert.Equal(dict[2].Property, itemRef.Property);
187+
188+
itemRef = new() { Value = 7, Property = 8 };
189+
190+
Assert.Equal(7, itemRef.Value);
191+
Assert.Equal(8, itemRef.Property);
192+
Assert.Equal(dict[2].Value, itemRef.Value);
193+
Assert.Equal(dict[2].Property, itemRef.Property);
194+
195+
// Check for exception
196+
197+
Assert.Throws<KeyNotFoundException>(() => CollectionsMarshal.GetValueRef(dict, 3));
198+
199+
Assert.Equal(2, dict.Count);
200+
}
201+
202+
[Fact]
203+
public void GetValueRefClass()
204+
{
205+
var dict = new Dictionary<int, IntAsObject>
206+
{
207+
{ 1, new() },
208+
{ 2, new() }
209+
};
210+
211+
Assert.Equal(2, dict.Count);
212+
213+
Assert.Equal(0, dict[1].Value);
214+
Assert.Equal(0, dict[1].Property);
215+
216+
var itemVal = dict[1];
217+
itemVal.Value = 1;
218+
itemVal.Property = 2;
219+
220+
// Does change values in dictionary
221+
Assert.Equal(1, dict[1].Value);
222+
Assert.Equal(2, dict[1].Property);
223+
224+
CollectionsMarshal.GetValueRef(dict, 1).Value = 3;
225+
CollectionsMarshal.GetValueRef(dict, 1).Property = 4;
226+
227+
Assert.Equal(3, dict[1].Value);
228+
Assert.Equal(4, dict[1].Property);
229+
230+
ref var itemRef = ref CollectionsMarshal.GetValueRef(dict, 2);
231+
232+
Assert.Equal(0, itemRef.Value);
233+
Assert.Equal(0, itemRef.Property);
234+
235+
itemRef.Value = 5;
236+
itemRef.Property = 6;
237+
238+
Assert.Equal(5, itemRef.Value);
239+
Assert.Equal(6, itemRef.Property);
240+
Assert.Equal(dict[2].Value, itemRef.Value);
241+
Assert.Equal(dict[2].Property, itemRef.Property);
242+
243+
itemRef = new() { Value = 7, Property = 8 };
244+
245+
Assert.Equal(7, itemRef.Value);
246+
Assert.Equal(8, itemRef.Property);
247+
Assert.Equal(dict[2].Value, itemRef.Value);
248+
Assert.Equal(dict[2].Property, itemRef.Property);
249+
250+
// Check for exception
251+
252+
Assert.Throws<KeyNotFoundException>(() => CollectionsMarshal.GetValueRef(dict, 3));
253+
254+
Assert.Equal(2, dict.Count);
255+
}
256+
257+
[Fact]
258+
public void GetValueRefLinkBreaksOnResize()
259+
{
260+
var dict = new Dictionary<int, Struct>
261+
{
262+
{ 1, new() }
263+
};
264+
265+
Assert.Equal(1, dict.Count);
266+
267+
ref var itemRef = ref CollectionsMarshal.GetValueRef(dict, 1);
268+
269+
Assert.Equal(0, itemRef.Value);
270+
Assert.Equal(0, itemRef.Property);
271+
272+
itemRef.Value = 1;
273+
itemRef.Property = 2;
274+
275+
Assert.Equal(1, itemRef.Value);
276+
Assert.Equal(2, itemRef.Property);
277+
Assert.Equal(dict[1].Value, itemRef.Value);
278+
Assert.Equal(dict[1].Property, itemRef.Property);
279+
280+
// Resize
281+
dict.EnsureCapacity(100);
282+
for (int i = 2; i <= 50; i++)
283+
{
284+
dict.Add(i, new());
285+
}
286+
287+
itemRef.Value = 3;
288+
itemRef.Property = 4;
289+
290+
Assert.Equal(3, itemRef.Value);
291+
Assert.Equal(4, itemRef.Property);
292+
293+
// Check connection broken
294+
Assert.NotEqual(dict[1].Value, itemRef.Value);
295+
Assert.NotEqual(dict[1].Property, itemRef.Property);
296+
297+
Assert.Equal(50, dict.Count);
298+
}
299+
147300
[Fact]
148301
public void GetValueRefOrNullRefValueType()
149302
{

0 commit comments

Comments
 (0)