|
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Collections.Frozen
{
/// <summary>
/// Provides a set of initialization methods for instances of the <see cref="FrozenDictionary{TKey, TValue}"/> class.
/// </summary>
public static class FrozenDictionary
{
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TValue}"/> with the specified key/value pairs.</summary>
/// <param name="source">The key/value pairs to use to populate the dictionary.</param>
/// <param name="comparer">The comparer implementation to use to compare keys for equality. If null, <see cref="EqualityComparer{TKey}.Default"/> is used.</param>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
/// <remarks>
/// If the same key appears multiple times in the input, the latter one in the sequence takes precedence. This differs from
/// <see cref="M:System.Linq.Enumerable.ToDictionary"/>, with which multiple duplicate keys will result in an exception.
/// </remarks>
/// <returns>A <see cref="FrozenDictionary{TKey, TValue}"/> that contains the specified keys and values.</returns>
public static FrozenDictionary<TKey, TValue> ToFrozenDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey>? comparer = null)
where TKey : notnull =>
GetExistingFrozenOrNewDictionary(source, comparer, out Dictionary<TKey, TValue>? uniqueValues) ??
CreateFromDictionary(uniqueValues!);
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TSource}"/> from an <see cref="IEnumerable{TSource}"/> according to specified key selector function.</summary>
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="source">An <see cref="IEnumerable{TSource}"/> from which to create a <see cref="FrozenDictionary{TKey, TSource}"/>.</param>
/// <param name="keySelector">A function to extract a key from each element.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{TKey}"/> to compare keys.</param>
/// <returns>A <see cref="FrozenDictionary{TKey, TElement}"/> that contains the keys and values selected from the input sequence.</returns>
public static FrozenDictionary<TKey, TSource> ToFrozenDictionary<TSource, TKey>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer = null)
where TKey : notnull =>
source.ToDictionary(keySelector, comparer).ToFrozenDictionary(comparer);
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TElement}"/> from an <see cref="IEnumerable{TSource}"/> according to specified key selector and element selector functions.</summary>
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <typeparam name="TElement">The type of the value returned by <paramref name="elementSelector"/>.</typeparam>
/// <param name="source">An <see cref="IEnumerable{TSource}"/> from which to create a <see cref="FrozenDictionary{TKey, TElement}"/>.</param>
/// <param name="keySelector">A function to extract a key from each element.</param>
/// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
/// <param name="comparer">An <see cref="IEqualityComparer{TKey}"/> to compare keys.</param>
/// <returns>A <see cref="FrozenDictionary{TKey, TElement}"/> that contains the keys and values selected from the input sequence.</returns>
public static FrozenDictionary<TKey, TElement> ToFrozenDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey>? comparer = null)
where TKey : notnull =>
source.ToDictionary(keySelector, elementSelector, comparer).ToFrozenDictionary(comparer);
/// <summary>
/// Extracts from the source either an existing <see cref="FrozenDictionary{TKey,TValue}"/> instance or a <see cref="Dictionary{TKey,TValue}"/>
/// containing the values and the specified <paramref name="comparer"/>.
/// </summary>
/// <returns>
/// If <paramref name="source"/> is already a <see cref="FrozenDictionary{TKey,TValue}"/> with the same <paramref name="comparer"/>, returns that instance.
/// If <paramref name="source"/> is empty, returns an empty <see cref="FrozenDictionary{TKey,TValue}"/> with the specified <paramref name="comparer"/>.
/// Otherwise, returns null, and <paramref name="newDictionary"/> is set to a dictionary containing the keys/values from <paramref name="source"/> and
/// specified <paramref name="comparer"/>.
/// </returns>
private static FrozenDictionary<TKey, TValue>? GetExistingFrozenOrNewDictionary<TKey, TValue>(
IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey>? comparer,
out Dictionary<TKey, TValue>? newDictionary)
where TKey : notnull
{
ThrowHelper.ThrowIfNull(source);
comparer ??= EqualityComparer<TKey>.Default;
// If the source is already frozen with the same comparer, it can simply be returned.
if (source is FrozenDictionary<TKey, TValue> fd && fd.Comparer.Equals(comparer))
{
newDictionary = null;
return fd;
}
// Ensure we have a Dictionary<,> using the specified comparer such that all keys
// are non-null and unique according to that comparer.
newDictionary = source as Dictionary<TKey, TValue>;
if (newDictionary is null || (newDictionary.Count != 0 && !newDictionary.Comparer.Equals(comparer)))
{
newDictionary = new Dictionary<TKey, TValue>(comparer);
foreach (KeyValuePair<TKey, TValue> pair in source)
{
// Dictionary's constructor uses Add, which will throw on duplicates.
// This implementation uses the indexer to avoid throwing and to overwrite
// existing entries such that last one wins.
newDictionary[pair.Key] = pair.Value;
}
}
if (newDictionary.Count == 0)
{
return ReferenceEquals(comparer, FrozenDictionary<TKey, TValue>.Empty.Comparer) ?
FrozenDictionary<TKey, TValue>.Empty :
new EmptyFrozenDictionary<TKey, TValue>(comparer);
}
Debug.Assert(newDictionary is not null);
Debug.Assert(newDictionary.Comparer.Equals(comparer));
return null;
}
/// <summary>Constructs a frozen dictionary, optimizing for the speed of reads on the created instance.</summary>
private static FrozenDictionary<TKey, TValue> CreateFromDictionary<TKey, TValue>(Dictionary<TKey, TValue> source)
where TKey : notnull
{
Debug.Assert(source.Count > 0, "Empty sources should have been filtered out by caller");
IEqualityComparer<TKey> comparer = source.Comparer;
// Optimize for value types when the default comparer is being used. In such a case, the implementation
// may use {Equality}Comparer<TKey>.Default.Compare/Equals/GetHashCode directly, with generic specialization enabling
// the Equals/GetHashCode methods to be devirtualized and possibly inlined.
if (typeof(TKey).IsValueType && ReferenceEquals(comparer, EqualityComparer<TKey>.Default))
{
if (source.Count <= Constants.MaxItemsInSmallValueTypeFrozenCollection)
{
// If the key is a something we know we can efficiently compare, use a specialized implementation
// that will enable quickly ruling out values outside of the range of keys stored.
if (Constants.IsKnownComparable<TKey>())
{
return (FrozenDictionary<TKey, TValue>)(object)new SmallValueTypeComparableFrozenDictionary<TKey, TValue>(source);
}
// Otherwise, use an implementation optimized for a small number of value types using the default comparer.
return (FrozenDictionary<TKey, TValue>)(object)new SmallValueTypeDefaultComparerFrozenDictionary<TKey, TValue>(source);
}
// Use a hash-based implementation.
// For Int32 keys, we can reuse the key storage as the hash storage, saving on space and extra indirection.
if (typeof(TKey) == typeof(int))
{
return (FrozenDictionary<TKey, TValue>)(object)new Int32FrozenDictionary<TValue>((Dictionary<int, TValue>)(object)source);
}
// Fallback to an implementation usable with any value type and the default comparer.
return new ValueTypeDefaultComparerFrozenDictionary<TKey, TValue>(source);
}
// Optimize for string keys with the default, Ordinal, or OrdinalIgnoreCase comparers.
// If the key is a string and the comparer is known to provide ordinal (case-sensitive or case-insensitive) semantics,
// we can use an implementation that's able to examine and optimize based on lengths and/or subsequences within those strings.
if (typeof(TKey) == typeof(string) &&
(ReferenceEquals(comparer, EqualityComparer<TKey>.Default) || ReferenceEquals(comparer, StringComparer.Ordinal) || ReferenceEquals(comparer, StringComparer.OrdinalIgnoreCase)))
{
IEqualityComparer<string> stringComparer = (IEqualityComparer<string>)(object)comparer;
// keys and values are needed for every strategy
string[] keys = (string[])(object)source.Keys.ToArray();
TValue[] values = source.Values.ToArray();
// Calculate the minimum and maximum lengths of the strings in the dictionary. Several of the analyses need this.
int minLength = int.MaxValue, maxLength = 0;
ulong lengthFilter = 0;
foreach (string key in keys)
{
if (key.Length < minLength) minLength = key.Length;
if (key.Length > maxLength) maxLength = key.Length;
lengthFilter |= (1UL << (key.Length % 64));
}
Debug.Assert(minLength >= 0 && maxLength >= minLength);
// Try to create an implementation that uses length buckets, where each bucket contains up to only a few strings of the same length.
FrozenDictionary<string, TValue>? frozenDictionary = LengthBucketsFrozenDictionary<TValue>.CreateLengthBucketsFrozenDictionaryIfAppropriate(keys, values, stringComparer, minLength, maxLength);
if (frozenDictionary is not null)
{
return (FrozenDictionary<TKey, TValue>)(object)frozenDictionary;
}
// Analyze the keys for unique substrings and create an implementation that minimizes the cost of hashing keys.
KeyAnalyzer.AnalysisResults analysis = KeyAnalyzer.Analyze(keys, ReferenceEquals(stringComparer, StringComparer.OrdinalIgnoreCase), minLength, maxLength);
if (analysis.SubstringHashing)
{
if (analysis.RightJustifiedSubstring)
{
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
: new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
else
{
frozenDictionary = analysis.HashCount == 1
? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
: new OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
}
else
{
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
: new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
else
{
frozenDictionary = analysis.HashCount == 1
? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
: new OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
}
}
else
{
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, lengthFilter)
: new OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, lengthFilter);
}
else
{
frozenDictionary = new OrdinalStringFrozenDictionary_Full<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, lengthFilter);
}
}
return (FrozenDictionary<TKey, TValue>)(object)frozenDictionary;
}
// Optimize for very small numbers of items by using a specialized implementation that just does a linear search.
if (source.Count <= Constants.MaxItemsInSmallFrozenCollection)
{
// Use the specialized dictionary for low item counts.
return new SmallFrozenDictionary<TKey, TValue>(source);
}
// No special-cases apply. Use the default frozen dictionary.
return new DefaultFrozenDictionary<TKey, TValue>(source);
}
}
/// <summary>Provides an immutable, read-only dictionary optimized for fast lookup and enumeration.</summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in this dictionary.</typeparam>
/// <remarks>
/// <see cref="FrozenDictionary{TKey, TValue}"/> is immutable and is optimized for situations where a dictionary
/// is created very infrequently but is used very frequently at run-time. It has a relatively high
/// cost to create but provides excellent lookup performance. Thus, it is ideal for cases
/// where a dictionary is created once, potentially at the startup of an application, and is used throughout
/// the remainder of the life of the application. <see cref="FrozenDictionary{TKey, TValue}"/> should only be
/// initialized with trusted keys, as the details of the keys impacts construction time.
/// </remarks>
[DebuggerTypeProxy(typeof(ImmutableDictionaryDebuggerProxy<,>))]
[DebuggerDisplay("Count = {Count}")]
public abstract partial class FrozenDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary
where TKey : notnull
{
/// <summary>Initialize the dictionary.</summary>
/// <param name="comparer">The comparer to use and to expose from <see cref="Comparer"/>.</param>
private protected FrozenDictionary(IEqualityComparer<TKey> comparer) => Comparer = comparer;
/// <summary>Gets an empty <see cref="FrozenDictionary{TKey, TValue}"/>.</summary>
public static FrozenDictionary<TKey, TValue> Empty { get; } = new EmptyFrozenDictionary<TKey, TValue>(EqualityComparer<TKey>.Default);
/// <summary>Gets the comparer used by this dictionary.</summary>
public IEqualityComparer<TKey> Comparer { get; }
/// <summary>
/// Gets a collection containing the keys in the dictionary.
/// </summary>
/// <remarks>
/// The order of the keys in the dictionary is unspecified, but it is the same order as the associated values returned by the <see cref="Values"/> property.
/// </remarks>
public ImmutableArray<TKey> Keys => ImmutableCollectionsMarshal.AsImmutableArray(KeysCore);
/// <inheritdoc cref="Keys" />
private protected abstract TKey[] KeysCore { get; }
/// <inheritdoc />
ICollection<TKey> IDictionary<TKey, TValue>.Keys =>
Keys is { Length: > 0 } keys ? keys : Array.Empty<TKey>();
/// <inheritdoc />
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys =>
((IDictionary<TKey, TValue>)this).Keys;
/// <inheritdoc />
ICollection IDictionary.Keys => Keys;
/// <summary>
/// Gets a collection containing the values in the dictionary.
/// </summary>
/// <remarks>
/// The order of the values in the dictionary is unspecified, but it is the same order as the associated keys returned by the <see cref="Keys"/> property.
/// </remarks>
public ImmutableArray<TValue> Values => ImmutableCollectionsMarshal.AsImmutableArray(ValuesCore);
/// <inheritdoc cref="Values" />
private protected abstract TValue[] ValuesCore { get; }
ICollection<TValue> IDictionary<TKey, TValue>.Values =>
Values is { Length: > 0 } values ? values : Array.Empty<TValue>();
/// <inheritdoc />
ICollection IDictionary.Values => Values;
/// <inheritdoc />
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values =>
((IDictionary<TKey, TValue>)this).Values;
/// <summary>Gets the number of key/value pairs contained in the dictionary.</summary>
public int Count => CountCore;
/// <inheritdoc cref="Count" />
private protected abstract int CountCore { get; }
/// <summary>Copies the elements of the dictionary to an array of type <see cref="KeyValuePair{TKey, TValue}"/>, starting at the specified <paramref name="destinationIndex"/>.</summary>
/// <param name="destination">The array that is the destination of the elements copied from the dictionary.</param>
/// <param name="destinationIndex">The zero-based index in <paramref name="destination"/> at which copying begins.</param>
public void CopyTo(KeyValuePair<TKey, TValue>[] destination, int destinationIndex)
{
ThrowHelper.ThrowIfNull(destination);
CopyTo(destination.AsSpan(destinationIndex));
}
/// <summary>Copies the elements of the dictionary to a span of type <see cref="KeyValuePair{TKey, TValue}"/>.</summary>
/// <param name="destination">The span that is the destination of the elements copied from the dictionary.</param>
public void CopyTo(Span<KeyValuePair<TKey, TValue>> destination)
{
if (destination.Length < Count)
{
ThrowHelper.ThrowIfDestinationTooSmall();
}
TKey[] keys = KeysCore;
TValue[] values = ValuesCore;
Debug.Assert(keys.Length == values.Length);
for (int i = 0; i < keys.Length; i++)
{
destination[i] = new KeyValuePair<TKey, TValue>(keys[i], values[i]);
}
}
/// <inheritdoc />
void ICollection.CopyTo(Array array, int index)
{
ThrowHelper.ThrowIfNull(array);
if (array.Rank != 1)
{
throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
}
if (array.GetLowerBound(0) != 0)
{
throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
}
if ((uint)index > (uint)array.Length)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < Count)
{
throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall, nameof(array));
}
if (array is KeyValuePair<TKey, TValue>[] pairs)
{
foreach (KeyValuePair<TKey, TValue> item in this)
{
pairs[index++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value);
}
}
else if (array is DictionaryEntry[] dictEntryArray)
{
foreach (KeyValuePair<TKey, TValue> item in this)
{
dictEntryArray[index++] = new DictionaryEntry(item.Key, item.Value);
}
}
else
{
if (array is not object[] objects)
{
throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
}
try
{
foreach (KeyValuePair<TKey, TValue> item in this)
{
objects[index++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value);
}
}
catch (ArrayTypeMismatchException)
{
throw new ArgumentException(SR.Argument_IncompatibleArrayType, nameof(array));
}
}
}
/// <inheritdoc />
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => true;
/// <inheritdoc />
bool IDictionary.IsReadOnly => true;
/// <inheritdoc />
bool IDictionary.IsFixedSize => true;
/// <inheritdoc />
bool ICollection.IsSynchronized => false;
/// <inheritdoc />
object ICollection.SyncRoot => this;
/// <inheritdoc />
object? IDictionary.this[object key]
{
get
{
ThrowHelper.ThrowIfNull(key);
return key is TKey tkey && TryGetValue(tkey, out TValue? value) ?
value :
(object?)null;
}
set => throw new NotSupportedException();
}
/// <summary>Gets either a reference to a <typeparamref name="TValue"/> in the dictionary or a null reference if the key does not exist in the dictionary.</summary>
/// <param name="key">The key used for lookup.</param>
/// <returns>A reference to a <typeparamref name="TValue"/> in the dictionary or a null reference if the key does not exist in the dictionary.</returns>
/// <remarks>The null reference can be detected by calling <see cref="Unsafe.IsNullRef"/>.</remarks>
public ref readonly TValue GetValueRefOrNullRef(TKey key)
{
if (key is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(key));
}
return ref GetValueRefOrNullRefCore(key);
}
/// <inheritdoc cref="GetValueRefOrNullRef" />
private protected abstract ref readonly TValue GetValueRefOrNullRefCore(TKey key);
/// <inheritdoc cref="GetValueRefOrNullRef" />
/// <remarks>
/// This is virtual rather than abstract because only some implementations need to support this, e.g. implementations that
/// are only ever used with the default comparer won't ever hit code paths that use this, at least not
/// until/if we make `EqualityComparer{string}.Default` implement `IAlternateEqualityComparer{ReadOnlySpan{char}, string}`.
///
/// This unfortunately needs to be a generic virtual method, but the only other known option involves having a dedicated
/// class instance such that the generic can be baked into that, where the methods on it are still virtual but don't have
/// extra generic methods. But for most implementations, either a) that class would need to be allocated as part of
/// TryGetAlternateLookup, which would be more expensive for use cases where someone needs a lookup for just a few operations,
/// or b) a dictionary of those instances would need to be maintained, which just replaces the runtime's dictionary for a GVM
/// with a custom one here.
/// </remarks>
private protected virtual ref readonly TValue GetValueRefOrNullRefCore<TAlternateKey>(TAlternateKey key)
where TAlternateKey : notnull
#if NET9_0_OR_GREATER
#pragma warning disable SA1001 // Commas should be spaced correctly
// This method will only ever be used on .NET 9+. However, because of how everything is structured,
// and to avoid a proliferation of conditional files for many of the derived types (in particular
// for the OrdinalString* implementations), we still build this method into all builds, even though
// it'll be unused. But we can't use the allows ref struct constraint downlevel, hence the #if.
, allows ref struct
#pragma warning restore SA1001
#endif
=> ref Unsafe.NullRef<TValue>();
/// <summary>Gets a reference to the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get.</param>
/// <returns>A reference to the value associated with the specified key.</returns>
/// <exception cref="KeyNotFoundException"><paramref name="key"/> does not exist in the collection.</exception>
public ref readonly TValue this[TKey key]
{
get
{
ref readonly TValue valueRef = ref GetValueRefOrNullRef(key);
if (Unsafe.IsNullRef(ref Unsafe.AsRef(in valueRef)))
{
ThrowHelper.ThrowKeyNotFoundException(key);
}
return ref valueRef;
}
}
/// <inheritdoc />
TValue IDictionary<TKey, TValue>.this[TKey key]
{
get => this[key];
set => throw new NotSupportedException();
}
/// <inheritdoc />
TValue IReadOnlyDictionary<TKey, TValue>.this[TKey key] =>
this[key];
/// <summary>Determines whether the dictionary contains the specified key.</summary>
/// <param name="key">The key to locate in the dictionary.</param>
/// <returns><see langword="true"/> if the dictionary contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
public bool ContainsKey(TKey key) =>
!Unsafe.IsNullRef(ref Unsafe.AsRef(in GetValueRefOrNullRef(key)));
/// <inheritdoc />
bool IDictionary.Contains(object key)
{
ThrowHelper.ThrowIfNull(key);
return key is TKey tkey && ContainsKey(tkey);
}
/// <inheritdoc />
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) =>
TryGetValue(item.Key, out TValue? value) &&
EqualityComparer<TValue>.Default.Equals(value, item.Value);
/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The 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>
/// <returns><see langword="true"/> if the dictionary contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
ref readonly TValue valueRef = ref GetValueRefOrNullRef(key);
if (!Unsafe.IsNullRef(ref Unsafe.AsRef(in valueRef)))
{
value = valueRef;
return true;
}
value = default;
return false;
}
/// <summary>Returns an enumerator that iterates through the dictionary.</summary>
/// <returns>An enumerator that iterates through the dictionary.</returns>
public Enumerator GetEnumerator() => GetEnumeratorCore();
/// <inheritdoc cref="GetEnumerator" />
private protected abstract Enumerator GetEnumeratorCore();
/// <inheritdoc />
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
Count == 0 ? ((IList<KeyValuePair<TKey, TValue>>)Array.Empty<KeyValuePair<TKey, TValue>>()).GetEnumerator() :
GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() =>
Count == 0 ? Array.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator() :
GetEnumerator();
/// <inheritdoc />
IDictionaryEnumerator IDictionary.GetEnumerator() =>
new DictionaryEnumerator<TKey, TValue>(GetEnumerator());
/// <inheritdoc />
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) => throw new NotSupportedException();
/// <inheritdoc />
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => throw new NotSupportedException();
/// <inheritdoc />
void IDictionary.Add(object key, object? value) => throw new NotSupportedException();
/// <inheritdoc />
bool IDictionary<TKey, TValue>.Remove(TKey key) => throw new NotSupportedException();
/// <inheritdoc />
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => throw new NotSupportedException();
/// <inheritdoc />
void IDictionary.Remove(object key) => throw new NotSupportedException();
/// <inheritdoc />
void ICollection<KeyValuePair<TKey, TValue>>.Clear() => throw new NotSupportedException();
/// <inheritdoc />
void IDictionary.Clear() => throw new NotSupportedException();
/// <summary>Enumerates the elements of a <see cref="FrozenDictionary{TKey, TValue}"/>.</summary>
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
private readonly TKey[] _keys;
private readonly TValue[] _values;
private int _index;
/// <summary>Initialize the enumerator with the specified keys and values.</summary>
internal Enumerator(TKey[] keys, TValue[] values)
{
Debug.Assert(keys.Length == values.Length);
_keys = keys;
_values = values;
_index = -1;
}
/// <inheritdoc cref="IEnumerator.MoveNext" />
public bool MoveNext()
{
_index++;
if ((uint)_index < (uint)_keys.Length)
{
return true;
}
_index = _keys.Length;
return false;
}
/// <inheritdoc cref="IEnumerator{T}.Current" />
public readonly KeyValuePair<TKey, TValue> Current
{
get
{
if ((uint)_index >= (uint)_keys.Length)
{
ThrowHelper.ThrowInvalidOperationException();
}
return new KeyValuePair<TKey, TValue>(_keys[_index], _values[_index]);
}
}
/// <inheritdoc />
object IEnumerator.Current => Current;
/// <inheritdoc />
void IEnumerator.Reset() => _index = -1;
/// <inheritdoc />
void IDisposable.Dispose() { }
}
}
}
|