diff --git a/src/core-unit/Unit.Tests/UnitFormatTests/UnitFormatTests.Format.Utf8.cs b/src/core-unit/Unit.Tests/UnitFormatTests/UnitFormatTests.Format.Utf8.cs new file mode 100644 index 00000000..302f81e8 --- /dev/null +++ b/src/core-unit/Unit.Tests/UnitFormatTests/UnitFormatTests.Format.Utf8.cs @@ -0,0 +1,20 @@ +using System; +using System.Text; +using Xunit; + +namespace PrimeFuncPack.Core.Tests; + +partial class UnitFormatTests +{ + [Theory] + [MemberData(nameof(ExpectedFormatCases))] + public static void FormatToUtf8_ExpectCorrespondingResult(string? format, string expected) + { + var expectedBytes = Encoding.UTF8.GetBytes(expected); + + var source = default(Unit); + var actualBytes = source.FormatToUtf8(format); + + Assert.Equal(expectedBytes, actualBytes); + } +} diff --git a/src/core-unit/Unit/Unit.csproj b/src/core-unit/Unit/Unit.csproj index ba78ed38..c3689020 100644 --- a/src/core-unit/Unit/Unit.csproj +++ b/src/core-unit/Unit/Unit.csproj @@ -19,7 +19,7 @@ PrimeFuncPack Core.Unit is a core library for .NET consisting of Unit type targeted for use in functional programming. System PrimeFuncPack.Core.Unit - 4.0.0-rc.1 + 4.0.0-rc.2 diff --git a/src/core-unit/Unit/Unit/Unit.Factory.From.cs b/src/core-unit/Unit/Unit/Unit.Factory.From.cs index dc780e87..ba684c72 100644 --- a/src/core-unit/Unit/Unit/Unit.Factory.From.cs +++ b/src/core-unit/Unit/Unit/Unit.Factory.From.cs @@ -7,6 +7,9 @@ namespace System; partial struct Unit { // We maintain that Unit can be derived from any input value. + // + // This factory is for use in method group conversion instead of lambda expressions + // like "_ => default(Unit)", "_ => new Unit()", or "_ => Unit.Value". [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Unit From(TResult result) => default; diff --git a/src/core-unit/Unit/Unit/Unit.Factory.cs b/src/core-unit/Unit/Unit/Unit.Factory.cs index 33b714eb..6aad7c75 100644 --- a/src/core-unit/Unit/Unit/Unit.Factory.cs +++ b/src/core-unit/Unit/Unit/Unit.Factory.cs @@ -4,8 +4,14 @@ namespace System; partial struct Unit { + // This singleton value is for use when it's needed to emphasize that Unit is being used. + // In other cases, prefer "default(Unit)" or "new Unit()" for better performance. + public static readonly Unit Value; + // This factory is for use in method group conversion instead of lambda expressions + // like "() => default(Unit)", "() => new Unit()", or "() => Unit.Value". + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Unit Get() => default; } diff --git a/src/core-unit/Unit/Unit/Unit.Format.cs b/src/core-unit/Unit/Unit/Unit.Format.cs index 8f0e8d10..1db10da0 100644 --- a/src/core-unit/Unit/Unit/Unit.Format.cs +++ b/src/core-unit/Unit/Unit/Unit.Format.cs @@ -1,7 +1,5 @@ #pragma warning disable CA1822 // Mark members as static -using System.Text; - namespace System; partial struct Unit @@ -10,6 +8,13 @@ public string Format(ReadOnlySpan format) => UnitFormatter.Format(format); + // We use a design with byte array result and copying the result bytes for following: + // - to provide to the user a possibility to work with both bytes and a span over them; + // - not to expose the inner cache state despite it's protected by ReadOnlySpan. + public byte[] FormatToUtf8(ReadOnlySpan format) + => + UnitFormatter.FormatToUtf8Span(format).ToArray(); + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format) { var formatted = UnitFormatter.Format(format); @@ -22,7 +27,7 @@ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format) { - var utf8Formatted = UnitFormatter.FormatToUtf8(format); + var utf8Formatted = UnitFormatter.FormatToUtf8Span(format); // Call TryCopyTo instead of CopyTo not to duplicate the length check here var result = utf8Formatted.TryCopyTo(utf8Destination); diff --git a/src/core-unit/Unit/Unit/Unit.Parse.Static.Explicit.cs b/src/core-unit/Unit/Unit/Unit.Parse.Static.Explicit.cs index a4714d9e..56892239 100644 --- a/src/core-unit/Unit/Unit/Unit.Parse.Static.Explicit.cs +++ b/src/core-unit/Unit/Unit/Unit.Parse.Static.Explicit.cs @@ -3,7 +3,7 @@ partial struct Unit { // We maintain that Unit can be derived from any input value. - + // // For the string versions we follow the contract which defines fail on null input, // to keep consistency between Parse and TryParse methods, taking into account that // TryParse has [NotNullWhen(true)] attribute on the input parameter. diff --git a/src/core-unit/Unit/UnitExtensions/ToUnit.cs b/src/core-unit/Unit/UnitExtensions/ToUnit.cs index 7f662ce8..e8c303bf 100644 --- a/src/core-unit/Unit/UnitExtensions/ToUnit.cs +++ b/src/core-unit/Unit/UnitExtensions/ToUnit.cs @@ -1,10 +1,12 @@ -using System.Runtime.CompilerServices; - -namespace System; +namespace System; partial class UnitExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + // We maintain that Unit can be derived from any input value. + // Unit.From is being called instead of returning default(Unit) for the sake of clarity. + // + // This extension is for more convenient syntax when converting to Unit from a result value. + public static Unit ToUnit(this TResult result) => Unit.From(result); diff --git a/src/core-unit/Unit/UnitFormatting/UnitFormUtf8.cs b/src/core-unit/Unit/UnitFormatting/UnitFormUtf8.cs index b6e1dfc0..43f17be5 100644 --- a/src/core-unit/Unit/UnitFormatting/UnitFormUtf8.cs +++ b/src/core-unit/Unit/UnitFormatting/UnitFormUtf8.cs @@ -17,6 +17,8 @@ internal static class UnitFormUtf8 internal static ReadOnlySpan JsonObjExtended => new(InnerBytes.JsonObjExtended); internal static ReadOnlySpan EmptyExtended => new(InnerBytes.EmptyExtended); + // We initialize the byte arrays inline with no static ctor to obtain and cache them + // lazily only on demand, and also isolate them in a nested class for thread-safety. private static class InnerBytes { internal static readonly byte[] General = InnerGetBytes(UnitForm.General); @@ -29,6 +31,7 @@ private static class InnerBytes internal static readonly byte[] JsonObjExtended = InnerGetBytes(UnitForm.JsonObjExtended); internal static readonly byte[] EmptyExtended = InnerGetBytes(UnitForm.EmptyExtended); + // We use the most proven method to obtain UTF-8 bytes. private static byte[] InnerGetBytes(string s) => Encoding.UTF8.GetBytes(s); diff --git a/src/core-unit/Unit/UnitFormatting/UnitFormatter.FormatToUtf8.cs b/src/core-unit/Unit/UnitFormatting/UnitFormatter.FormatToUtf8.cs index 798a4604..51d961b2 100644 --- a/src/core-unit/Unit/UnitFormatting/UnitFormatter.FormatToUtf8.cs +++ b/src/core-unit/Unit/UnitFormatting/UnitFormatter.FormatToUtf8.cs @@ -5,7 +5,7 @@ namespace System; partial class UnitFormatter { [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ReadOnlySpan FormatToUtf8(ReadOnlySpan format) + internal static ReadOnlySpan FormatToUtf8Span(ReadOnlySpan format) => format switch {