-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support IAlternateEqualityComparer with FrozenDictionary/Set (#103191)
* Support IAlternateEqualityComparer with FrozenDictionary/Set * Address PR feedback
- Loading branch information
1 parent
f5ab8d0
commit 1ddfd18
Showing
57 changed files
with
1,199 additions
and
232 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.net9.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// ------------------------------------------------------------------------------ | ||
// Changes to this file must follow the https://aka.ms/api-review process. | ||
// ------------------------------------------------------------------------------ | ||
|
||
namespace System.Collections.Frozen | ||
{ | ||
public abstract partial class FrozenDictionary<TKey, TValue> | ||
{ | ||
public System.Collections.Frozen.FrozenDictionary<TKey,TValue>.AlternateLookup<TAlternateKey> GetAlternateLookup<TAlternateKey>() where TAlternateKey : notnull, allows ref struct { throw null; } | ||
public bool TryGetAlternateLookup<TAlternateKey>(out System.Collections.Frozen.FrozenDictionary<TKey, TValue>.AlternateLookup<TAlternateKey> lookup) where TAlternateKey : notnull, allows ref struct { throw null; } | ||
public readonly partial struct AlternateLookup<TAlternateKey> where TAlternateKey : notnull, allows ref struct | ||
{ | ||
private readonly object _dummy; | ||
private readonly int _dummyPrimitive; | ||
public System.Collections.Frozen.FrozenDictionary<TKey, TValue> Dictionary { get { throw null; } } | ||
public TValue this[TAlternateKey key] { get { throw null; } } | ||
public bool ContainsKey(TAlternateKey key) { throw null; } | ||
public bool TryGetValue(TAlternateKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } | ||
} | ||
} | ||
public abstract partial class FrozenSet<T> | ||
{ | ||
public System.Collections.Frozen.FrozenSet<T>.AlternateLookup<TAlternate> GetAlternateLookup<TAlternate>() { throw null; } | ||
public bool TryGetAlternateLookup<TAlternate>(out System.Collections.Frozen.FrozenSet<T>.AlternateLookup<TAlternate> lookup) { throw null; } | ||
public readonly partial struct AlternateLookup<TAlternate> | ||
{ | ||
private readonly object _dummy; | ||
private readonly int _dummyPrimitive; | ||
public System.Collections.Frozen.FrozenSet<T> Set { get { throw null; } } | ||
public bool Contains(TAlternate item) { throw null; } | ||
public bool TryGetValue(TAlternate equalValue, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T actualValue) { throw null; } | ||
} | ||
} | ||
} |
52 changes: 0 additions & 52 deletions
52
src/libraries/System.Collections.Immutable/src/Interfaces.cd
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
...ctions.Immutable/src/System/Collections/Frozen/DefaultFrozenDictionary.AlternateLookup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace System.Collections.Frozen | ||
{ | ||
internal sealed partial class DefaultFrozenDictionary<TKey, TValue> | ||
{ | ||
/// <inheritdoc/> | ||
private protected override ref readonly TValue GetValueRefOrNullRefCore<TAlternateKey>(TAlternateKey key) | ||
{ | ||
IAlternateEqualityComparer<TAlternateKey, TKey> comparer = GetAlternateEqualityComparer<TAlternateKey>(); | ||
|
||
int hashCode = comparer.GetHashCode(key); | ||
_hashTable.FindMatchingEntries(hashCode, out int index, out int endIndex); | ||
|
||
while (index <= endIndex) | ||
{ | ||
if (hashCode == _hashTable.HashCodes[index] && comparer.Equals(key, _keys[index])) | ||
{ | ||
return ref _values[index]; | ||
} | ||
|
||
index++; | ||
} | ||
|
||
return ref Unsafe.NullRef<TValue>(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...m.Collections.Immutable/src/System/Collections/Frozen/DefaultFrozenSet.AlternateLookup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace System.Collections.Frozen | ||
{ | ||
internal sealed partial class DefaultFrozenSet<T> | ||
{ | ||
/// <inheritdoc /> | ||
private protected override int FindItemIndex<TAlternate>(TAlternate item) | ||
{ | ||
IAlternateEqualityComparer<TAlternate, T> comparer = GetAlternateEqualityComparer<TAlternate>(); | ||
|
||
int hashCode = item is null ? 0 : comparer.GetHashCode(item); | ||
_hashTable.FindMatchingEntries(hashCode, out int index, out int endIndex); | ||
|
||
while (index <= endIndex) | ||
{ | ||
if (hashCode == _hashTable.HashCodes[index] && comparer.Equals(item, _items[index])) | ||
{ | ||
return index; | ||
} | ||
|
||
index++; | ||
} | ||
|
||
return -1; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
...m.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.AlternateLookup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace System.Collections.Frozen | ||
{ | ||
public abstract partial class FrozenDictionary<TKey, TValue> | ||
{ | ||
/// <summary> | ||
/// Gets an instance of a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/> | ||
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>. | ||
/// </summary> | ||
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam> | ||
/// <returns>The created lookup instance.</returns> | ||
/// <exception cref="InvalidOperationException">This instance's comparer is not compatible with <typeparamref name="TAlternateKey"/>.</exception> | ||
/// <remarks> | ||
/// This instance must be using a comparer that implements <see cref="IAlternateEqualityComparer{TAlternateKey, TKey}"/> with | ||
/// <typeparamref name="TAlternateKey"/> and <typeparamref name="TKey"/>. If it doesn't, an exception will be thrown. | ||
/// </remarks> | ||
public AlternateLookup<TAlternateKey> GetAlternateLookup<TAlternateKey>() where TAlternateKey : notnull, allows ref struct | ||
{ | ||
if (!TryGetAlternateLookup(out AlternateLookup<TAlternateKey> lookup)) | ||
{ | ||
ThrowHelper.ThrowIncompatibleComparer(); | ||
} | ||
|
||
return lookup; | ||
} | ||
|
||
/// <summary> | ||
/// Gets an instance of a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/> | ||
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>. | ||
/// </summary> | ||
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam> | ||
/// <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> | ||
/// <returns>true if a lookup could be created; otherwise, false.</returns> | ||
/// <remarks> | ||
/// This instance must be using a comparer that implements <see cref="IAlternateEqualityComparer{TAlternateKey, TKey}"/> with | ||
/// <typeparamref name="TAlternateKey"/> and <typeparamref name="TKey"/>. If it doesn't, the method will return false. | ||
/// </remarks> | ||
public bool TryGetAlternateLookup<TAlternateKey>(out AlternateLookup<TAlternateKey> lookup) where TAlternateKey : notnull, allows ref struct | ||
{ | ||
// The comparer must support the specified TAlternateKey. If it doesn't we can't create a lookup. | ||
// Some implementations where TKey is string rely on the length of the input and use it as part of the storage scheme. | ||
// That means we can only support TAlternateKeys that have a length we can check, which means we have to special-case | ||
// it. Since which implementation we pick is based on a heuristic and can't be predicted by the consumer, we don't | ||
// just have this requirement in that one implementation but for all implementations that might be picked for string. | ||
// As such, if the key is a string, we only support ReadOnlySpan<char> as the alternate key. | ||
if (Comparer is IAlternateEqualityComparer<TAlternateKey, TKey> && | ||
(typeof(TKey) != typeof(string) || typeof(TAlternateKey) == typeof(ReadOnlySpan<char>))) | ||
{ | ||
lookup = new AlternateLookup<TAlternateKey>(this); | ||
return true; | ||
} | ||
|
||
lookup = default; | ||
return false; | ||
} | ||
|
||
/// <summary>Gets the <see cref="Comparer"/> as an <see cref="IAlternateEqualityComparer{TAlternate, T}"/>.</summary> | ||
/// <remarks>This must only be used when it's already been proven that the comparer implements the target interface.</remarks> | ||
private protected IAlternateEqualityComparer<TAlternateKey, TKey> GetAlternateEqualityComparer<TAlternateKey>() where TAlternateKey : notnull, allows ref struct | ||
{ | ||
Debug.Assert(Comparer is IAlternateEqualityComparer<TAlternateKey, TKey>, "Must have already been verified"); | ||
return Unsafe.As<IAlternateEqualityComparer<TAlternateKey, TKey>>(Comparer); | ||
} | ||
|
||
/// <summary> | ||
/// Provides a type that may be used to perform operations on a <see cref="FrozenDictionary{TKey, TValue}"/> | ||
/// using a <typeparamref name="TAlternateKey"/> as a key instead of a <typeparamref name="TKey"/>. | ||
/// </summary> | ||
/// <typeparam name="TAlternateKey">The alternate type of a key for performing lookups.</typeparam> | ||
public readonly struct AlternateLookup<TAlternateKey> where TAlternateKey : notnull, allows ref struct | ||
{ | ||
/// <summary>Initialize the instance. The dictionary must have already been verified to have a compatible comparer.</summary> | ||
internal AlternateLookup(FrozenDictionary<TKey, TValue> dictionary) | ||
{ | ||
Debug.Assert(dictionary is not null); | ||
Debug.Assert(dictionary.Comparer is IAlternateEqualityComparer<TAlternateKey, TKey>); | ||
Dictionary = dictionary; | ||
} | ||
|
||
/// <summary>Gets the <see cref="FrozenDictionary{TKey, TValue}"/> against which this instance performs operations.</summary> | ||
public FrozenDictionary<TKey, TValue> Dictionary { get; } | ||
|
||
/// <summary>Gets or sets the value associated with the specified alternate key.</summary> | ||
/// <param name="key">The alternate key of the value to get or set.</param> | ||
/// <value> | ||
/// The value associated with the specified alternate key. If the specified alternate key is not found, a get operation throws | ||
/// a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key. | ||
/// </value> | ||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="KeyNotFoundException">The alternate key does not exist in the collection.</exception> | ||
public TValue this[TAlternateKey key] | ||
{ | ||
get | ||
{ | ||
ref readonly TValue valueRef = ref Dictionary.GetValueRefOrNullRefCore(key); | ||
if (Unsafe.IsNullRef(in valueRef)) | ||
{ | ||
ThrowHelper.ThrowKeyNotFoundException(); | ||
} | ||
|
||
return valueRef; | ||
} | ||
} | ||
|
||
/// <summary>Determines whether the <see cref="FrozenDictionary{TKey, TValue}"/> contains the specified alternate key.</summary> | ||
/// <param name="key">The alternate key to check.</param> | ||
/// <returns><see langword="true"/> if the key is in the dictionary; otherwise, <see langword="false"/>.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> | ||
public bool ContainsKey(TAlternateKey key) => | ||
!Unsafe.IsNullRef(in Dictionary.GetValueRefOrNullRefCore(key)); | ||
|
||
/// <summary>Gets the value associated with the specified alternate key.</summary> | ||
/// <param name="key">The alternate key of the value to get.</param> | ||
/// <param name="value"> | ||
/// When this method returns, contains the value associated with the specified key, if the key is found; | ||
/// otherwise, the default value for the type of the value parameter. | ||
/// </param> | ||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> | ||
public bool TryGetValue(TAlternateKey key, [MaybeNullWhen(false)] out TValue value) | ||
{ | ||
ref readonly TValue valueRef = ref Dictionary.GetValueRefOrNullRefCore(key); | ||
|
||
if (!Unsafe.IsNullRef(in valueRef)) | ||
{ | ||
value = valueRef; | ||
return true; | ||
} | ||
|
||
value = default; | ||
return false; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.