// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Animancer { /// /// A wrapper which allows fast reference equality checks and dictionary usage /// by ensuring that users of identical strings are given the same /// instead of needing to compare each character in the strings. /// /// /// Rather than a constructor, instances of this class are acquired via /// or via implicit conversion from (which calls the same method). /// /// Unlike UnityEngine.InputSystem.Utilities.InternedString, /// this implementation is case-sensitive and treats null and "" as not equal. /// It's also a class to allow usage as a key in a dictionary keyed by without boxing. /// /// Example: /// /// public static readonly StringReference MyStringReference = "My String"; /// /// /// https://kybernetik.com.au/animancer/api/Animancer/StringReference public class StringReference : IComparable, IConvertable { /************************************************************************************************************************/ /// The encapsulated . /// This field will never be null. public readonly string String; /************************************************************************************************************************/ private static readonly Dictionary StringToReference = new(256); /// Returns a containing the `value`. /// /// The returned reference is cached and the same one will be /// returned each time this method is called with the same `value`. /// /// Returns null if the `value` is null. /// /// The `value` is case sensitive. /// public static StringReference Get(string value) { if (value is null) return null; if (!StringToReference.TryGetValue(value, out var reference)) StringToReference.Add(value, reference = new(value)); // This system could be made case insensitive based on a static bool. // If true, convert the value to lower case for the dictionary key but still reference the original. // When changing the setting, rebuild the dictionary with the appropriate keys. return reference; } /************************************************************************************************************************/ /// Creates a new array of s to the `strings`. public static StringReference[] Get(params string[] strings) { if (strings == null) return null; if (strings.Length == 0) return Array.Empty(); var references = new StringReference[strings.Length]; for (int i = 0; i < strings.Length; i++) references[i] = strings[i]; return references; } /************************************************************************************************************************/ /// Returns a containing the `value` if one has already been created. /// The `value` is case sensitive. public static bool TryGet(string value, out StringReference reference) { if (value is not null && StringToReference.TryGetValue(value, out reference)) return true; reference = null; return false; } /************************************************************************************************************************/ /// Creates a new . [MethodImpl(MethodImplOptions.AggressiveInlining)] private StringReference(string value) => String = value; /// Calls . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator StringReference(string value) => Get(value); /// [Internal] /// Returns a new which will not be shared by regular calls to /// . /// /// /// This means the reference will never be equal to others /// even if they contain the same . /// internal static StringReference Unique(string value) => new(value); /************************************************************************************************************************/ /// Returns the . [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() => String; /// Returns the . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator string(StringReference value) => value?.String; /************************************************************************************************************************/ /// string IConvertable.Convert() => String; /************************************************************************************************************************/ /// Compares the s. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(StringReference other) => String.CompareTo(other?.String); /************************************************************************************************************************/ } /// Extension methods for . public static class StringReferenceExtensions { /************************************************************************************************************************/ /// Is the `reference` null or its empty? /// Similar to . public static bool IsNullOrEmpty(this StringReference reference) => reference is null || reference.String.Length == 0; /************************************************************************************************************************/ /// /// Is the equal to the `other` /// when treating "" as equal to null? /// public static bool EqualsWhereEmptyIsNull(this StringReference reference, StringReference other) { if (reference == other) return true; else if (reference == null) return other.String.Length == 0; else if (reference.String.Length == 0) return other == null; else return false; } /************************************************************************************************************************/ /// Creates a new array containing the s. public static string[] ToStrings(this StringReference[] references) { if (references == null) return null; if (references.Length == 0) return Array.Empty(); var strings = new string[references.Length]; for (int i = 0; i < references.Length; i++) strings[i] = references[i]; return strings; } /************************************************************************************************************************/ } }