Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ddfaaa4

Browse files
committed
Support IAlternateEqualityComparer with FrozenDictionary/Set
1 parent 2da65a9 commit ddfaaa4

File tree

57 files changed

+1185
-232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1185
-232
lines changed

src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
<Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\CollectionBuilderAttribute.cs" Link="System\Runtime\CompilerServices\CollectionBuilderAttribute.cs" />
1616
</ItemGroup>
1717

18+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
19+
<Compile Include="System.Collections.Immutable.net9.cs" />
20+
</ItemGroup>
21+
1822
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
1923
<ProjectReference Include="$(LibrariesProjectRoot)System.Collections\ref\System.Collections.csproj" />
2024
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\ref\System.Runtime.csproj" />
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// ------------------------------------------------------------------------------
4+
// Changes to this file must follow the https://aka.ms/api-review process.
5+
// ------------------------------------------------------------------------------
6+
7+
namespace System.Collections.Frozen
8+
{
9+
public abstract partial class FrozenDictionary<TKey, TValue>
10+
{
11+
public System.Collections.Frozen.FrozenDictionary<TKey,TValue>.AlternateLookup<TAlternateKey> GetAlternateLookup<TAlternateKey>() where TAlternateKey : notnull, allows ref struct { throw null; }
12+
public bool TryGetAlternateLookup<TAlternateKey>(out System.Collections.Frozen.FrozenDictionary<TKey, TValue>.AlternateLookup<TAlternateKey> lookup) where TAlternateKey : notnull, allows ref struct { throw null; }
13+
public readonly partial struct AlternateLookup<TAlternateKey> where TAlternateKey : notnull, allows ref struct
14+
{
15+
private readonly object _dummy;
16+
private readonly int _dummyPrimitive;
17+
public System.Collections.Frozen.FrozenDictionary<TKey, TValue> Dictionary { get { throw null; } }
18+
public TValue this[TAlternateKey key] { get { throw null; } }
19+
public bool ContainsKey(TAlternateKey key) { throw null; }
20+
public bool TryGetValue(TAlternateKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; }
21+
}
22+
}
23+
public abstract partial class FrozenSet<T>
24+
{
25+
public System.Collections.Frozen.FrozenSet<T>.AlternateLookup<TAlternate> GetAlternateLookup<TAlternate>() { throw null; }
26+
public bool TryGetAlternateLookup<TAlternate>(out System.Collections.Frozen.FrozenSet<T>.AlternateLookup<TAlternate> lookup) { throw null; }
27+
public readonly partial struct AlternateLookup<TAlternate>
28+
{
29+
private readonly object _dummy;
30+
private readonly int _dummyPrimitive;
31+
public System.Collections.Frozen.FrozenSet<T> Set { get { throw null; } }
32+
public bool Contains(TAlternate item) { throw null; }
33+
public bool TryGetValue(TAlternate equalValue, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T actualValue) { throw null; }
34+
}
35+
}
36+
}

src/libraries/System.Collections.Immutable/src/Interfaces.cd

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/libraries/System.Collections.Immutable/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,7 @@
105105
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
106106
<value>Non-negative number required.</value>
107107
</data>
108+
<data name="InvalidOperation_IncompatibleComparer" xml:space="preserve">
109+
<value>The collection's comparer does not support the requested operation.</value>
110+
</data>
108111
</root>

src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,21 @@ The System.Collections.Immutable library is built-in as part of the shared frame
146146
<Compile Include="System\Runtime.InteropServices\ImmutableCollectionsMarshal.cs" />
147147
<Compile Include="Validation\Requires.cs" />
148148
<Compile Include="$(CommonPath)System\Runtime\Versioning\NonVersionableAttribute.cs" Link="Common\System\Runtime\Versioning\NonVersionableAttribute.cs" />
149-
<None Include="Interfaces.cd" />
149+
</ItemGroup>
150+
151+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
152+
<Compile Include="System\Collections\Frozen\DefaultFrozenDictionary.AlternateLookup.cs" />
153+
<Compile Include="System\Collections\Frozen\DefaultFrozenSet.AlternateLookup.cs" />
154+
<Compile Include="System\Collections\Frozen\FrozenDictionary.AlternateLookup.cs" />
155+
<Compile Include="System\Collections\Frozen\FrozenSet.AlternateLookup.cs" />
156+
<Compile Include="System\Collections\Frozen\SmallFrozenDictionary.AlternateLookup.cs" />
157+
<Compile Include="System\Collections\Frozen\SmallFrozenSet.AlternateLookup.cs" />
158+
<Compile Include="System\Collections\Frozen\Int32\Int32FrozenDictionary.AlternateLookup.cs" />
159+
<Compile Include="System\Collections\Frozen\Int32\Int32FrozenSet.AlternateLookup.cs" />
160+
<Compile Include="System\Collections\Frozen\String\LengthBucketsFrozenDictionary.AlternateLookup.cs" />
161+
<Compile Include="System\Collections\Frozen\String\LengthBucketsFrozenSet.AlternateLookup.cs" />
162+
<Compile Include="System\Collections\Frozen\String\OrdinalStringFrozenDictionary.AlternateLookup.cs" />
163+
<Compile Include="System\Collections\Frozen\String\OrdinalStringFrozenSet.AlternateLookup.cs" />
150164
</ItemGroup>
151165

