diff --git a/src/Orleans.Core.Abstractions/IDs/GrainId.cs b/src/Orleans.Core.Abstractions/IDs/GrainId.cs index 37b85af27e..a098322b83 100644 --- a/src/Orleans.Core.Abstractions/IDs/GrainId.cs +++ b/src/Orleans.Core.Abstractions/IDs/GrainId.cs @@ -12,7 +12,7 @@ namespace Orleans.Runtime /// [Serializable, GenerateSerializer, Immutable] [JsonConverter(typeof(GrainIdJsonConverter))] - public readonly struct GrainId : IEquatable, IComparable, ISerializable, ISpanFormattable + public readonly struct GrainId : IEquatable, IComparable, ISerializable, ISpanFormattable, ISpanParsable { [Id(0)] private readonly GrainType _type; @@ -64,36 +64,71 @@ private GrainId(SerializationInfo info, StreamingContext context) public static GrainId Create(GrainType type, IdSpan key) => new GrainId(type, key); /// - /// Creates a new instance. + /// Parses a from the span. /// - public static GrainId Parse(string value) + public static GrainId Parse(ReadOnlySpan value, IFormatProvider? provider = null) { - if (!TryParse(value, out var result)) + if (!TryParse(value, provider, out var result)) { ThrowInvalidGrainId(value); - static void ThrowInvalidGrainId(string value) => throw new ArgumentException($"Unable to parse \"{value}\" as a grain id"); + static void ThrowInvalidGrainId(ReadOnlySpan value) => throw new ArgumentException($"Unable to parse \"{value}\" as a grain id"); } return result; } /// - /// Creates a new instance. + /// Tries to parse a from the span. /// - public static bool TryParse(string? value, out GrainId grainId) + /// if a valid was parsed. otherwise + public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out GrainId result) { int i; - if (value is null || (i = value.IndexOf('/')) < 0) + if ((i = value.IndexOf('/')) < 0) { - grainId = default; + result = default; return false; } - grainId = new(new GrainType(Encoding.UTF8.GetBytes(value, 0, i)), new IdSpan(Encoding.UTF8.GetBytes(value, i + 1, value.Length - i - 1))); + var typeSpan = value[0..i]; + var type = new byte[Encoding.UTF8.GetByteCount(typeSpan)]; + Encoding.UTF8.GetBytes(typeSpan, type); + + var idSpan = value[(i + 1)..]; + var id = new byte[Encoding.UTF8.GetByteCount(idSpan)]; + Encoding.UTF8.GetBytes(idSpan, id); + + result = new(new GrainType(type), new IdSpan(id)); return true; } + /// + /// Parses a from the string. + /// + public static GrainId Parse(string value) + => Parse(value.AsSpan(), null); + + /// + /// Parses a from the string. + /// + public static GrainId Parse(string value, IFormatProvider? provider = null) + => Parse(value.AsSpan(), provider); + + /// + /// Tries to parse a from the string. + /// + /// if a valid was parsed. otherwise + public static bool TryParse(string? value, out GrainId result) + => TryParse(value.AsSpan(), null, out result); + + /// + /// Tries to parse a from the string. + /// + /// if a valid was parsed. otherwise + public static bool TryParse(string? value, IFormatProvider? provider, out GrainId result) + => TryParse(value.AsSpan(), provider, out result); + /// /// if this instance is the default value, if it is not. /// @@ -167,7 +202,19 @@ bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, Re public sealed class GrainIdJsonConverter : JsonConverter { /// - public override GrainId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => GrainId.Parse(reader.GetString()!); + public override GrainId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var valueLength = reader.HasValueSequence + ? checked((int)reader.ValueSequence.Length) + : reader.ValueSpan.Length; + + Span buf = stackalloc char[valueLength]; + + var written = reader.CopyString(buf); + buf = buf[..written]; + + return GrainId.Parse(buf); + } /// public override void Write(Utf8JsonWriter writer, GrainId value, JsonSerializerOptions options)