198 lines
8.5 KiB
C#
198 lines
8.5 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Animancer
|
|
{
|
|
/// <summary>
|
|
/// A <see cref="string"/> wrapper which allows fast reference equality checks and dictionary usage
|
|
/// by ensuring that users of identical strings are given the same <see cref="StringReference"/>
|
|
/// instead of needing to compare each character in the strings.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Rather than a constructor, instances of this class are acquired via <see cref="Get(string)"/>
|
|
/// or via implicit conversion from <see cref="string"/> (which calls the same method).
|
|
/// <para></para>
|
|
/// Unlike <c>UnityEngine.InputSystem.Utilities.InternedString</c>,
|
|
/// this implementation is case-sensitive and treats <c>null</c> and <c>""</c> as not equal.
|
|
/// It's also a class to allow usage as a key in a dictionary keyed by <see cref="object"/> without boxing.
|
|
/// <para></para>
|
|
/// <strong>Example:</strong>
|
|
/// <code>
|
|
/// public static readonly StringReference MyStringReference = "My String";
|
|
/// </code>
|
|
/// </remarks>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/StringReference
|
|
public class StringReference :
|
|
IComparable<StringReference>,
|
|
IConvertable<string>
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The encapsulated <see cref="string"/>.</summary>
|
|
/// <remarks>This field will never be null.</remarks>
|
|
public readonly string String;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private static readonly Dictionary<string, StringReference>
|
|
StringToReference = new(256);
|
|
|
|
/// <summary>Returns a <see cref="StringReference"/> containing the `value`.</summary>
|
|
/// <remarks>
|
|
/// The returned reference is cached and the same one will be
|
|
/// returned each time this method is called with the same `value`.
|
|
/// <para></para>
|
|
/// Returns <c>null</c> if the `value` is <c>null</c>.
|
|
/// <para></para>
|
|
/// The `value` is case sensitive.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new array of <see cref="StringReference"/>s to the `strings`.</summary>
|
|
public static StringReference[] Get(params string[] strings)
|
|
{
|
|
if (strings == null)
|
|
return null;
|
|
|
|
if (strings.Length == 0)
|
|
return Array.Empty<StringReference>();
|
|
|
|
var references = new StringReference[strings.Length];
|
|
for (int i = 0; i < strings.Length; i++)
|
|
references[i] = strings[i];
|
|
return references;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns a <see cref="StringReference"/> containing the `value` if one has already been created.</summary>
|
|
/// <remarks>The `value` is case sensitive.</remarks>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="StringReference"/>.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private StringReference(string value)
|
|
=> String = value;
|
|
|
|
/// <summary>Calls <see cref="Get(string)"/>.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static implicit operator StringReference(string value)
|
|
=> Get(value);
|
|
|
|
/// <summary>[Internal]
|
|
/// Returns a new <see cref="StringReference"/> which will not be shared by regular calls to
|
|
/// <see cref="Get(string)"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This means the reference will never be equal to others
|
|
/// even if they contain the same <see cref="String"/>.
|
|
/// </remarks>
|
|
internal static StringReference Unique(string value)
|
|
=> new(value);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns the <see cref="String"/>.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override string ToString()
|
|
=> String;
|
|
|
|
/// <summary>Returns the <see cref="String"/>.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static implicit operator string(StringReference value)
|
|
=> value?.String;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <inheritdoc/>
|
|
string IConvertable<string>.Convert()
|
|
=> String;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Compares the <see cref="String"/>s.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public int CompareTo(StringReference other)
|
|
=> String.CompareTo(other?.String);
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
|
|
/// <summary>Extension methods for <see cref="StringReference"/>.</summary>
|
|
public static class StringReferenceExtensions
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Is the `reference` <c>null</c> or its <see cref="StringReference.String"/> empty?</summary>
|
|
/// <remarks>Similar to <see cref="string.IsNullOrEmpty"/>.</remarks>
|
|
public static bool IsNullOrEmpty(this StringReference reference)
|
|
=> reference is null
|
|
|| reference.String.Length == 0;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Is the <see cref="StringReference.String"/> equal to the `other`
|
|
/// when treating <c>""</c> as equal to <c>null</c>?
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new array containing the <see cref="StringReference.String"/>s.</summary>
|
|
public static string[] ToStrings(this StringReference[] references)
|
|
{
|
|
if (references == null)
|
|
return null;
|
|
|
|
if (references.Length == 0)
|
|
return Array.Empty<string>();
|
|
|
|
var strings = new string[references.Length];
|
|
for (int i = 0; i < references.Length; i++)
|
|
strings[i] = references[i];
|
|
return strings;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|