152166
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace System.Collections.Frozen
9+
{
10+
internal sealed partial class DefaultFrozenDictionary<TKey, TValue>
11+
{
12+
/// <inheritdoc/>
13+
private protected override ref readonly TValue GetValueRefOrNullRefCore<TAlternateKey>(TAlternateKey key)
14+
{
15+
IAlternateEqualityComparer<TAlternateKey, TKey> comparer = GetAlternateEqualityComparer<TAlternateKey>();
16+
17+
int hashCode = comparer.GetHashCode(key);
18+
_hashTable.FindMatchingEntries(hashCode, out int index, out int endIndex);
19+
20+
while (index <= endIndex)
21+
{
22+
if (hashCode == _hashTable.HashCodes[index] && comparer.Equals(key, _keys[index]))
23+
{
24+
return ref _values[index];
25+
}
26+
27+
index++;
28+
}
29+
30+
return ref Unsafe.NullRef<TValue>();
31+
}
32+
}
33+
}

src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/DefaultFrozenDictionary.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace System.Collections.Frozen
99
/// <summary>Provides the default <see cref="FrozenDictionary{TKey, TValue}"/> implementation to use when no other special-cases apply.</summary>
1010
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
1111
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
12-
internal sealed class DefaultFrozenDictionary<TKey, TValue> : KeysAndValuesFrozenDictionary<TKey, TValue>, IDictionary<TKey, TValue>
12+
internal sealed partial class DefaultFrozenDictionary<TKey, TValue> : KeysAndValuesFrozenDictionary<TKey, TValue>, IDictionary<TKey, TValue>
1313
where TKey : notnull
1414
{
1515
internal DefaultFrozenDictionary(Dictionary<TKey, TValue> source)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
6+
namespace System.Collections.Frozen
7+
{
8+
internal sealed partial class DefaultFrozenSet<T>
9+
{
10+
/// <inheritdoc />
11+
private protected override int FindItemIndex<TAlternate>(TAlternate item)
12+
{
13+
IAlternateEqualityComparer<TAlternate, T> comparer = GetAlternateEqualityComparer<TAlternate>();
14+
15+
int hashCode = item is null ? 0 : comparer.GetHashCode(item);
16+
_hashTable.FindMatchingEntries(hashCode, out int index, out int endIndex);
17+
18+
while (index <= endIndex)
19+
{
20+
if (hashCode == _hashTable.HashCodes[index] && comparer.Equals(item, _items[index]))
21+
{
22+
return index;
23+
}
24+
25+
index++;
26+
}
27+
28+
return -1;
29+
}
30+
}
31+
}

src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/DefaultFrozenSet.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace System.Collections.Frozen
77
{
88
/// <summary>Provides the default <see cref="FrozenSet{T}"/> implementation to use when no other special-cases apply.</summary>
99
/// <typeparam name="T">The type of the values in the set.</typeparam>
10-
internal sealed class DefaultFrozenSet<T> : ItemsFrozenSet<T, DefaultFrozenSet<T>.GSW>
10+
internal sealed partial class DefaultFrozenSet<T> : ItemsFrozenSet<T, DefaultFrozenSet<T>.GSW>
1111
{
1212
internal DefaultFrozenSet(HashSet<T> source)
1313
: base(source)
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
9+
namespace System.Collections.Frozen
10+
{
11+
public abstract partial class FrozenDictionary<TKey, TValue>
12+
{
13+
/// <summary>
14+
/// Gets an instance of a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/>
15+
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>.
16+
/// </summary>
17+
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam>
18+
/// <returns>The created lookup instance.</returns>
19+
/// <exception cref="InvalidOperationException">This instance's comparer is not compatible with <typeparamref name="TAlternateKey"/>.</exception>
20+
/// <remarks>
21+
/// This instance must be using a comparer that implements <see cref="IAlternateEqualityComparer{TAlternateKey, TKey}"/> with
22+
/// <typeparamref name="TAlternateKey"/> and <typeparamref name="TKey"/>. If it doesn't, an exception will be thrown.
23+
/// </remarks>
24+
public AlternateLookup<TAlternateKey> GetAlternateLookup<TAlternateKey>() where TAlternateKey : notnull, allows ref struct
25+
{
26+
if (!TryGetAlternateLookup(out AlternateLookup<TAlternateKey> lookup))
27+
{
28+
ThrowHelper.ThrowIncompatibleComparer();
29+
}
30+
31+
return lookup;
32+
}
33+
34+
/// <summary>
35+
/// Gets an instance of a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/>
36+
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>.
37+
/// </summary>
38+
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam>
39+
/// <param name="lookup">The created lookup instance when the method returns true, or a default instance that should not be used if the method returns false.</param>
40+
/// <returns>true if a lookup could be created; otherwise, false.</returns>
41+
/// <remarks>
42+
/// This instance must be using a comparer that implements <see cref="IAlternateEqualityComparer{TAlternateKey, TKey}"/> with
43+
/// <typeparamref name="TAlternateKey"/> and <typeparamref name="TKey"/>. If it doesn't, the method will return false.
44+
/// </remarks>
45+
public bool TryGetAlternateLookup<TAlternateKey>(out AlternateLookup<TAlternateKey> lookup) where TAlternateKey : notnull, allows ref struct
46+
{
47+
// The comparer must support the specified TAlternateKey. If it doesn't we can't create a lookup.
48+
// Some implementations where TKey is string rely on the length of the input and use it as part of the storage scheme.
49+
// That means we can only support TAlternateKeys that have a length we can check, which means we have to special-case
50+
// it. Since which implementation we pick is based on a heuristic and can't be predicted by the consumer, we don't
51+
// just have this requirement in that one implementation but for all implementations that might be picked for string.
52+
// As such, if the key is a string, we only support ReadOnlySpan<char> as the alternate key.
53+
if (Comparer is IAlternateEqualityComparer<TAlternateKey, TKey> &&
54+
(typeof(TKey) != typeof(string) || typeof(TAlternateKey) == typeof(ReadOnlySpan<char>)))
55+
{
56+
lookup = new AlternateLookup<TAlternateKey>(this);
57+
return true;
58+
}
59+
60+
lookup = default;
61+
return false;
62+
}
63+
64+
/// <summary>Gets the <see cref="Comparer"/> as an <see cref="IAlternateEqualityComparer{TAlternate, T}"/>.</summary>
65+
/// <remarks>This must only be used when it's already been proven that the comparer implements the target interface.</remarks>
66+
private protected IAlternateEqualityComparer<TAlternateKey, TKey> GetAlternateEqualityComparer<TAlternateKey>() where TAlternateKey : notnull, allows ref struct
67+
{
68+
Debug.Assert(Comparer is IAlternateEqualityComparer<TAlternateKey, TKey>, "Must have already been verified");
69+
return Unsafe.As<IAlternateEqualityComparer<TAlternateKey, TKey>>(Comparer);
70+
}
71+
72+
/// <summary>
73+
/// Provides a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/>
74+
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>.
75+
/// </summary>
76+
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam>
77+
public readonly struct AlternateLookup<TAlternateKey> where TAlternateKey : notnull, allows ref struct
78+
{
79+
/// <summary>Initialize the instance. The dictionary must have already been verified to have a compatible comparer.</summary>
80+
internal AlternateLookup(FrozenDictionary<TKey, TValue> dictionary)
81+
{
82+
Debug.Assert(dictionary is not null);
83+
Debug.Assert(dictionary.Comparer is IAlternateEqualityComparer<TAlternateKey, TKey>);
84+
Dictionary = dictionary;
85+
}
86+
87+
/// <summary>Gets the <see cref="FrozenDictionary{TKey, TValue}"/> against which this instance performs operations.</summary>
88+
public FrozenDictionary<TKey, TValue> Dictionary { get; }
89+
90+
/// <summary>Gets or sets the value associated with the specified alternate key.</summary>
91+
/// <param name="key">The alternate key of the value to get or set.</param>
92+
/// <value>
93+
/// The value associated with the specified alternate key. If the specified alternate key is not found, a get operation throws
94+
/// a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.
95+
/// </value>
96+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
97+
/// <exception cref="KeyNotFoundException">The alternate key does not exist in the collection.</exception>
98+
public TValue this[TAlternateKey key]
99+
{
100+
get
101+
{
102+
ref readonly TValue valueRef = ref Dictionary.GetValueRefOrNullRefCore(key);
103+
if (Unsafe.IsNullRef(in valueRef))
104+
{
105+
ThrowHelper.ThrowKeyNotFoundException();
106+
}
107+
108+
return valueRef;
109+
}
110+
}
111+
112+
/// <summary>Determines whether the <see cref="FrozenDictionary{TKey, TValue}"/> contains the specified alternate key.</summary>
113+
/// <param name="key">The alternate key to check.</param>
114+
/// <returns><see langword="true"/> if the key is in the dictionary; otherwise, <see langword="false"/>.</returns>
115+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
116+
public bool ContainsKey(TAlternateKey key) =>
117+
!Unsafe.IsNullRef(in Dictionary.GetValueRefOrNullRefCore(key));
118+
119+
/// <summary>Gets the value associated with the specified alternate key.</summary>
120+
/// <param name="key">The alternate key of the value to get.</param>
121+
/// <param name="value">
122+
/// When this method returns, contains the value associated with the specified key, if the key is found;
123+
/// otherwise, the default value for the type of the value parameter.
124+
/// </param>
125+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
126+
public bool TryGetValue(TAlternateKey key, [MaybeNullWhen(false)] out TValue value)
127+
{
128+
ref readonly TValue valueRef = ref Dictionary.GetValueRefOrNullRefCore(key);
129+
130+
if (!Unsafe.IsNullRef(in valueRef))
131+
{
132+
value = valueRef;
133+
return true;
134+
}
135+
136+
value = default;
137+
return false;
138+
}
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)