Skip to content

Commit c92b557

Browse files
committed
Struct
1 parent 2733b31 commit c92b557

File tree

2 files changed

+33
-46
lines changed

2 files changed

+33
-46
lines changed

src/Shared/Dictionary/SmallCapacityDictionary.cs

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Internal.Dictionary
1212
/// <summary>
1313
/// An <see cref="IDictionary{String, Object}"/> type to hold a small amount of items (4 or less in the common case).
1414
/// </summary>
15-
internal class SmallCapacityDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull
15+
internal struct SmallCapacityDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull
1616
{
1717
// Threshold for size of array to use.
1818
private static readonly int DefaultArrayThreshold = 4;
@@ -91,17 +91,10 @@ public static SmallCapacityDictionary<TKey, TValue> FromArray(KeyValuePair<TKey,
9191
{
9292
_arrayStorage = items!,
9393
_count = start,
94+
_comparer = comparer
9495
};
9596
}
9697

97-
/// <summary>
98-
/// Creates an empty <see cref="SmallCapacityDictionary{TKey, TValue}"/>.
99-
/// </summary>
100-
public SmallCapacityDictionary()
101-
: this(0, EqualityComparer<TKey>.Default)
102-
{
103-
}
104-
10598
/// <summary>
10699
/// Creates a <see cref="SmallCapacityDictionary{TKey, TValue}"/>.
107100
/// </summary>
@@ -127,6 +120,9 @@ public SmallCapacityDictionary(int capacity)
127120
/// <param name="comparer">Equality comparison.</param>
128121
public SmallCapacityDictionary(int capacity, IEqualityComparer<TKey> comparer)
129122
{
123+
_backup = null;
124+
_count = 0;
125+
130126
if (comparer is not null && comparer != EqualityComparer<TKey>.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily
131127
{
132128
_comparer = comparer;
@@ -162,6 +158,9 @@ public SmallCapacityDictionary(int capacity, IEqualityComparer<TKey> comparer)
162158
/// <param name="capacity">Initial capacity.</param>
163159
public SmallCapacityDictionary(IEnumerable<KeyValuePair<TKey, TValue>> values, int capacity, IEqualityComparer<TKey> comparer)
164160
{
161+
_backup = null;
162+
_count = 0;
163+
165164
_comparer = comparer ?? EqualityComparer<TKey>.Default;
166165

167166
_arrayStorage = new KeyValuePair<TKey, TValue>[capacity];
@@ -600,13 +599,18 @@ private static void ThrowArgumentNullExceptionForKey()
600599
}
601600

602601
[MethodImpl(MethodImplOptions.AggressiveInlining)]
603-
private void EnsureCapacity(int capacity)
602+
private void EnsureCapacity(int capacity)
604603
{
605604
EnsureCapacitySlow(capacity);
606605
}
607606

608607
private void EnsureCapacitySlow(int capacity)
609608
{
609+
if (_arrayStorage == null)
610+
{
611+
_arrayStorage = Array.Empty<KeyValuePair<TKey, TValue>>();
612+
}
613+
610614
if (_arrayStorage.Length < capacity)
611615
{
612616
if (capacity > DefaultArrayThreshold)
@@ -644,7 +648,7 @@ private int FindIndex(TKey key)
644648

645649
for (var i = 0; i < count; i++)
646650
{
647-
if (_comparer.Equals(array[i].Key, key))
651+
if (Comparer.Equals(array[i].Key, key))
648652
{
649653
return i;
650654
}
@@ -658,21 +662,26 @@ private bool TryFindItem(TKey key, out TValue? value)
658662
{
659663
var array = _arrayStorage;
660664
var count = _count;
665+
value = default;
666+
667+
if (_arrayStorage == null)
668+
{
669+
return false;
670+
}
661671

662672
// Elide bounds check for indexing.
663673
if ((uint)count <= (uint)array.Length)
664674
{
665675
for (var i = 0; i < count; i++)
666676
{
667-
if (_comparer.Equals(array[i].Key, key))
677+
if (Comparer.Equals(array[i].Key, key))
668678
{
669679
value = array[i].Value;
670680
return true;
671681
}
672682
}
673683
}
674684

675-
value = default;
676685
return false;
677686
}
678687

@@ -682,12 +691,17 @@ private bool ContainsKeyArray(TKey key)
682691
var array = _arrayStorage;
683692
var count = _count;
684693

694+
if (array == null)
695+
{
696+
return false;
697+
}
698+
685699
// Elide bounds check for indexing.
686700
if ((uint)count <= (uint)array.Length)
687701
{
688702
for (var i = 0; i < count; i++)
689703
{
690-
if (_comparer.Equals(array[i].Key, key))
704+
if (Comparer.Equals(array[i].Key, key))
691705
{
692706
return true;
693707
}
@@ -700,7 +714,7 @@ private bool ContainsKeyArray(TKey key)
700714
/// <inheritdoc />
701715
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
702716
{
703-
private readonly SmallCapacityDictionary<TKey, TValue> _dictionary;
717+
private SmallCapacityDictionary<TKey, TValue> _dictionary;
704718
private int _index;
705719

706720
/// <summary>
@@ -709,11 +723,6 @@ public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
709723
/// <param name="dictionary">A <see cref="SmallCapacityDictionary{TKey, TValue}"/>.</param>
710724
public Enumerator(SmallCapacityDictionary<TKey, TValue> dictionary)
711725
{
712-
if (dictionary == null)
713-
{
714-
throw new ArgumentNullException();
715-
}
716-
717726
_dictionary = dictionary;
718727

719728
Current = default;
@@ -738,7 +747,7 @@ public void Dispose()
738747
public bool MoveNext()
739748
{
740749
var dictionary = _dictionary;
741-
if (dictionary._arrayStorage.Length >= _index)
750+
if (dictionary._arrayStorage == null || dictionary._arrayStorage.Length >= _index)
742751
{
743752
return false;
744753
}

src/Shared/test/Shared.Tests/SmallCapacityDictionaryTests.cs

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public void DefaultCtor()
2121

2222
// Assert
2323
Assert.Empty(dict);
24-
Assert.Empty(dict._arrayStorage);
24+
Assert.Null(dict._arrayStorage);
2525
Assert.Null(dict._backup);
2626
}
2727

@@ -34,7 +34,7 @@ public void CreateFromNull()
3434

3535
// Assert
3636
Assert.Empty(dict);
37-
Assert.Empty(dict._arrayStorage);
37+
Assert.Null(dict._arrayStorage);
3838
Assert.Null(dict._backup);
3939
}
4040

@@ -371,7 +371,6 @@ public void Keys_EmptyStorage()
371371

372372
// Assert
373373
Assert.Empty(keys);
374-
Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
375374
}
376375

377376
[Fact]
@@ -402,7 +401,6 @@ public void Values_EmptyStorage()
402401

403402
// Assert
404403
Assert.Empty(values);
405-
Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
406404
}
407405

408406
[Fact]
@@ -511,26 +509,6 @@ public void Add_DuplicateKey_CaseInsensitive()
511509
Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
512510
}
513511

514-
[Fact]
515-
public void Add_KeyValuePair()
516-
{
517-
// Arrange
518-
var dict = new SmallCapacityDictionary<string, object>()
519-
{
520-
{ "age", 30 },
521-
};
522-
523-
// Act
524-
((ICollection<KeyValuePair<string, object?>>)dict).Add(new KeyValuePair<string, object?>("key", "value"));
525-
526-
// Assert
527-
Assert.Collection(
528-
dict.OrderBy(kvp => kvp.Key),
529-
kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
530-
kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
531-
Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
532-
}
533-
534512
[Fact]
535513
public void Clear_EmptyStorage()
536514
{
@@ -1246,7 +1224,7 @@ public void ListStorage_SwitchesToDictionaryAfter4()
12461224
storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
12471225
Assert.Equal(4, storage.Length);
12481226

1249-
Assert.Equal(8, dict.Count);
1227+
Assert.Equal(5, dict.Count);
12501228
}
12511229

12521230
[Fact]

0 commit comments

Comments
 (0)