using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace PkmnLib.Static.Utils; /// /// A case-insensitive string key. We use this class for things like looking up data from dictionaries, etc. /// /// /// This is a struct, as it's effectively just a wrapper around a single reference object. Heap allocation would be silly. /// public readonly struct StringKey : IEquatable, IEquatable { private static readonly ConcurrentDictionary HashCodes = new(); private readonly string _key; private readonly int _hashCode; /// public StringKey(string key) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Key cannot be null or whitespace.", nameof(key)); _key = key; if (!HashCodes.TryGetValue(_key, out _hashCode)) { _hashCode = HashCodes[_key] = StringComparer.InvariantCultureIgnoreCase.GetHashCode(_key); } } /// /// Converts a to a . /// public static implicit operator string(StringKey key) => key._key; /// /// Converts a to a . /// [return: NotNullIfNotNull("key")] public static implicit operator StringKey?(string? key) => string.IsNullOrWhiteSpace(key) ? null! : new StringKey(key); /// /// Converts a to a . /// Throws an if the key is null or whitespace. /// public static implicit operator StringKey(string key) => string.IsNullOrWhiteSpace(key) ? throw new ArgumentException("Key cannot be null or whitespace.", nameof(key)) : new StringKey(key); /// public override string ToString() => _key.ToLowerInvariant(); /// public override bool Equals(object? obj) { return obj switch { StringKey other => Equals(other), string str => Equals(str), _ => false, }; } /// public bool Equals(StringKey other) => _hashCode == other._hashCode; /// public bool Equals(string other) => string.Equals(_key, other, StringComparison.InvariantCultureIgnoreCase); /// public override int GetHashCode() => _hashCode; /// public static bool operator ==(StringKey? left, string? right) => (left is null && right is null) || (right != null && (left?.Equals(right) ?? false)); /// public static bool operator !=(StringKey? left, string? right) => !(left == right); /// public static bool operator ==(StringKey? left, StringKey right) => left?.Equals(right) ?? false; /// public static bool operator !=(StringKey? left, StringKey right) => !(left == right); public bool Contains(StringKey other) => _key.IndexOf(other._key, StringComparison.InvariantCultureIgnoreCase) >= 0; }