├── .editorconfig
├── .github
├── dependabot.yaml
└── workflows
│ ├── build-debug.yaml
│ ├── build-release.yaml
│ └── stale.yaml
├── .gitignore
├── Directory.Build.props
├── Icon.png
├── LICENSE.md
├── README.md
├── Utf8StringInterpolation.sln
├── docs
└── images.pptx
├── opensource.snk
├── sandbox
├── ConsoleApp
│ ├── ConsoleApp.csproj
│ ├── Program.cs
│ └── ReadMeSample.cs
└── Net6ConsoleApp
│ ├── Net6ConsoleApp.csproj
│ └── Program.cs
├── src
└── Utf8StringInterpolation
│ ├── ArrayBufferWriter.cs
│ ├── Internal
│ └── ArrayBufferWriterPool.cs
│ ├── Shims.NetStandard2.cs
│ ├── Shims.cs
│ ├── Utf8String.cs
│ ├── Utf8StringBuffer.cs
│ ├── Utf8StringInterpolation.csproj
│ ├── Utf8StringWriter.AppendFormatted.cs
│ ├── Utf8StringWriter.AppendFormatted.tt
│ └── Utf8StringWriter.cs
└── tests
└── Utf8StringInterpolation.Tests
├── CompositeFormatTest.cs
├── EnumTest.cs
├── FormatTest.cs
├── JoinTest.cs
├── MathF.cs
├── Primitives.cs
├── Shims.cs
├── Utf8StringBuilderTest.cs
├── Utf8StringInterpolation.Tests.csproj
├── Utf8StringTest.cs
└── _ShouldExtensions.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | # Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker)
13 | spelling_exclusion_path = ./exclusion.dic
14 |
15 | [*.cs]
16 | indent_size = 4
17 | charset = utf-8-bom
18 | end_of_line = unset
19 |
20 | # Solution files
21 | [*.{sln,slnx}]
22 | end_of_line = unset
23 |
24 | # MSBuild project files
25 | [*.{csproj,props,targets}]
26 | end_of_line = unset
27 |
28 | # Xml config files
29 | [*.{ruleset,config,nuspec,resx,runsettings,DotSettings}]
30 | end_of_line = unset
31 |
32 | [*{_AssemblyInfo.cs,.notsupported.cs}]
33 | generated_code = true
34 |
35 | # C# code style settings
36 | [*.{cs}]
37 | dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly
38 |
39 | # https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0
40 | dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member
41 | dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure
42 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | # ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
2 | version: 2
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "weekly" # Check for updates to GitHub Actions every week
8 | ignore:
9 | # I just want update action when major/minor version is updated. patch updates are too noisy.
10 | - dependency-name: '*'
11 | update-types:
12 | - version-update:semver-patch
13 |
--------------------------------------------------------------------------------
/.github/workflows/build-debug.yaml:
--------------------------------------------------------------------------------
1 | name: Build-Debug
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - "main"
8 | pull_request:
9 | branches:
10 | - "main"
11 |
12 | jobs:
13 | build-dotnet:
14 | permissions:
15 | contents: read
16 | runs-on: ubuntu-24.04
17 | timeout-minutes: 10
18 | steps:
19 | - uses: Cysharp/Actions/.github/actions/checkout@main
20 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main
21 | with:
22 | dotnet-version: |
23 | 6.0.x
24 | 8.0.x
25 | - run: dotnet build -c Debug
26 | - run: dotnet test -c Debug --no-build
27 |
--------------------------------------------------------------------------------
/.github/workflows/build-release.yaml:
--------------------------------------------------------------------------------
1 | name: Build-Release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | tag:
7 | description: "tag: git tag you want create. (sample 1.0.0)"
8 | required: true
9 | dry-run:
10 | description: "dry-run: true will never create relase/nuget."
11 | required: true
12 | default: false
13 | type: boolean
14 |
15 | jobs:
16 | build-dotnet:
17 | permissions:
18 | contents: read
19 | runs-on: ubuntu-24.04
20 | timeout-minutes: 10
21 | steps:
22 | - uses: Cysharp/Actions/.github/actions/checkout@main
23 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main
24 | with:
25 | dotnet-version: |
26 | 6.0.x
27 | 8.0.x
28 | # pack nuget
29 | - run: dotnet build -c Release -p:Version=${{ inputs.tag }}
30 | - run: dotnet test -c Release --no-build
31 | - run: dotnet pack -c Release --no-build -p:Version=${{ inputs.tag }} -o ./publish
32 | # Store artifacts.
33 | - uses: Cysharp/Actions/.github/actions/upload-artifact@main
34 | with:
35 | name: nuget
36 | path: ./publish/
37 | retention-days: 1
38 |
39 | # release
40 | create-release:
41 | needs: [build-dotnet]
42 | permissions:
43 | contents: write
44 | uses: Cysharp/Actions/.github/workflows/create-release.yaml@main
45 | with:
46 | commit-id: ''
47 | tag: ${{ inputs.tag }}
48 | dry-run: ${{ inputs.dry-run }}
49 | nuget-push: true
50 | release-upload: false
51 | secrets: inherit
52 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yaml:
--------------------------------------------------------------------------------
1 | name: "Close stale issues"
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: "0 0 * * *"
7 |
8 | jobs:
9 | stale:
10 | permissions:
11 | contents: read
12 | pull-requests: write
13 | issues: write
14 | uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
2 | [Bb]in/
3 | [Oo]bj/
4 |
5 | # mstest test results
6 | TestResults
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Rr]elease/
19 | *_i.c
20 | *_p.c
21 | *.ilk
22 | *.obj
23 | *.pch
24 | *.pdb
25 | *.pgc
26 | *.pgd
27 | *.rsp
28 | *.sbr
29 | *.tlb
30 | *.tli
31 | *.tlh
32 | *.tmp
33 | *.log
34 | *.vspscc
35 | *.vssscc
36 | .builds
37 |
38 | # Visual C++ cache files
39 | ipch/
40 | *.aps
41 | *.ncb
42 | *.opensdf
43 | *.sdf
44 |
45 | # Visual Studio profiler
46 | *.psess
47 | *.vsp
48 | *.vspx
49 |
50 | # Guidance Automation Toolkit
51 | *.gpState
52 |
53 | # ReSharper is a .NET coding add-in
54 | _ReSharper*
55 |
56 | # NCrunch
57 | *.ncrunch*
58 | .*crunch*.local.xml
59 |
60 | # Installshield output folder
61 | [Ee]xpress
62 |
63 | # DocProject is a documentation generator add-in
64 | DocProject/buildhelp/
65 | DocProject/Help/*.HxT
66 | DocProject/Help/*.HxC
67 | DocProject/Help/*.hhc
68 | DocProject/Help/*.hhk
69 | DocProject/Help/*.hhp
70 | DocProject/Help/Html2
71 | DocProject/Help/html
72 |
73 | # Click-Once directory
74 | publish
75 |
76 | # Publish Web Output
77 | *.Publish.xml
78 |
79 | # NuGet Packages Directory
80 | # packages # upm pacakge will use Packages
81 |
82 | # Windows Azure Build Output
83 | csx
84 | *.build.csdef
85 |
86 | # Windows Store app package directory
87 | AppPackages/
88 |
89 | # Others
90 | [Bb]in
91 | [Oo]bj
92 | sql
93 | TestResults
94 | [Tt]est[Rr]esult*
95 | *.Cache
96 | ClientBin
97 | [Ss]tyle[Cc]op.*
98 | ~$*
99 | *.dbmdl
100 | Generated_Code #added for RIA/Silverlight projects
101 |
102 | # Backup & report files from converting an old project file to a newer
103 | # Visual Studio version. Backup files are not needed, because we have git ;-)
104 | _UpgradeReport_Files/
105 | Backup*/
106 | UpgradeLog*.XML
107 | .vs/config/applicationhost.config
108 | .vs/restore.dg
109 |
110 | nuget/tools/*
111 | nuget/*.nupkg
112 | nuget/*.unitypackage
113 | .vs/
114 |
115 | # Jetbrains Rider
116 | .idea/
117 |
118 | __packages/
119 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | latest
4 | enable
5 | true
6 | $(NoWarn);CS1591
7 | true
8 | $(MSBuildThisFileDirectory)opensource.snk
9 |
10 |
11 | $(Version)
12 | Cysharp
13 | Cysharp
14 | © Cysharp, Inc.
15 | https://github.com/Cysharp/Utf8StringInterpolation
16 | $(PackageProjectUrl)
17 | git
18 | MIT
19 | Icon.png
20 | $(MSBuildThisFileDirectory)opensource.snk
21 |
22 |
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cysharp/Utf8StringInterpolation/d999aa19fa9a5dbdae23ec093a2d821a05e3b292/Icon.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Cysharp, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Utf8StringInterpolation
2 | [](https://github.com/Cysharp/Utf8StringInterpolation/actions) [](https://github.com/Cysharp/Utf8StringInterpolation/releases)
3 | [](https://nuget.org/packages/Utf8StringInterpolation)
4 |
5 | Successor of [ZString](https://github.com/Cysharp/ZString/); UTF8 based zero allocation high-peformance String Interpolation and StringBuilder.
6 |
7 | [C# 10.0 Improved Interpolated Strings](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md) gets extremely high performance in string generation by deconstructing format strings at compile time and executing the most optimal calls. However, this applies only to String (UTF16) and cannot be applied to the generation of UTF8 strings. `Utf8StringInterpolation` has achieved the generation of `byte[]` as UTF8 and direct writing to `IBufferWriter` with zero allocation and at peak performance, thanks to its **compiler support custom InterpolatedStringHandler** optimized for UTF8 writing, all while retaining the user-friendly syntax and compiler support of InterpolatedString. With the addition of **IUtf8SpanFormattable in .NET 8**, we have achieved optimized Utf8 writing for numerous value types.
8 |
9 | ```csharp
10 | using Utf8StringInterpolation;
11 |
12 | // Create UTF8 encoded string directly(without encoding).
13 | byte[] utf8 = Utf8String.Format($"Hello, {name}, Your id is {id}!");
14 |
15 | // write to IBufferWriter(for example ASP.NET HttpResponse.BodyWriter)
16 | var bufferWriter = new ArrayBufferWriter();
17 | Utf8String.Format(bufferWriter, $"Today is {DateTime.Now:yyyy-MM-dd}"); // support format
18 |
19 | // write to FileStream directly
20 | using var fs = File.OpenWrite("foo.txt");
21 | var pipeWriter = PipeWriter.Create(fs);
22 | Utf8String.Format(pipeWriter, $"Foo: {id,10} {name,-5}"); // support alignment
23 |
24 | // like a StringBuilder
25 | var writer = Utf8String.CreateWriter(bufferWriter);
26 | writer.Append("My Name...");
27 | writer.AppendFormat($"is...? {name}");
28 | writer.AppendLine();
29 | writer.Flush();
30 |
31 | // Join, Concat methods
32 | var seq = Enumerable.Range(1, 10);
33 | byte[] utf8seq = Utf8String.Join(", ", seq);
34 | ```
35 |
36 | Modern C# treats `ReadOnlySpan` as Utf8. Additionally, modern C# writes to the output using `IBufferWriter`. `Utf8StringInterpolation` provides a variety of utilities and writers optimized for use with them. In .NET 8, the new [IUtf8SpanFormattable](https://learn.microsoft.com/en-us/dotnet/api/system.iutf8spanformattable) is used to directly write values to UTF8.
37 |
38 | ## Getting Started
39 |
40 | This library is distributed via NuGet, supporting `.NET Standard 2.0`, `.NET Standard 2.1`, `.NET 6(.NET 7)` and `.NET 8` or above.
41 |
42 | PM> Install-Package [Utf8StringInterpolation](https://www.nuget.org/packages/Utf8StringInterpolation)
43 |
44 | ```csharp
45 | // `Format(ref Utf8StringWriter)` accepts string interpolation
46 | Utf8String.Format($"Hello, {name}, Your id is {id}!");
47 |
48 | // Interpolated string compiles like following.
49 | var writer = new Utf8StringWriter(literalLength: 20, formattedCount: 2);
50 | writer.AppendLiteral("Hello, ");
51 | writer.AppendFormatted(name);
52 | writer.AppendLiteral(", You id is ");
53 | writer.AppendFormatted(id);
54 | writer.AppendLiteral("!");
55 |
56 | // internal struct writer write value to utf8 directly without boxing.
57 | [InterpolatedStringHandler]
58 | public ref struct Utf8StringWriter where TBufferWriter : IBufferWriter
59 | {
60 | TBufferWriter bufferWriter; // when buffer is full, advance and get more buffer
61 | Span buffer; // current write buffer
62 |
63 | public void AppendLiteral(string value)
64 | {
65 | // encode string literal to Utf8 buffer directly
66 | var bytesWritten = Encoding.UTF8.GetBytes(value, buffer);
67 | buffer = buffer.Slice(bytesWritten);
68 | }
69 |
70 | public void AppendFormatted(T value, int alignment = 0, string? format = null)
71 | where T : IUtf8SpanFormattable
72 | {
73 | // write value to Utf8 buffer directly
74 | while (!value.TryFormat(buffer, out bytesWritten, format))
75 | {
76 | Grow();
77 | }
78 | buffer = buffer.Slice(bytesWritten);
79 | }
80 | }
81 | ```
82 |
83 | The actual Utf8StringWriter accepts various types, uses the Utf8Formatter in .NET 6 environments that do not support IUtf8SpanFormattable, and is designed with optimizations such as more efficient buffer usage.
84 |
85 | ## Utf8String methods
86 |
87 | Entry point is `Utf8StringInterpolation.Utf8String`. You can use static methods or create writer.
88 |
89 | When the argument is `ref Utf8StringWriter`, you can pass a string interpolation expression like `$"{...}"`.
90 |
91 | ### Utf8String.Format
92 |
93 | Format is a one-shot method. The Format that takes an `IBufferWriter` performs writing based on the bufferWriter and finally flushes (Advance). The Format that returns a `byte[]` writes using its internally pooled `ArrayBufferWriter` and generates a final `byte[]`. TryFormat writes to the specified `Span`, and if the length is insufficient, it returns false.
94 |
95 | ```csharp
96 | byte[] Format(ref Utf8StringWriter format)
97 | void Format(TBufferWriter bufferWriter, ref Utf8StringWriter format)
98 | where TBufferWriter : IBufferWriter
99 | bool TryFormat(Span destination, out int bytesWritten, ref Utf8StringWriter format)
100 | ```
101 |
102 | ### Utf8String.Concat, Join
103 |
104 | `Utf8String.Concat` and `Utf8String.Join` are similar to `String.Concat` and `String.Join`, but they write everything in UTF8. Like Format, there are two overloads: one that writes to `IBufferWriter` and another that writes to the internally pooled `ArrayBufferWriter` and then returns a `byte[]`.
105 |
106 | ```csharp
107 | // Concat overloads, return byte[] or receive IBufferWriter.
108 | byte[] Concat(params string?[] values)
109 | void Concat(TBufferWriter bufferWriter, params string?[] values)
110 | where TBufferWriter : IBufferWriter
111 | byte[] Concat(IEnumerable values)
112 | void Concat(TBufferWriter bufferWriter, IEnumerable values)
113 | where TBufferWriter : IBufferWriter
114 | ```
115 |
116 | ```csharp
117 | // Join overloads, return byte[] or receive IBufferWriter.
118 | byte[] Join(string separator, params string?[] values)
119 | void Join(TBufferWriter bufferWriter, string separator, params string?[] values)
120 | where TBufferWriter : IBufferWriter
121 | byte[] Join(string separator, IEnumerable values)
122 | void Join(TBufferWriter bufferWriter, string separator, IEnumerable values)
123 | where TBufferWriter : IBufferWriter
124 | ```
125 |
126 | ## Utf8String.CreateWriter
127 |
128 | `Utf8String.CreateWriter` allows you to obtain a `Utf8StringWriter` that can write continuously, similar to `StringBuilder`. Each `Append***` method can be used in a manner similar to `StringBuilder`. However, unlike `StringBuilder`, the buffer is managed by the provided BufferWriter, which is why it's named Writer. For performance reasons, the Append methods don't always flush (Advance) to the internal BufferWriter. By manually calling Flush, you ensure that Advance is invoked. Additionally, Dispose also calls Flush.
129 |
130 | ```csharp
131 | var writer = Utf8String.CreateWriter(bufferWriter);
132 |
133 | // call each append methods.
134 | writer.Append("foo");
135 | writer.AppendFormat($"bar {Guid.NewGuid}");
136 |
137 | // finally call Flush(or Dispose)
138 | writer.Flush();
139 | ```
140 |
141 | When writing larger messages, it's advisable to periodically call Flush. At those times, for instance, if using a `PipeWriter`, you can invoke `pipeWriter.FlushAsync()` to stream the write operations without holding onto a large buffer internally.
142 |
143 | `CreateWriter` has two overloads. One takes an `IBufferWriter`, and the other uses the internally pooled buffer.
144 |
145 | ```csharp
146 | Utf8StringWriter CreateWriter(TBufferWriter bufferWriter, IFormatProvider? formatProvider = null)
147 | where TBufferWriter : IBufferWriter
148 |
149 | Utf8StringBuffer CreateWriter(out Utf8StringWriter> stringWriter, IFormatProvider? formatProvider = null)
150 | ```
151 |
152 | `Utf8StringBuffer` is convenient because it uses an internally pooled buffer. Therefore, when you just want to obtain a `byte[]`, for example, there's no need to separately prepare or manage an `IBufferWriter`.
153 |
154 | ```csharp
155 | // buffer must Dispose after used(recommend to use using)
156 | using var buffer = Utf8String.CreateWriter(out var writer);
157 |
158 | // call each append methods.
159 | writer.Append("foo");
160 | writer.AppendFormat($"bar {Guid.NewGuid}");
161 |
162 | // finally call Flush(no need to call Dispose for writer)
163 | writer.Flush();
164 |
165 | // copy to written byte[]
166 | var bytes = buffer.ToArray();
167 |
168 | // or copy to other IBufferWriter, get ReadOnlySpan
169 | buffer.CopyTo(otherBufferWriter);
170 | var writtenData = buffer.WrittenSpan;
171 | ```
172 |
173 | ### Utf8StringWriter
174 |
175 | `AppendLiteral`, `AppendFormatted` is called from compiler generated code.
176 |
177 | ```csharp
178 | void AppendLiteral(string s)
179 | void AppendWhitespace(int count)
180 | void Append(string? s)
181 | void Append(char c)
182 | void Append(char c, int repeatCount)
183 | void AppendUtf8(scoped ReadOnlySpan utf8String)
184 | void AppendFormatted(scoped ReadOnlySpan utf8String)
185 | void AppendFormatted(scoped ReadOnlySpan s)
186 | void AppendFormatted(string value, int alignment = 0, string? format = null)
187 | void AppendFormatted(T value, int alignment = 0, string? format = null)
188 | void AppendFormat(ref Utf8StringWriter format) // extension method
189 | void AppendLine(ref Utf8StringWriter format) // extension method
190 | void AppendLine()
191 | void AppendLine(string s)
192 | void Flush()
193 | void Dispose() // call Flush and dereference buffer and bufferwriter
194 | ```
195 |
196 | ### Utf8StringBuffer
197 |
198 | `Utf8StringBuffer` can obtain from `Utf8String.CreateWriter` overload. Since it holds an internally pooled buffer, it's necessary to call `Dispose` to release the buffer once obtained.
199 |
200 | ```csharp
201 | int WrittenCount { get; }
202 | ReadOnlySpan WrittenSpan { get; }
203 | ReadOnlyMemory WrittenMemory { get; }
204 | byte[] ToArray()
205 | void CopyTo(TBufferWriter bufferWriter)
206 | where TBufferWriter : IBufferWriter
207 | Dispose()
208 | ```
209 |
210 | ## Format strings
211 |
212 | The formatting in string interpolation can use alignment and format just like regular .NET. In .NET 8, all formatting follows the standard format. For more details, please refer to the .NET documentation on [formatting-types](https://learn.microsoft.com/en-us/dotnet/standard/base-types/formatting-types).
213 |
214 | However, this is only the case for .NET 8 and above where `IUtf8SpanFormattable` is implemented. In .NET Standard 2.1, .NET 6 (and .NET 7), UTF8 writing of values is performed using [Utf8Formatter.TryFormat](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.text.utf8formatter.tryformat). This requires a specific format called [StandardFormat](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.standardformat), which might not be compatible with standard formats in some cases. The supported format strings are illustrated in the Remarks section of the TryFormat documentation. The types in focus are `bool, byte, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64`.
215 |
216 | Exceptionally, `DateTime`, `DateTimeOffset`, and `TimeSpan` can use regular format specifiers even in .NET Standard 2.1 and .NET 6 (and .NET 7). This special accommodation was made because `StandardFormat` only allows for four patterns, which was found to be too limiting.
217 |
218 | ```csharp
219 | // .NET 8 supports all numeric custom format string but .NET Standard 2.1, .NET 6(.NET 7) does not.
220 | Utf8String.Format($"Double value is {123.456789:.###}");
221 |
222 | // DateTime, DateTimeOffset, TimeSpan support custom format string on all target plaftorms.
223 | // https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
224 | Utf8String.Format($"Today is {DateTime.Now:yyyy-MM-dd}");
225 | ```
226 |
227 | Unity
228 | ---
229 | [Cysharp/ZLogger](https://github.com/Cysharp/ZLogger) is using Utf8StringInterpolation and supports Unity. See instruction for details.
230 |
231 | License
232 | ---
233 | This library is licensed under the MIT License.
234 |
--------------------------------------------------------------------------------
/Utf8StringInterpolation.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34112.27
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{77AECFE2-39F1-4377-90FD-3372F7C16A42}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utf8StringInterpolation", "src\Utf8StringInterpolation\Utf8StringInterpolation.csproj", "{92924227-0EE6-4F49-9418-25128AFBF493}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{6576F04F-290C-411A-962F-E5C0B82E9E07}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "sandbox\ConsoleApp\ConsoleApp.csproj", "{CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net6ConsoleApp", "sandbox\Net6ConsoleApp\Net6ConsoleApp.csproj", "{A41BC3B6-A363-4AFD-81BC-97AE4E551BD1}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AC23EACD-3970-4DE6-BB31-13612E3A1886}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utf8StringInterpolation.Tests", "tests\Utf8StringInterpolation.Tests\Utf8StringInterpolation.Tests.csproj", "{2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Release|Any CPU = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 | {92924227-0EE6-4F49-9418-25128AFBF493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {92924227-0EE6-4F49-9418-25128AFBF493}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {92924227-0EE6-4F49-9418-25128AFBF493}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {92924227-0EE6-4F49-9418-25128AFBF493}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {A41BC3B6-A363-4AFD-81BC-97AE4E551BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {A41BC3B6-A363-4AFD-81BC-97AE4E551BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {A41BC3B6-A363-4AFD-81BC-97AE4E551BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {A41BC3B6-A363-4AFD-81BC-97AE4E551BD1}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(NestedProjects) = preSolution
47 | {92924227-0EE6-4F49-9418-25128AFBF493} = {77AECFE2-39F1-4377-90FD-3372F7C16A42}
48 | {CC28B62D-A7C9-4E80-BD99-1770C8FE2DF4} = {6576F04F-290C-411A-962F-E5C0B82E9E07}
49 | {A41BC3B6-A363-4AFD-81BC-97AE4E551BD1} = {6576F04F-290C-411A-962F-E5C0B82E9E07}
50 | {2FDBCD9E-9E23-47AD-83E7-50EF1A26F5CE} = {AC23EACD-3970-4DE6-BB31-13612E3A1886}
51 | EndGlobalSection
52 | GlobalSection(ExtensibilityGlobals) = postSolution
53 | SolutionGuid = {AA1E5CE4-5F0A-4706-ABCD-A463B61B3295}
54 | EndGlobalSection
55 | EndGlobal
56 |
--------------------------------------------------------------------------------
/docs/images.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cysharp/Utf8StringInterpolation/d999aa19fa9a5dbdae23ec093a2d821a05e3b292/docs/images.pptx
--------------------------------------------------------------------------------
/opensource.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cysharp/Utf8StringInterpolation/d999aa19fa9a5dbdae23ec093a2d821a05e3b292/opensource.snk
--------------------------------------------------------------------------------
/sandbox/ConsoleApp/ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 | false
10 | $(NoWarn);CS0169;CS0649
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/sandbox/ConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Buffers.Text;
4 | using System.Reflection.PortableExecutable;
5 | using System.Runtime.InteropServices.Marshalling;
6 | using System.Text;
7 | using System.Xml.Linq;
8 | using Utf8StringInterpolation;
9 |
10 | //Console.WriteLine($"Foo{100,5}Bar");
11 |
12 | int? foo = 23;
13 |
14 | // var a = foo.GetType();
15 |
16 | var test = Utf8String.Format($"foo{foo}");
17 |
18 | //var actual = Utf8String.Format($"Prime numbers less than 10: {2,01}, {3,02}, {5,3:D}, {7,4: X}");
19 | //actual.Should().Be(expected);
20 |
21 |
22 |
23 |
24 | // true.TryFormat
25 |
26 | // var bytes = Cysharp.Text.Utf8String.Join(" - ", new[] { 1, 10, 100 });
27 |
28 |
29 |
30 | //var bytes = Utf8String.Format($"foo{DateTime.Now:yy,あ:mm,:dd}Bar");
31 |
32 | //Console.WriteLine(Encoding.UTF8.GetString(bytes));
33 |
34 | //var i = 100;
35 | //Utf8String.Format(writer, $"Today {DateTime.Now:O}.");
36 |
37 |
38 | // check: DateTimeOffset TryFormat
39 | // check: TimeSpan TryFormat
40 | // ZString.CreateUtf8StringBuilder().
41 |
42 |
43 |
44 | //DateTime.Now.TryFormat(writer.GetSpan(), out var bytesWritten, "yyyy:mm:ss");
45 |
46 | //writer.Advance(bytesWritten);
47 |
48 | //var builder = Utf8String.CreateBuilder(writer);
49 |
50 | //builder.AppendLiteral("Hello, ");
51 | //builder.AppendFormat($"Today is {DateTime.Now}.");
52 |
53 | //builder.Flush();
54 |
55 | //var span = writer.WrittenSpan;
56 | //Console.WriteLine(Encoding.UTF8.GetString(span));
57 |
58 | enum Fruit
59 | {
60 | Apple, Grape, Orange
61 | }
--------------------------------------------------------------------------------
/sandbox/ConsoleApp/ReadMeSample.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Buffers;
4 | using System.Collections.Generic;
5 | using System.IO.Pipelines;
6 | using System.Linq;
7 | using System.Runtime.CompilerServices;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using System.Xml.Linq;
11 | using Utf8StringInterpolation;
12 |
13 | namespace ConsoleApp;
14 |
15 | internal class ReadMeSample
16 | {
17 |
18 | void Sample(int id, string name)
19 | {
20 | // Create UTF8 encoded string directly(without encoding).
21 | byte[] utf8 = Utf8String.Format($"Hello, {name}, You id is {id}!");
22 |
23 | // write to IBufferWriter(for example ASP.NET HttpResponse.BodyWriter)
24 | var bufferWriter = new ArrayBufferWriter();
25 | Utf8String.Format(bufferWriter, $"Today is {DateTime.Now:yyyy-MM-dd}"); // support format
26 |
27 | // write to FileStream directly
28 | using var fs = File.OpenWrite("foo.txt");
29 | var pipeWriter = PipeWriter.Create(fs);
30 | Utf8String.Format(pipeWriter, $"Foo: {id,10} {name,-5}"); // support alignment
31 |
32 | // like StringBuilder
33 | var writer = Utf8String.CreateWriter(bufferWriter);
34 | writer.Append("My Name...");
35 | writer.AppendFormat($"is...? {name}");
36 | writer.AppendLine();
37 | writer.Flush();
38 |
39 | // Join, Concat methods
40 | var seq = Enumerable.Range(1, 10);
41 | byte[] utf8seq = Utf8String.Join(", ", seq);
42 |
43 |
44 |
45 | }
46 |
47 | void GettingStarted(string name, int id)
48 | {
49 |
50 |
51 | // `Format(ref Utf8StringWriter)` accepts string interpolation
52 | Utf8String.Format($"Hello, {name}, Your id is {id}!");
53 |
54 |
55 |
56 | // Interpolated string compiles like following.
57 | var writer = new Utf8StringWriter>(literalLength: 20, formattedCount: 2);
58 | writer.AppendLiteral("Hello, ");
59 | writer.AppendFormatted(name);
60 | writer.AppendLiteral(", You id is ");
61 | writer.AppendFormatted(id);
62 | writer.AppendLiteral("!");
63 |
64 |
65 | }
66 |
67 | class Dummy
68 | {
69 | #pragma warning disable CS0282
70 |
71 | [InterpolatedStringHandler]
72 | public ref partial struct Utf8StringWriter
73 | where TBufferWriter : IBufferWriter
74 | {
75 | TBufferWriter bufferWriter; // when buffer is full, advance and get more buffer
76 | Span buffer; // current write buffer
77 |
78 | public void AppendFormatted(T value, int alignment = 0, string? format = null)
79 | where T : IUtf8SpanFormattable
80 | {
81 | // write value to Utf8 buffer directly
82 | var bytesWritten = 0;
83 | while (!value.TryFormat(buffer, out bytesWritten, format, provider))
84 | {
85 | Grow();
86 | }
87 | buffer = buffer.Slice(bytesWritten);
88 | }
89 | }
90 |
91 |
92 | public ref partial struct Utf8StringWriter where TBufferWriter : IBufferWriter
93 | {
94 | IFormatProvider provider;
95 | void Grow() { }
96 |
97 |
98 | public void AppendLiteral(string value)
99 | {
100 | // encode string literal to Utf8 buffer directly
101 | var bytesWritten = Encoding.UTF8.GetBytes(value, buffer);
102 | buffer = buffer.Slice(bytesWritten);
103 | }
104 |
105 | }
106 | }
107 |
108 |
109 |
110 | void WriterSample()
111 | {
112 | var bufferWriter = new ArrayBufferWriter();
113 |
114 | var writer = Utf8String.CreateWriter(bufferWriter);
115 |
116 | // call each append methods.
117 | writer.Append("foo");
118 | writer.AppendFormat($"bar {Guid.NewGuid()}");
119 |
120 | // finally call Flush(or Dispose)
121 | writer.Flush();
122 |
123 | // get written utf8
124 | var writtenData = bufferWriter.WrittenSpan;
125 | }
126 |
127 | void WriterSample2(IBufferWriter otherBufferWriter)
128 | {
129 | // buffer must Dispose after used(recommend to use using)
130 | using var buffer = Utf8String.CreateWriter(out var writer);
131 |
132 | // call each append methods.
133 | writer.Append("foo");
134 | writer.AppendFormat($"bar {Guid.NewGuid()}");
135 |
136 | // finally call Flush(or Dispose)
137 | writer.Flush();
138 |
139 | // copy to written data or get byte[] / ReadOnlySpan
140 | buffer.CopyTo(otherBufferWriter);
141 | var bytes = buffer.ToArray();
142 | var writtenData = buffer.WrittenSpan;
143 | }
144 |
145 | void Formatting()
146 | {
147 | // .NET 8 supports all numeric custom format string but .NET Standard 2.1, .NET 6(.NET 7) does not.
148 | Utf8String.Format($"Double value is {123.456789:.###}");
149 |
150 | // DateTime, DateTimeOffset, TimeSpan support custom format string on all target plaftorms.
151 | // https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
152 | Utf8String.Format($"Today is {DateTime.Now:yyyy-MM-dd}");
153 |
154 |
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/sandbox/Net6ConsoleApp/Net6ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sandbox/Net6ConsoleApp/Program.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | using System.Buffers;
5 | using System.Text;
6 | using Utf8StringInterpolation;
7 |
8 | var a = Utf8String.Format($"Double value is {123.456789:.###}");
9 | var b = Utf8String.Format($"Today is {DateTime.Now:yyyy-MM-dd}");
10 | Console.WriteLine(Encoding.UTF8.GetString(a));
11 | Console.WriteLine(Encoding.UTF8.GetString(b));
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/ArrayBufferWriter.cs:
--------------------------------------------------------------------------------
1 | #if !NET6_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER
2 | using System;
3 | using System.Diagnostics;
4 |
5 | namespace System.Buffers
6 | {
7 | ///
8 | /// Represents a heap-based, array-backed output sink into which data can be written.
9 | ///
10 | ///
11 | public sealed class ArrayBufferWriter : IBufferWriter
12 | {
13 | // Copy of Array.MaxLength.
14 | // Used by projects targeting .NET Framework.
15 | private const int ArrayMaxLength = 0x7FFFFFC7;
16 |
17 | private const int DefaultInitialBufferSize = 256;
18 |
19 | private T[] _buffer;
20 | private int _index;
21 |
22 |
23 | ///
24 | /// Creates an instance of an , in which data can be written to,
25 | /// with the default initial capacity.
26 | ///
27 | public ArrayBufferWriter()
28 | {
29 | _buffer = Array.Empty();
30 | _index = 0;
31 | }
32 |
33 | ///
34 | /// Creates an instance of an , in which data can be written to,
35 | /// with an initial capacity specified.
36 | ///
37 | /// The minimum capacity with which to initialize the underlying buffer.
38 | ///
39 | /// Thrown when is not positive (i.e. less than or equal to 0).
40 | ///
41 | public ArrayBufferWriter(int initialCapacity)
42 | {
43 | if (initialCapacity <= 0)
44 | throw new ArgumentException(null, nameof(initialCapacity));
45 |
46 | _buffer = new T[initialCapacity];
47 | _index = 0;
48 | }
49 |
50 | ///
51 | /// Returns the data written to the underlying buffer so far, as a .
52 | ///
53 | public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _index);
54 |
55 | ///
56 | /// Returns the data written to the underlying buffer so far, as a .
57 | ///
58 | public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _index);
59 |
60 | ///
61 | /// Returns the amount of data written to the underlying buffer so far.
62 | ///
63 | public int WrittenCount => _index;
64 |
65 | ///
66 | /// Returns the total amount of space within the underlying buffer.
67 | ///
68 | public int Capacity => _buffer.Length;
69 |
70 | ///
71 | /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
72 | ///
73 | public int FreeCapacity => _buffer.Length - _index;
74 |
75 | ///
76 | /// Clears the data written to the underlying buffer.
77 | ///
78 | ///
79 | ///
80 | /// You must reset or clear the before trying to re-use it.
81 | ///
82 | ///
83 | /// The method is faster since it only sets to zero the writer's index
84 | /// while the method additionally zeroes the content of the underlying buffer.
85 | ///
86 | ///
87 | ///
88 | public void Clear()
89 | {
90 | Debug.Assert(_buffer.Length >= _index);
91 | _buffer.AsSpan(0, _index).Clear();
92 | _index = 0;
93 | }
94 |
95 | ///
96 | /// Resets the data written to the underlying buffer without zeroing its content.
97 | ///
98 | ///
99 | ///
100 | /// You must reset or clear the before trying to re-use it.
101 | ///
102 | ///
103 | /// If you reset the writer using the method, the underlying buffer will not be cleared.
104 | ///
105 | ///
106 | ///
107 | public void ResetWrittenCount() => _index = 0;
108 |
109 | ///
110 | /// Notifies that amount of data was written to the output /
111 | ///
112 | ///
113 | /// Thrown when is negative.
114 | ///
115 | ///
116 | /// Thrown when attempting to advance past the end of the underlying buffer.
117 | ///
118 | ///
119 | /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
120 | ///
121 | public void Advance(int count)
122 | {
123 | if (count < 0)
124 | throw new ArgumentException(null, nameof(count));
125 |
126 | if (_index > _buffer.Length - count)
127 | ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length);
128 |
129 | _index += count;
130 | }
131 |
132 | ///
133 | /// Returns a to write to that is at least the requested length (specified by ).
134 | /// If no is provided (or it's equal to 0
), some non-empty buffer is returned.
135 | ///
136 | ///
137 | /// Thrown when is negative.
138 | ///
139 | ///
140 | ///
141 | /// This will never return an empty .
142 | ///
143 | ///
144 | /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
145 | ///
146 | ///
147 | /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
148 | ///
149 | ///
150 | /// If you reset the writer using the method, this method may return a non-cleared .
151 | ///
152 | ///
153 | /// If you clear the writer using the method, this method will return a with its content zeroed.
154 | ///
155 | ///
156 | public Memory GetMemory(int sizeHint = 0)
157 | {
158 | CheckAndResizeBuffer(sizeHint);
159 | Debug.Assert(_buffer.Length > _index);
160 | return _buffer.AsMemory(_index);
161 | }
162 |
163 | ///
164 | /// Returns a to write to that is at least the requested length (specified by ).
165 | /// If no is provided (or it's equal to 0
), some non-empty buffer is returned.
166 | ///
167 | ///
168 | /// Thrown when is negative.
169 | ///
170 | ///
171 | ///
172 | /// This will never return an empty .
173 | ///
174 | ///
175 | /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
176 | ///
177 | ///
178 | /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
179 | ///
180 | ///
181 | /// If you reset the writer using the method, this method may return a non-cleared .
182 | ///
183 | ///
184 | /// If you clear the writer using the method, this method will return a with its content zeroed.
185 | ///
186 | ///
187 | public Span GetSpan(int sizeHint = 0)
188 | {
189 | CheckAndResizeBuffer(sizeHint);
190 | Debug.Assert(_buffer.Length > _index);
191 | return _buffer.AsSpan(_index);
192 | }
193 |
194 | private void CheckAndResizeBuffer(int sizeHint)
195 | {
196 | if (sizeHint < 0)
197 | throw new ArgumentException(nameof(sizeHint));
198 |
199 | if (sizeHint == 0)
200 | {
201 | sizeHint = 1;
202 | }
203 |
204 | if (sizeHint > FreeCapacity)
205 | {
206 | int currentLength = _buffer.Length;
207 |
208 | // Attempt to grow by the larger of the sizeHint and double the current size.
209 | int growBy = Math.Max(sizeHint, currentLength);
210 |
211 | if (currentLength == 0)
212 | {
213 | growBy = Math.Max(growBy, DefaultInitialBufferSize);
214 | }
215 |
216 | int newSize = currentLength + growBy;
217 |
218 | if ((uint)newSize > int.MaxValue)
219 | {
220 | // Attempt to grow to ArrayMaxLength.
221 | uint needed = (uint)(currentLength - FreeCapacity + sizeHint);
222 | Debug.Assert(needed > currentLength);
223 |
224 | if (needed > ArrayMaxLength)
225 | {
226 | ThrowOutOfMemoryException(needed);
227 | }
228 |
229 | newSize = ArrayMaxLength;
230 | }
231 |
232 | Array.Resize(ref _buffer, newSize);
233 | }
234 |
235 | Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
236 | }
237 |
238 | private static void ThrowInvalidOperationException_AdvancedTooFar(int capacity)
239 | {
240 | throw new InvalidOperationException();
241 | }
242 |
243 | private static void ThrowOutOfMemoryException(uint capacity)
244 | {
245 | throw new OutOfMemoryException();
246 | }
247 | }
248 | }
249 | #endif
250 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Internal/ArrayBufferWriterPool.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Collections.Concurrent;
3 |
4 | namespace Utf8StringInterpolation.Internal;
5 |
6 | internal static class ArrayBufferWriterPool
7 | {
8 | [ThreadStatic]
9 | static ArrayBufferWriter? bufferWriter;
10 |
11 | static ConcurrentQueue> cache = new ConcurrentQueue>();
12 |
13 | public static ArrayBufferWriter GetThreadStaticInstance()
14 | {
15 | var writer = bufferWriter;
16 | if (writer == null)
17 | {
18 | writer = bufferWriter = new ArrayBufferWriter();
19 | }
20 | #if NET8_0_OR_GREATER
21 | writer.ResetWrittenCount();
22 | #else
23 | writer.Clear();
24 | #endif
25 | return writer;
26 | }
27 |
28 | public static ArrayBufferWriter Rent()
29 | {
30 | if (cache.TryDequeue(out var writer))
31 | {
32 | return writer;
33 | }
34 | return new ArrayBufferWriter(256);
35 | }
36 |
37 | public static void Return(ArrayBufferWriter writer)
38 | {
39 | #if NET8_0_OR_GREATER
40 | writer.ResetWrittenCount();
41 | #else
42 | writer.Clear();
43 | #endif
44 | cache.Enqueue(writer);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Shims.NetStandard2.cs:
--------------------------------------------------------------------------------
1 | #if NETSTANDARD2_0
2 |
3 | using System;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 |
7 | namespace Utf8StringInterpolation
8 | {
9 | internal static partial class Shims
10 | {
11 | public static string GetString(this Encoding encoding, scoped ReadOnlySpan bytes)
12 | {
13 | if (bytes.IsEmpty) return string.Empty;
14 |
15 | unsafe
16 | {
17 | fixed (byte* pB = &MemoryMarshal.GetReference(bytes))
18 | {
19 | return encoding.GetString(pB, bytes.Length);
20 | }
21 | }
22 | }
23 |
24 | public static int GetByteCount(this Encoding encoding, scoped ReadOnlySpan chars)
25 | {
26 | unsafe
27 | {
28 | fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
29 | {
30 | return encoding.GetByteCount(charsPtr, chars.Length);
31 | }
32 | }
33 | }
34 |
35 | public static int GetBytes(this Encoding encoding, scoped ReadOnlySpan chars, scoped Span bytes)
36 | {
37 | unsafe
38 | {
39 | fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
40 | fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
41 | {
42 | return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length);
43 | }
44 | }
45 | }
46 |
47 | private static bool TryFormat(this DateTime value, scoped Span destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
48 | {
49 | string s = value.ToString(format, formatProvider);
50 | if (s.Length > destination.Length)
51 | {
52 | charsWritten = 0;
53 | return false;
54 | }
55 |
56 | s.AsSpan().CopyTo(destination);
57 | charsWritten = s.Length;
58 | return true;
59 | }
60 |
61 | private static bool TryFormat(this DateTimeOffset value, scoped Span destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
62 | {
63 | string s = value.ToString(format, formatProvider);
64 | if (s.Length > destination.Length)
65 | {
66 | charsWritten = 0;
67 | return false;
68 | }
69 |
70 | s.AsSpan().CopyTo(destination);
71 | charsWritten = s.Length;
72 | return true;
73 | }
74 |
75 | private static bool TryFormat(this TimeSpan value, scoped Span destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
76 | {
77 | string s = value.ToString(format, formatProvider);
78 | if (s.Length > destination.Length)
79 | {
80 | charsWritten = 0;
81 | return false;
82 | }
83 |
84 | s.AsSpan().CopyTo(destination);
85 | charsWritten = s.Length;
86 | return true;
87 | }
88 | }
89 | }
90 |
91 | #endif
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Shims.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CA2014 // Do not use stackalloc in loops
2 |
3 | using System.Buffers.Text;
4 | using System.Text;
5 |
6 | namespace Utf8StringInterpolation
7 | {
8 | internal static partial class Shims
9 | {
10 | public static bool TryFormat(this char value, Span utf8Destination, out int bytesWritten, string? format, IFormatProvider? formatProvider)
11 | {
12 | #if NET6_0_OR_GREATER
13 | return new Rune(value).TryEncodeToUtf8(utf8Destination, out bytesWritten);
14 | #else
15 | Span xs = stackalloc char[1];
16 | xs[0] = value;
17 |
18 | var count = Encoding.UTF8.GetByteCount(xs);
19 | if (utf8Destination.Length < count)
20 | {
21 | bytesWritten = 0;
22 | return false;
23 | }
24 | else
25 | {
26 | bytesWritten = Encoding.UTF8.GetBytes(xs, utf8Destination);
27 | return true;
28 | }
29 | #endif
30 | }
31 |
32 | #if !NET8_0_OR_GREATER
33 | public static bool TryFormat(this DateTime value, Span utf8Destination, out int bytesWritten, string? format, IFormatProvider? formatProvider)
34 | {
35 | Span charDest = stackalloc char[256];
36 | var charWritten = 0;
37 | while (!value.TryFormat(charDest, out charWritten, format, formatProvider))
38 | {
39 | if (charDest.Length < 512)
40 | {
41 | charDest = stackalloc char[charDest.Length * 2];
42 | }
43 | else
44 | {
45 | charDest = new char[charDest.Length * 2]; // too large
46 | }
47 | }
48 |
49 | var slice = charDest.Slice(0, charWritten);
50 | var count = Encoding.UTF8.GetByteCount(slice);
51 | if (utf8Destination.Length < count)
52 | {
53 | bytesWritten = 0;
54 | return false;
55 | }
56 |
57 | bytesWritten = Encoding.UTF8.GetBytes(slice, utf8Destination);
58 | return true;
59 | }
60 |
61 | public static bool TryFormat(this DateTimeOffset value, Span utf8Destination, out int bytesWritten, string? format, IFormatProvider? formatProvider)
62 | {
63 | Span charDest = stackalloc char[256];
64 | var charWritten = 0;
65 | while (!value.TryFormat(charDest, out charWritten, format, formatProvider))
66 | {
67 | if (charDest.Length < 512)
68 | {
69 | charDest = stackalloc char[charDest.Length * 2];
70 | }
71 | else
72 | {
73 | charDest = new char[charDest.Length * 2]; // too large
74 | }
75 | }
76 |
77 | var slice = charDest.Slice(0, charWritten);
78 | var count = Encoding.UTF8.GetByteCount(slice);
79 | if (utf8Destination.Length < count)
80 | {
81 | bytesWritten = 0;
82 | return false;
83 | }
84 |
85 | bytesWritten = Encoding.UTF8.GetBytes(slice, utf8Destination);
86 | return true;
87 | }
88 |
89 | public static bool TryFormat(this TimeSpan value, Span utf8Destination, out int bytesWritten, string? format, IFormatProvider? formatProvider)
90 | {
91 | Span charDest = stackalloc char[256];
92 | var charWritten = 0;
93 | while (!value.TryFormat(charDest, out charWritten, format, formatProvider))
94 | {
95 | if (charDest.Length < 512)
96 | {
97 | charDest = stackalloc char[charDest.Length * 2];
98 | }
99 | else
100 | {
101 | charDest = new char[charDest.Length * 2]; // too large
102 | }
103 | }
104 |
105 | var slice = charDest.Slice(0, charWritten);
106 | var count = Encoding.UTF8.GetByteCount(slice);
107 | if (utf8Destination.Length < count)
108 | {
109 | bytesWritten = 0;
110 | return false;
111 | }
112 |
113 | bytesWritten = Encoding.UTF8.GetBytes(slice, utf8Destination);
114 | return true;
115 | }
116 | #endif
117 | }
118 | }
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8String.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Runtime.CompilerServices;
3 | using System.Text;
4 | using Utf8StringInterpolation.Internal;
5 |
6 | namespace Utf8StringInterpolation;
7 |
8 | public static class Utf8String
9 | {
10 | // Format API
11 |
12 | public static byte[] Format(ref Utf8StringWriter> format)
13 | {
14 | format.Flush();
15 | var writer = format.GetBufferWriter();
16 | return writer.WrittenSpan.ToArray();
17 | }
18 |
19 | public static void Format(
20 | TBufferWriter bufferWriter,
21 | [InterpolatedStringHandlerArgument("bufferWriter")] ref Utf8StringWriter format)
22 | where TBufferWriter : IBufferWriter
23 | {
24 | format.Flush();
25 | }
26 |
27 | public static bool TryFormat(
28 | Span destination,
29 | out int bytesWritten,
30 | [InterpolatedStringHandlerArgument("destination")] ref Utf8StringWriter> format)
31 | {
32 | var written = format.GetCurrentWritten();
33 | format.Flush();
34 | if (destination.Length != format.GetAllocatedDestinationSize())
35 | {
36 | bytesWritten = 0;
37 | return false;
38 | }
39 | bytesWritten = written;
40 | return true;
41 | }
42 |
43 | // Writer API
44 |
45 | public static Utf8StringWriter CreateWriter(TBufferWriter bufferWriter, IFormatProvider? formatProvider = null)
46 | where TBufferWriter : IBufferWriter
47 | {
48 | return new Utf8StringWriter(0, 0, bufferWriter, formatProvider);
49 | }
50 |
51 | public static Utf8StringBuffer CreateWriter(out Utf8StringWriter> stringWriter, IFormatProvider? formatProvider = null)
52 | {
53 | var writer = ArrayBufferWriterPool.Rent();
54 | stringWriter = new Utf8StringWriter>(0, 0, writer, formatProvider);
55 | return new Utf8StringBuffer(writer);
56 | }
57 |
58 | // Concat, byte[], TBufferWriter
59 |
60 | public static byte[] Concat(params string?[] values)
61 | {
62 | using var buffer = CreateWriter(out var builder);
63 | foreach (var item in values)
64 | {
65 | if (item == null) continue;
66 | builder.AppendLiteral(item);
67 | }
68 | builder.Flush();
69 | return buffer.ToArray();
70 | }
71 |
72 | public static void Concat(TBufferWriter bufferWriter, params string?[] values)
73 | where TBufferWriter : IBufferWriter
74 | {
75 | using var builder = CreateWriter(bufferWriter); // Dispose calls Flush
76 | foreach (var item in values)
77 | {
78 | if (item == null) continue;
79 | builder.AppendLiteral(item);
80 | }
81 | }
82 |
83 | public static byte[] Concat(IEnumerable values)
84 | {
85 | using var buffer = CreateWriter(out var builder);
86 | foreach (var item in values)
87 | {
88 | builder.AppendFormatted(item);
89 | }
90 | builder.Flush();
91 | return buffer.ToArray();
92 | }
93 |
94 | public static void Concat(TBufferWriter bufferWriter, IEnumerable values)
95 | where TBufferWriter : IBufferWriter
96 | {
97 | using var builder = CreateWriter(bufferWriter); // Dispose calls Flush
98 | foreach (var item in values)
99 | {
100 | builder.AppendFormatted(item);
101 | }
102 | }
103 |
104 | // Join, byte[], TBufferWriter
105 |
106 | public static byte[] Join(string separator, params string?[] values)
107 | {
108 | using var buffer = CreateWriter(out var builder);
109 |
110 | var separatorSize = Encoding.UTF8.GetByteCount(separator);
111 | Span utf8Separator = stackalloc byte[separatorSize];
112 | Encoding.UTF8.GetBytes(separator.AsSpan(), utf8Separator);
113 |
114 | var first = true;
115 | foreach (var item in values)
116 | {
117 | if (first)
118 | {
119 | first = false;
120 | }
121 | else
122 | {
123 | builder.AppendUtf8(utf8Separator);
124 | }
125 | builder.Append(item);
126 | }
127 |
128 | builder.Flush();
129 | return buffer.ToArray();
130 | }
131 |
132 | public static void Join(TBufferWriter bufferWriter, string separator, params string?[] values)
133 | where TBufferWriter : IBufferWriter
134 | {
135 | using var builder = CreateWriter(bufferWriter); // Dispose calls Flush
136 |
137 | var separatorSize = Encoding.UTF8.GetByteCount(separator);
138 | Span utf8Separator = stackalloc byte[separatorSize];
139 | Encoding.UTF8.GetBytes(separator.AsSpan(), utf8Separator);
140 |
141 | var first = true;
142 | foreach (var item in values)
143 | {
144 | if (first)
145 | {
146 | first = false;
147 | }
148 | else
149 | {
150 | builder.AppendUtf8(utf8Separator);
151 | }
152 | builder.Append(item);
153 | }
154 | }
155 |
156 | public static byte[] Join(string separator, IEnumerable values)
157 | {
158 | using var buffer = CreateWriter(out var builder);
159 |
160 | var separatorSize = Encoding.UTF8.GetByteCount(separator);
161 | Span utf8Separator = stackalloc byte[separatorSize];
162 | Encoding.UTF8.GetBytes(separator.AsSpan(), utf8Separator);
163 |
164 | var first = true;
165 | foreach (var item in values)
166 | {
167 | if (first)
168 | {
169 | first = false;
170 | }
171 | else
172 | {
173 | builder.AppendUtf8(utf8Separator);
174 | }
175 | builder.AppendFormatted(item);
176 | }
177 |
178 | builder.Flush();
179 | return buffer.ToArray();
180 | }
181 |
182 | public static void Join(TBufferWriter bufferWriter, string separator, IEnumerable values)
183 | where TBufferWriter : IBufferWriter
184 | {
185 | using var builder = CreateWriter(bufferWriter); // Dispose calls Flush
186 |
187 | var separatorSize = Encoding.UTF8.GetByteCount(separator);
188 | Span utf8Separator = stackalloc byte[separatorSize];
189 | Encoding.UTF8.GetBytes(separator.AsSpan(), utf8Separator);
190 |
191 | var first = true;
192 | foreach (var item in values)
193 | {
194 | if (first)
195 | {
196 | first = false;
197 | }
198 | else
199 | {
200 | builder.AppendUtf8(utf8Separator);
201 | }
202 | builder.AppendFormatted(item);
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8StringBuffer.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Text;
3 | using Utf8StringInterpolation.Internal;
4 |
5 | namespace Utf8StringInterpolation;
6 |
7 | public struct Utf8StringBuffer : IDisposable
8 | {
9 | ArrayBufferWriter innerBuffer;
10 |
11 | internal Utf8StringBuffer(ArrayBufferWriter bufferWriter)
12 | {
13 | innerBuffer = bufferWriter;
14 | }
15 |
16 | public int WrittenCount => innerBuffer.WrittenCount;
17 | public ReadOnlySpan WrittenSpan => innerBuffer.WrittenSpan;
18 | public ReadOnlyMemory WrittenMemory => innerBuffer.WrittenMemory;
19 |
20 | public byte[] ToArray()
21 | {
22 | return innerBuffer.WrittenSpan.ToArray();
23 | }
24 |
25 | public void CopyTo(TBufferWriter bufferWriter)
26 | where TBufferWriter : IBufferWriter
27 | {
28 | var written = innerBuffer.WrittenSpan;
29 | var dest = bufferWriter.GetSpan(written.Length);
30 | written.CopyTo(dest);
31 | bufferWriter.Advance(written.Length);
32 | }
33 |
34 | public override string ToString()
35 | {
36 | return Encoding.UTF8.GetString(WrittenSpan);
37 | }
38 |
39 | public void Dispose()
40 | {
41 | if (innerBuffer != null)
42 | {
43 | ArrayBufferWriterPool.Return(innerBuffer);
44 | }
45 | innerBuffer = null!;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8StringInterpolation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1;net6.0;net8.0
5 | enable
6 | 11
7 | enable
8 | true
9 | $(NoWarn);CS1591;CA2255
10 | string
11 | Successor of ZString; UTF8 based zero allocation high-peformance String Interpolation and StringBuilder.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | TextTemplatingFileGenerator
30 | Utf8StringWriter.AppendFormatted.cs
31 |
32 |
33 | True
34 | True
35 | Utf8StringWriter.AppendFormatted.tt
36 |
37 |
38 |
39 |
40 |
41 | all
42 | runtime; build; native; contentfiles; analyzers; buildtransitive
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8StringWriter.AppendFormatted.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Buffers.Text;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace Utf8StringInterpolation;
6 |
7 | public ref partial struct Utf8StringWriter
8 | {
9 | #if true
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public void AppendFormatted(bool? value, int alignment = 0, string? format = null)
12 | {
13 | if (!value.HasValue)
14 | {
15 | if (alignment != 0)
16 | {
17 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
18 | }
19 | return;
20 | }
21 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
22 | }
23 |
24 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
25 | public void AppendFormatted(bool value, int alignment = 0, string? format = null)
26 | {
27 | if (alignment == 0)
28 | {
29 | int bytesWritten;
30 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
31 | {
32 | GrowCore(0);
33 | }
34 | destination = destination.Slice(bytesWritten);
35 | currentWritten += bytesWritten;
36 | return;
37 | }
38 |
39 | AppendFormattedAlignment(value, alignment, format);
40 | }
41 |
42 | void AppendFormattedAlignment(bool value, int alignment, string? format)
43 | {
44 | int bytesWritten;
45 |
46 | // add left whitespace
47 | if (alignment > 0)
48 | {
49 | Span buffer = stackalloc byte[32];
50 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
51 | {
52 | if (buffer.Length < 512)
53 | {
54 | #pragma warning disable CA2014 // Do not use stackalloc in loops
55 | buffer = stackalloc byte[buffer.Length * 2];
56 | #pragma warning restore CA2014 // Do not use stackalloc in loops
57 | }
58 | else
59 | {
60 | buffer = new byte[buffer.Length * 2]; // too large
61 | }
62 | }
63 |
64 | var space = alignment - bytesWritten;
65 | if (space > 0)
66 | {
67 | AppendWhitespace(space);
68 | }
69 |
70 | TryGrow(bytesWritten);
71 | buffer.Slice(0, bytesWritten).CopyTo(destination);
72 | destination = destination.Slice(bytesWritten);
73 | currentWritten += bytesWritten;
74 | return;
75 | }
76 | else
77 | {
78 | // add right whitespace
79 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
80 | {
81 | GrowCore(0);
82 | }
83 | destination = destination.Slice(bytesWritten);
84 | currentWritten += bytesWritten;
85 |
86 | var space = bytesWritten + alignment;
87 | if (space < 0)
88 | {
89 | AppendWhitespace(-space);
90 | }
91 | }
92 | }
93 | #endif
94 |
95 | #if !NET8_0_OR_GREATER
96 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
97 | public void AppendFormatted(byte? value, int alignment = 0, string? format = null)
98 | {
99 | if (!value.HasValue)
100 | {
101 | if (alignment != 0)
102 | {
103 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
104 | }
105 | return;
106 | }
107 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
108 | }
109 |
110 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
111 | public void AppendFormatted(byte value, int alignment = 0, string? format = null)
112 | {
113 | if (alignment == 0)
114 | {
115 | int bytesWritten;
116 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
117 | {
118 | GrowCore(0);
119 | }
120 | destination = destination.Slice(bytesWritten);
121 | currentWritten += bytesWritten;
122 | return;
123 | }
124 |
125 | AppendFormattedAlignment(value, alignment, format);
126 | }
127 |
128 | void AppendFormattedAlignment(byte value, int alignment, string? format)
129 | {
130 | int bytesWritten;
131 |
132 | // add left whitespace
133 | if (alignment > 0)
134 | {
135 | Span buffer = stackalloc byte[32];
136 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
137 | {
138 | if (buffer.Length < 512)
139 | {
140 | #pragma warning disable CA2014 // Do not use stackalloc in loops
141 | buffer = stackalloc byte[buffer.Length * 2];
142 | #pragma warning restore CA2014 // Do not use stackalloc in loops
143 | }
144 | else
145 | {
146 | buffer = new byte[buffer.Length * 2]; // too large
147 | }
148 | }
149 |
150 | var space = alignment - bytesWritten;
151 | if (space > 0)
152 | {
153 | AppendWhitespace(space);
154 | }
155 |
156 | TryGrow(bytesWritten);
157 | buffer.Slice(0, bytesWritten).CopyTo(destination);
158 | destination = destination.Slice(bytesWritten);
159 | currentWritten += bytesWritten;
160 | return;
161 | }
162 | else
163 | {
164 | // add right whitespace
165 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
166 | {
167 | GrowCore(0);
168 | }
169 | destination = destination.Slice(bytesWritten);
170 | currentWritten += bytesWritten;
171 |
172 | var space = bytesWritten + alignment;
173 | if (space < 0)
174 | {
175 | AppendWhitespace(-space);
176 | }
177 | }
178 | }
179 | #endif
180 |
181 | #if !NET8_0_OR_GREATER
182 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
183 | public void AppendFormatted(Decimal? value, int alignment = 0, string? format = null)
184 | {
185 | if (!value.HasValue)
186 | {
187 | if (alignment != 0)
188 | {
189 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
190 | }
191 | return;
192 | }
193 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
194 | }
195 |
196 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
197 | public void AppendFormatted(Decimal value, int alignment = 0, string? format = null)
198 | {
199 | if (alignment == 0)
200 | {
201 | int bytesWritten;
202 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
203 | {
204 | GrowCore(0);
205 | }
206 | destination = destination.Slice(bytesWritten);
207 | currentWritten += bytesWritten;
208 | return;
209 | }
210 |
211 | AppendFormattedAlignment(value, alignment, format);
212 | }
213 |
214 | void AppendFormattedAlignment(Decimal value, int alignment, string? format)
215 | {
216 | int bytesWritten;
217 |
218 | // add left whitespace
219 | if (alignment > 0)
220 | {
221 | Span buffer = stackalloc byte[32];
222 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
223 | {
224 | if (buffer.Length < 512)
225 | {
226 | #pragma warning disable CA2014 // Do not use stackalloc in loops
227 | buffer = stackalloc byte[buffer.Length * 2];
228 | #pragma warning restore CA2014 // Do not use stackalloc in loops
229 | }
230 | else
231 | {
232 | buffer = new byte[buffer.Length * 2]; // too large
233 | }
234 | }
235 |
236 | var space = alignment - bytesWritten;
237 | if (space > 0)
238 | {
239 | AppendWhitespace(space);
240 | }
241 |
242 | TryGrow(bytesWritten);
243 | buffer.Slice(0, bytesWritten).CopyTo(destination);
244 | destination = destination.Slice(bytesWritten);
245 | currentWritten += bytesWritten;
246 | return;
247 | }
248 | else
249 | {
250 | // add right whitespace
251 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
252 | {
253 | GrowCore(0);
254 | }
255 | destination = destination.Slice(bytesWritten);
256 | currentWritten += bytesWritten;
257 |
258 | var space = bytesWritten + alignment;
259 | if (space < 0)
260 | {
261 | AppendWhitespace(-space);
262 | }
263 | }
264 | }
265 | #endif
266 |
267 | #if !NET8_0_OR_GREATER
268 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
269 | public void AppendFormatted(Double? value, int alignment = 0, string? format = null)
270 | {
271 | if (!value.HasValue)
272 | {
273 | if (alignment != 0)
274 | {
275 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
276 | }
277 | return;
278 | }
279 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
280 | }
281 |
282 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
283 | public void AppendFormatted(Double value, int alignment = 0, string? format = null)
284 | {
285 | if (alignment == 0)
286 | {
287 | int bytesWritten;
288 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
289 | {
290 | GrowCore(0);
291 | }
292 | destination = destination.Slice(bytesWritten);
293 | currentWritten += bytesWritten;
294 | return;
295 | }
296 |
297 | AppendFormattedAlignment(value, alignment, format);
298 | }
299 |
300 | void AppendFormattedAlignment(Double value, int alignment, string? format)
301 | {
302 | int bytesWritten;
303 |
304 | // add left whitespace
305 | if (alignment > 0)
306 | {
307 | Span buffer = stackalloc byte[32];
308 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
309 | {
310 | if (buffer.Length < 512)
311 | {
312 | #pragma warning disable CA2014 // Do not use stackalloc in loops
313 | buffer = stackalloc byte[buffer.Length * 2];
314 | #pragma warning restore CA2014 // Do not use stackalloc in loops
315 | }
316 | else
317 | {
318 | buffer = new byte[buffer.Length * 2]; // too large
319 | }
320 | }
321 |
322 | var space = alignment - bytesWritten;
323 | if (space > 0)
324 | {
325 | AppendWhitespace(space);
326 | }
327 |
328 | TryGrow(bytesWritten);
329 | buffer.Slice(0, bytesWritten).CopyTo(destination);
330 | destination = destination.Slice(bytesWritten);
331 | currentWritten += bytesWritten;
332 | return;
333 | }
334 | else
335 | {
336 | // add right whitespace
337 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
338 | {
339 | GrowCore(0);
340 | }
341 | destination = destination.Slice(bytesWritten);
342 | currentWritten += bytesWritten;
343 |
344 | var space = bytesWritten + alignment;
345 | if (space < 0)
346 | {
347 | AppendWhitespace(-space);
348 | }
349 | }
350 | }
351 | #endif
352 |
353 | #if !NET8_0_OR_GREATER
354 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
355 | public void AppendFormatted(Guid? value, int alignment = 0, string? format = null)
356 | {
357 | if (!value.HasValue)
358 | {
359 | if (alignment != 0)
360 | {
361 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
362 | }
363 | return;
364 | }
365 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
366 | }
367 |
368 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
369 | public void AppendFormatted(Guid value, int alignment = 0, string? format = null)
370 | {
371 | if (alignment == 0)
372 | {
373 | int bytesWritten;
374 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
375 | {
376 | GrowCore(0);
377 | }
378 | destination = destination.Slice(bytesWritten);
379 | currentWritten += bytesWritten;
380 | return;
381 | }
382 |
383 | AppendFormattedAlignment(value, alignment, format);
384 | }
385 |
386 | void AppendFormattedAlignment(Guid value, int alignment, string? format)
387 | {
388 | int bytesWritten;
389 |
390 | // add left whitespace
391 | if (alignment > 0)
392 | {
393 | Span buffer = stackalloc byte[32];
394 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
395 | {
396 | if (buffer.Length < 512)
397 | {
398 | #pragma warning disable CA2014 // Do not use stackalloc in loops
399 | buffer = stackalloc byte[buffer.Length * 2];
400 | #pragma warning restore CA2014 // Do not use stackalloc in loops
401 | }
402 | else
403 | {
404 | buffer = new byte[buffer.Length * 2]; // too large
405 | }
406 | }
407 |
408 | var space = alignment - bytesWritten;
409 | if (space > 0)
410 | {
411 | AppendWhitespace(space);
412 | }
413 |
414 | TryGrow(bytesWritten);
415 | buffer.Slice(0, bytesWritten).CopyTo(destination);
416 | destination = destination.Slice(bytesWritten);
417 | currentWritten += bytesWritten;
418 | return;
419 | }
420 | else
421 | {
422 | // add right whitespace
423 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
424 | {
425 | GrowCore(0);
426 | }
427 | destination = destination.Slice(bytesWritten);
428 | currentWritten += bytesWritten;
429 |
430 | var space = bytesWritten + alignment;
431 | if (space < 0)
432 | {
433 | AppendWhitespace(-space);
434 | }
435 | }
436 | }
437 | #endif
438 |
439 | #if !NET8_0_OR_GREATER
440 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
441 | public void AppendFormatted(Int16? value, int alignment = 0, string? format = null)
442 | {
443 | if (!value.HasValue)
444 | {
445 | if (alignment != 0)
446 | {
447 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
448 | }
449 | return;
450 | }
451 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
452 | }
453 |
454 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
455 | public void AppendFormatted(Int16 value, int alignment = 0, string? format = null)
456 | {
457 | if (alignment == 0)
458 | {
459 | int bytesWritten;
460 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
461 | {
462 | GrowCore(0);
463 | }
464 | destination = destination.Slice(bytesWritten);
465 | currentWritten += bytesWritten;
466 | return;
467 | }
468 |
469 | AppendFormattedAlignment(value, alignment, format);
470 | }
471 |
472 | void AppendFormattedAlignment(Int16 value, int alignment, string? format)
473 | {
474 | int bytesWritten;
475 |
476 | // add left whitespace
477 | if (alignment > 0)
478 | {
479 | Span buffer = stackalloc byte[32];
480 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
481 | {
482 | if (buffer.Length < 512)
483 | {
484 | #pragma warning disable CA2014 // Do not use stackalloc in loops
485 | buffer = stackalloc byte[buffer.Length * 2];
486 | #pragma warning restore CA2014 // Do not use stackalloc in loops
487 | }
488 | else
489 | {
490 | buffer = new byte[buffer.Length * 2]; // too large
491 | }
492 | }
493 |
494 | var space = alignment - bytesWritten;
495 | if (space > 0)
496 | {
497 | AppendWhitespace(space);
498 | }
499 |
500 | TryGrow(bytesWritten);
501 | buffer.Slice(0, bytesWritten).CopyTo(destination);
502 | destination = destination.Slice(bytesWritten);
503 | currentWritten += bytesWritten;
504 | return;
505 | }
506 | else
507 | {
508 | // add right whitespace
509 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
510 | {
511 | GrowCore(0);
512 | }
513 | destination = destination.Slice(bytesWritten);
514 | currentWritten += bytesWritten;
515 |
516 | var space = bytesWritten + alignment;
517 | if (space < 0)
518 | {
519 | AppendWhitespace(-space);
520 | }
521 | }
522 | }
523 | #endif
524 |
525 | #if !NET8_0_OR_GREATER
526 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
527 | public void AppendFormatted(Int32? value, int alignment = 0, string? format = null)
528 | {
529 | if (!value.HasValue)
530 | {
531 | if (alignment != 0)
532 | {
533 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
534 | }
535 | return;
536 | }
537 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
538 | }
539 |
540 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
541 | public void AppendFormatted(Int32 value, int alignment = 0, string? format = null)
542 | {
543 | if (alignment == 0)
544 | {
545 | int bytesWritten;
546 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
547 | {
548 | GrowCore(0);
549 | }
550 | destination = destination.Slice(bytesWritten);
551 | currentWritten += bytesWritten;
552 | return;
553 | }
554 |
555 | AppendFormattedAlignment(value, alignment, format);
556 | }
557 |
558 | void AppendFormattedAlignment(Int32 value, int alignment, string? format)
559 | {
560 | int bytesWritten;
561 |
562 | // add left whitespace
563 | if (alignment > 0)
564 | {
565 | Span buffer = stackalloc byte[32];
566 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
567 | {
568 | if (buffer.Length < 512)
569 | {
570 | #pragma warning disable CA2014 // Do not use stackalloc in loops
571 | buffer = stackalloc byte[buffer.Length * 2];
572 | #pragma warning restore CA2014 // Do not use stackalloc in loops
573 | }
574 | else
575 | {
576 | buffer = new byte[buffer.Length * 2]; // too large
577 | }
578 | }
579 |
580 | var space = alignment - bytesWritten;
581 | if (space > 0)
582 | {
583 | AppendWhitespace(space);
584 | }
585 |
586 | TryGrow(bytesWritten);
587 | buffer.Slice(0, bytesWritten).CopyTo(destination);
588 | destination = destination.Slice(bytesWritten);
589 | currentWritten += bytesWritten;
590 | return;
591 | }
592 | else
593 | {
594 | // add right whitespace
595 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
596 | {
597 | GrowCore(0);
598 | }
599 | destination = destination.Slice(bytesWritten);
600 | currentWritten += bytesWritten;
601 |
602 | var space = bytesWritten + alignment;
603 | if (space < 0)
604 | {
605 | AppendWhitespace(-space);
606 | }
607 | }
608 | }
609 | #endif
610 |
611 | #if !NET8_0_OR_GREATER
612 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
613 | public void AppendFormatted(Int64? value, int alignment = 0, string? format = null)
614 | {
615 | if (!value.HasValue)
616 | {
617 | if (alignment != 0)
618 | {
619 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
620 | }
621 | return;
622 | }
623 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
624 | }
625 |
626 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
627 | public void AppendFormatted(Int64 value, int alignment = 0, string? format = null)
628 | {
629 | if (alignment == 0)
630 | {
631 | int bytesWritten;
632 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
633 | {
634 | GrowCore(0);
635 | }
636 | destination = destination.Slice(bytesWritten);
637 | currentWritten += bytesWritten;
638 | return;
639 | }
640 |
641 | AppendFormattedAlignment(value, alignment, format);
642 | }
643 |
644 | void AppendFormattedAlignment(Int64 value, int alignment, string? format)
645 | {
646 | int bytesWritten;
647 |
648 | // add left whitespace
649 | if (alignment > 0)
650 | {
651 | Span buffer = stackalloc byte[32];
652 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
653 | {
654 | if (buffer.Length < 512)
655 | {
656 | #pragma warning disable CA2014 // Do not use stackalloc in loops
657 | buffer = stackalloc byte[buffer.Length * 2];
658 | #pragma warning restore CA2014 // Do not use stackalloc in loops
659 | }
660 | else
661 | {
662 | buffer = new byte[buffer.Length * 2]; // too large
663 | }
664 | }
665 |
666 | var space = alignment - bytesWritten;
667 | if (space > 0)
668 | {
669 | AppendWhitespace(space);
670 | }
671 |
672 | TryGrow(bytesWritten);
673 | buffer.Slice(0, bytesWritten).CopyTo(destination);
674 | destination = destination.Slice(bytesWritten);
675 | currentWritten += bytesWritten;
676 | return;
677 | }
678 | else
679 | {
680 | // add right whitespace
681 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
682 | {
683 | GrowCore(0);
684 | }
685 | destination = destination.Slice(bytesWritten);
686 | currentWritten += bytesWritten;
687 |
688 | var space = bytesWritten + alignment;
689 | if (space < 0)
690 | {
691 | AppendWhitespace(-space);
692 | }
693 | }
694 | }
695 | #endif
696 |
697 | #if !NET8_0_OR_GREATER
698 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
699 | public void AppendFormatted(SByte? value, int alignment = 0, string? format = null)
700 | {
701 | if (!value.HasValue)
702 | {
703 | if (alignment != 0)
704 | {
705 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
706 | }
707 | return;
708 | }
709 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
710 | }
711 |
712 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
713 | public void AppendFormatted(SByte value, int alignment = 0, string? format = null)
714 | {
715 | if (alignment == 0)
716 | {
717 | int bytesWritten;
718 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
719 | {
720 | GrowCore(0);
721 | }
722 | destination = destination.Slice(bytesWritten);
723 | currentWritten += bytesWritten;
724 | return;
725 | }
726 |
727 | AppendFormattedAlignment(value, alignment, format);
728 | }
729 |
730 | void AppendFormattedAlignment(SByte value, int alignment, string? format)
731 | {
732 | int bytesWritten;
733 |
734 | // add left whitespace
735 | if (alignment > 0)
736 | {
737 | Span buffer = stackalloc byte[32];
738 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
739 | {
740 | if (buffer.Length < 512)
741 | {
742 | #pragma warning disable CA2014 // Do not use stackalloc in loops
743 | buffer = stackalloc byte[buffer.Length * 2];
744 | #pragma warning restore CA2014 // Do not use stackalloc in loops
745 | }
746 | else
747 | {
748 | buffer = new byte[buffer.Length * 2]; // too large
749 | }
750 | }
751 |
752 | var space = alignment - bytesWritten;
753 | if (space > 0)
754 | {
755 | AppendWhitespace(space);
756 | }
757 |
758 | TryGrow(bytesWritten);
759 | buffer.Slice(0, bytesWritten).CopyTo(destination);
760 | destination = destination.Slice(bytesWritten);
761 | currentWritten += bytesWritten;
762 | return;
763 | }
764 | else
765 | {
766 | // add right whitespace
767 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
768 | {
769 | GrowCore(0);
770 | }
771 | destination = destination.Slice(bytesWritten);
772 | currentWritten += bytesWritten;
773 |
774 | var space = bytesWritten + alignment;
775 | if (space < 0)
776 | {
777 | AppendWhitespace(-space);
778 | }
779 | }
780 | }
781 | #endif
782 |
783 | #if !NET8_0_OR_GREATER
784 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
785 | public void AppendFormatted(Single? value, int alignment = 0, string? format = null)
786 | {
787 | if (!value.HasValue)
788 | {
789 | if (alignment != 0)
790 | {
791 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
792 | }
793 | return;
794 | }
795 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
796 | }
797 |
798 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
799 | public void AppendFormatted(Single value, int alignment = 0, string? format = null)
800 | {
801 | if (alignment == 0)
802 | {
803 | int bytesWritten;
804 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
805 | {
806 | GrowCore(0);
807 | }
808 | destination = destination.Slice(bytesWritten);
809 | currentWritten += bytesWritten;
810 | return;
811 | }
812 |
813 | AppendFormattedAlignment(value, alignment, format);
814 | }
815 |
816 | void AppendFormattedAlignment(Single value, int alignment, string? format)
817 | {
818 | int bytesWritten;
819 |
820 | // add left whitespace
821 | if (alignment > 0)
822 | {
823 | Span buffer = stackalloc byte[32];
824 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
825 | {
826 | if (buffer.Length < 512)
827 | {
828 | #pragma warning disable CA2014 // Do not use stackalloc in loops
829 | buffer = stackalloc byte[buffer.Length * 2];
830 | #pragma warning restore CA2014 // Do not use stackalloc in loops
831 | }
832 | else
833 | {
834 | buffer = new byte[buffer.Length * 2]; // too large
835 | }
836 | }
837 |
838 | var space = alignment - bytesWritten;
839 | if (space > 0)
840 | {
841 | AppendWhitespace(space);
842 | }
843 |
844 | TryGrow(bytesWritten);
845 | buffer.Slice(0, bytesWritten).CopyTo(destination);
846 | destination = destination.Slice(bytesWritten);
847 | currentWritten += bytesWritten;
848 | return;
849 | }
850 | else
851 | {
852 | // add right whitespace
853 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
854 | {
855 | GrowCore(0);
856 | }
857 | destination = destination.Slice(bytesWritten);
858 | currentWritten += bytesWritten;
859 |
860 | var space = bytesWritten + alignment;
861 | if (space < 0)
862 | {
863 | AppendWhitespace(-space);
864 | }
865 | }
866 | }
867 | #endif
868 |
869 | #if !NET8_0_OR_GREATER
870 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
871 | public void AppendFormatted(UInt16? value, int alignment = 0, string? format = null)
872 | {
873 | if (!value.HasValue)
874 | {
875 | if (alignment != 0)
876 | {
877 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
878 | }
879 | return;
880 | }
881 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
882 | }
883 |
884 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
885 | public void AppendFormatted(UInt16 value, int alignment = 0, string? format = null)
886 | {
887 | if (alignment == 0)
888 | {
889 | int bytesWritten;
890 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
891 | {
892 | GrowCore(0);
893 | }
894 | destination = destination.Slice(bytesWritten);
895 | currentWritten += bytesWritten;
896 | return;
897 | }
898 |
899 | AppendFormattedAlignment(value, alignment, format);
900 | }
901 |
902 | void AppendFormattedAlignment(UInt16 value, int alignment, string? format)
903 | {
904 | int bytesWritten;
905 |
906 | // add left whitespace
907 | if (alignment > 0)
908 | {
909 | Span buffer = stackalloc byte[32];
910 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
911 | {
912 | if (buffer.Length < 512)
913 | {
914 | #pragma warning disable CA2014 // Do not use stackalloc in loops
915 | buffer = stackalloc byte[buffer.Length * 2];
916 | #pragma warning restore CA2014 // Do not use stackalloc in loops
917 | }
918 | else
919 | {
920 | buffer = new byte[buffer.Length * 2]; // too large
921 | }
922 | }
923 |
924 | var space = alignment - bytesWritten;
925 | if (space > 0)
926 | {
927 | AppendWhitespace(space);
928 | }
929 |
930 | TryGrow(bytesWritten);
931 | buffer.Slice(0, bytesWritten).CopyTo(destination);
932 | destination = destination.Slice(bytesWritten);
933 | currentWritten += bytesWritten;
934 | return;
935 | }
936 | else
937 | {
938 | // add right whitespace
939 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
940 | {
941 | GrowCore(0);
942 | }
943 | destination = destination.Slice(bytesWritten);
944 | currentWritten += bytesWritten;
945 |
946 | var space = bytesWritten + alignment;
947 | if (space < 0)
948 | {
949 | AppendWhitespace(-space);
950 | }
951 | }
952 | }
953 | #endif
954 |
955 | #if !NET8_0_OR_GREATER
956 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
957 | public void AppendFormatted(UInt32? value, int alignment = 0, string? format = null)
958 | {
959 | if (!value.HasValue)
960 | {
961 | if (alignment != 0)
962 | {
963 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
964 | }
965 | return;
966 | }
967 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
968 | }
969 |
970 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
971 | public void AppendFormatted(UInt32 value, int alignment = 0, string? format = null)
972 | {
973 | if (alignment == 0)
974 | {
975 | int bytesWritten;
976 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
977 | {
978 | GrowCore(0);
979 | }
980 | destination = destination.Slice(bytesWritten);
981 | currentWritten += bytesWritten;
982 | return;
983 | }
984 |
985 | AppendFormattedAlignment(value, alignment, format);
986 | }
987 |
988 | void AppendFormattedAlignment(UInt32 value, int alignment, string? format)
989 | {
990 | int bytesWritten;
991 |
992 | // add left whitespace
993 | if (alignment > 0)
994 | {
995 | Span buffer = stackalloc byte[32];
996 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
997 | {
998 | if (buffer.Length < 512)
999 | {
1000 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1001 | buffer = stackalloc byte[buffer.Length * 2];
1002 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1003 | }
1004 | else
1005 | {
1006 | buffer = new byte[buffer.Length * 2]; // too large
1007 | }
1008 | }
1009 |
1010 | var space = alignment - bytesWritten;
1011 | if (space > 0)
1012 | {
1013 | AppendWhitespace(space);
1014 | }
1015 |
1016 | TryGrow(bytesWritten);
1017 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1018 | destination = destination.Slice(bytesWritten);
1019 | currentWritten += bytesWritten;
1020 | return;
1021 | }
1022 | else
1023 | {
1024 | // add right whitespace
1025 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
1026 | {
1027 | GrowCore(0);
1028 | }
1029 | destination = destination.Slice(bytesWritten);
1030 | currentWritten += bytesWritten;
1031 |
1032 | var space = bytesWritten + alignment;
1033 | if (space < 0)
1034 | {
1035 | AppendWhitespace(-space);
1036 | }
1037 | }
1038 | }
1039 | #endif
1040 |
1041 | #if !NET8_0_OR_GREATER
1042 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1043 | public void AppendFormatted(UInt64? value, int alignment = 0, string? format = null)
1044 | {
1045 | if (!value.HasValue)
1046 | {
1047 | if (alignment != 0)
1048 | {
1049 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1050 | }
1051 | return;
1052 | }
1053 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1054 | }
1055 |
1056 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1057 | public void AppendFormatted(UInt64 value, int alignment = 0, string? format = null)
1058 | {
1059 | if (alignment == 0)
1060 | {
1061 | int bytesWritten;
1062 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
1063 | {
1064 | GrowCore(0);
1065 | }
1066 | destination = destination.Slice(bytesWritten);
1067 | currentWritten += bytesWritten;
1068 | return;
1069 | }
1070 |
1071 | AppendFormattedAlignment(value, alignment, format);
1072 | }
1073 |
1074 | void AppendFormattedAlignment(UInt64 value, int alignment, string? format)
1075 | {
1076 | int bytesWritten;
1077 |
1078 | // add left whitespace
1079 | if (alignment > 0)
1080 | {
1081 | Span buffer = stackalloc byte[32];
1082 | while (!Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format)))
1083 | {
1084 | if (buffer.Length < 512)
1085 | {
1086 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1087 | buffer = stackalloc byte[buffer.Length * 2];
1088 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1089 | }
1090 | else
1091 | {
1092 | buffer = new byte[buffer.Length * 2]; // too large
1093 | }
1094 | }
1095 |
1096 | var space = alignment - bytesWritten;
1097 | if (space > 0)
1098 | {
1099 | AppendWhitespace(space);
1100 | }
1101 |
1102 | TryGrow(bytesWritten);
1103 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1104 | destination = destination.Slice(bytesWritten);
1105 | currentWritten += bytesWritten;
1106 | return;
1107 | }
1108 | else
1109 | {
1110 | // add right whitespace
1111 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format)))
1112 | {
1113 | GrowCore(0);
1114 | }
1115 | destination = destination.Slice(bytesWritten);
1116 | currentWritten += bytesWritten;
1117 |
1118 | var space = bytesWritten + alignment;
1119 | if (space < 0)
1120 | {
1121 | AppendWhitespace(-space);
1122 | }
1123 | }
1124 | }
1125 | #endif
1126 |
1127 | #if !NET8_0_OR_GREATER
1128 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1129 | public void AppendFormatted(DateTime? value, int alignment = 0, string? format = null)
1130 | {
1131 | if (!value.HasValue)
1132 | {
1133 | if (alignment != 0)
1134 | {
1135 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1136 | }
1137 | return;
1138 | }
1139 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1140 | }
1141 |
1142 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1143 | public void AppendFormatted(DateTime value, int alignment = 0, string? format = null)
1144 | {
1145 | if (alignment == 0)
1146 | {
1147 | int bytesWritten;
1148 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1149 | {
1150 | GrowCore(0);
1151 | }
1152 | destination = destination.Slice(bytesWritten);
1153 | currentWritten += bytesWritten;
1154 | return;
1155 | }
1156 |
1157 | AppendFormattedAlignment(value, alignment, format);
1158 | }
1159 |
1160 | void AppendFormattedAlignment(DateTime value, int alignment, string? format)
1161 | {
1162 | int bytesWritten;
1163 |
1164 | // add left whitespace
1165 | if (alignment > 0)
1166 | {
1167 | Span buffer = stackalloc byte[32];
1168 | while (!value.TryFormat(buffer, out bytesWritten, format, formatProvider))
1169 | {
1170 | if (buffer.Length < 512)
1171 | {
1172 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1173 | buffer = stackalloc byte[buffer.Length * 2];
1174 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1175 | }
1176 | else
1177 | {
1178 | buffer = new byte[buffer.Length * 2]; // too large
1179 | }
1180 | }
1181 |
1182 | var space = alignment - bytesWritten;
1183 | if (space > 0)
1184 | {
1185 | AppendWhitespace(space);
1186 | }
1187 |
1188 | TryGrow(bytesWritten);
1189 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1190 | destination = destination.Slice(bytesWritten);
1191 | currentWritten += bytesWritten;
1192 | return;
1193 | }
1194 | else
1195 | {
1196 | // add right whitespace
1197 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1198 | {
1199 | GrowCore(0);
1200 | }
1201 | destination = destination.Slice(bytesWritten);
1202 | currentWritten += bytesWritten;
1203 |
1204 | var space = bytesWritten + alignment;
1205 | if (space < 0)
1206 | {
1207 | AppendWhitespace(-space);
1208 | }
1209 | }
1210 | }
1211 | #endif
1212 |
1213 | #if !NET8_0_OR_GREATER
1214 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1215 | public void AppendFormatted(DateTimeOffset? value, int alignment = 0, string? format = null)
1216 | {
1217 | if (!value.HasValue)
1218 | {
1219 | if (alignment != 0)
1220 | {
1221 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1222 | }
1223 | return;
1224 | }
1225 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1226 | }
1227 |
1228 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1229 | public void AppendFormatted(DateTimeOffset value, int alignment = 0, string? format = null)
1230 | {
1231 | if (alignment == 0)
1232 | {
1233 | int bytesWritten;
1234 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1235 | {
1236 | GrowCore(0);
1237 | }
1238 | destination = destination.Slice(bytesWritten);
1239 | currentWritten += bytesWritten;
1240 | return;
1241 | }
1242 |
1243 | AppendFormattedAlignment(value, alignment, format);
1244 | }
1245 |
1246 | void AppendFormattedAlignment(DateTimeOffset value, int alignment, string? format)
1247 | {
1248 | int bytesWritten;
1249 |
1250 | // add left whitespace
1251 | if (alignment > 0)
1252 | {
1253 | Span buffer = stackalloc byte[32];
1254 | while (!value.TryFormat(buffer, out bytesWritten, format, formatProvider))
1255 | {
1256 | if (buffer.Length < 512)
1257 | {
1258 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1259 | buffer = stackalloc byte[buffer.Length * 2];
1260 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1261 | }
1262 | else
1263 | {
1264 | buffer = new byte[buffer.Length * 2]; // too large
1265 | }
1266 | }
1267 |
1268 | var space = alignment - bytesWritten;
1269 | if (space > 0)
1270 | {
1271 | AppendWhitespace(space);
1272 | }
1273 |
1274 | TryGrow(bytesWritten);
1275 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1276 | destination = destination.Slice(bytesWritten);
1277 | currentWritten += bytesWritten;
1278 | return;
1279 | }
1280 | else
1281 | {
1282 | // add right whitespace
1283 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1284 | {
1285 | GrowCore(0);
1286 | }
1287 | destination = destination.Slice(bytesWritten);
1288 | currentWritten += bytesWritten;
1289 |
1290 | var space = bytesWritten + alignment;
1291 | if (space < 0)
1292 | {
1293 | AppendWhitespace(-space);
1294 | }
1295 | }
1296 | }
1297 | #endif
1298 |
1299 | #if !NET8_0_OR_GREATER
1300 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1301 | public void AppendFormatted(TimeSpan? value, int alignment = 0, string? format = null)
1302 | {
1303 | if (!value.HasValue)
1304 | {
1305 | if (alignment != 0)
1306 | {
1307 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1308 | }
1309 | return;
1310 | }
1311 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1312 | }
1313 |
1314 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1315 | public void AppendFormatted(TimeSpan value, int alignment = 0, string? format = null)
1316 | {
1317 | if (alignment == 0)
1318 | {
1319 | int bytesWritten;
1320 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1321 | {
1322 | GrowCore(0);
1323 | }
1324 | destination = destination.Slice(bytesWritten);
1325 | currentWritten += bytesWritten;
1326 | return;
1327 | }
1328 |
1329 | AppendFormattedAlignment(value, alignment, format);
1330 | }
1331 |
1332 | void AppendFormattedAlignment(TimeSpan value, int alignment, string? format)
1333 | {
1334 | int bytesWritten;
1335 |
1336 | // add left whitespace
1337 | if (alignment > 0)
1338 | {
1339 | Span buffer = stackalloc byte[32];
1340 | while (!value.TryFormat(buffer, out bytesWritten, format, formatProvider))
1341 | {
1342 | if (buffer.Length < 512)
1343 | {
1344 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1345 | buffer = stackalloc byte[buffer.Length * 2];
1346 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1347 | }
1348 | else
1349 | {
1350 | buffer = new byte[buffer.Length * 2]; // too large
1351 | }
1352 | }
1353 |
1354 | var space = alignment - bytesWritten;
1355 | if (space > 0)
1356 | {
1357 | AppendWhitespace(space);
1358 | }
1359 |
1360 | TryGrow(bytesWritten);
1361 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1362 | destination = destination.Slice(bytesWritten);
1363 | currentWritten += bytesWritten;
1364 | return;
1365 | }
1366 | else
1367 | {
1368 | // add right whitespace
1369 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1370 | {
1371 | GrowCore(0);
1372 | }
1373 | destination = destination.Slice(bytesWritten);
1374 | currentWritten += bytesWritten;
1375 |
1376 | var space = bytesWritten + alignment;
1377 | if (space < 0)
1378 | {
1379 | AppendWhitespace(-space);
1380 | }
1381 | }
1382 | }
1383 | #endif
1384 |
1385 | #if true
1386 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1387 | public void AppendFormatted(char? value, int alignment = 0, string? format = null)
1388 | {
1389 | if (!value.HasValue)
1390 | {
1391 | if (alignment != 0)
1392 | {
1393 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1394 | }
1395 | return;
1396 | }
1397 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1398 | }
1399 |
1400 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1401 | public void AppendFormatted(char value, int alignment = 0, string? format = null)
1402 | {
1403 | if (alignment == 0)
1404 | {
1405 | int bytesWritten;
1406 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1407 | {
1408 | GrowCore(0);
1409 | }
1410 | destination = destination.Slice(bytesWritten);
1411 | currentWritten += bytesWritten;
1412 | return;
1413 | }
1414 |
1415 | AppendFormattedAlignment(value, alignment, format);
1416 | }
1417 |
1418 | void AppendFormattedAlignment(char value, int alignment, string? format)
1419 | {
1420 | int bytesWritten;
1421 |
1422 | // add left whitespace
1423 | if (alignment > 0)
1424 | {
1425 | Span buffer = stackalloc byte[32];
1426 | while (!value.TryFormat(buffer, out bytesWritten, format, formatProvider))
1427 | {
1428 | if (buffer.Length < 512)
1429 | {
1430 | #pragma warning disable CA2014 // Do not use stackalloc in loops
1431 | buffer = stackalloc byte[buffer.Length * 2];
1432 | #pragma warning restore CA2014 // Do not use stackalloc in loops
1433 | }
1434 | else
1435 | {
1436 | buffer = new byte[buffer.Length * 2]; // too large
1437 | }
1438 | }
1439 |
1440 | var space = alignment - bytesWritten;
1441 | if (space > 0)
1442 | {
1443 | AppendWhitespace(space);
1444 | }
1445 |
1446 | TryGrow(bytesWritten);
1447 | buffer.Slice(0, bytesWritten).CopyTo(destination);
1448 | destination = destination.Slice(bytesWritten);
1449 | currentWritten += bytesWritten;
1450 | return;
1451 | }
1452 | else
1453 | {
1454 | // add right whitespace
1455 | while (!value.TryFormat(destination, out bytesWritten, format, formatProvider))
1456 | {
1457 | GrowCore(0);
1458 | }
1459 | destination = destination.Slice(bytesWritten);
1460 | currentWritten += bytesWritten;
1461 |
1462 | var space = bytesWritten + alignment;
1463 | if (space < 0)
1464 | {
1465 | AppendWhitespace(-space);
1466 | }
1467 | }
1468 | }
1469 | #endif
1470 |
1471 |
1472 | #if NET8_0_OR_GREATER
1473 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1474 | public void AppendFormatted(byte? value, int alignment = 0, string? format = null)
1475 | {
1476 | if (!value.HasValue)
1477 | {
1478 | if (alignment != 0)
1479 | {
1480 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1481 | }
1482 | return;
1483 | }
1484 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1485 | }
1486 |
1487 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1488 | public void AppendFormatted(byte value, int alignment = 0, string? format = null)
1489 | {
1490 | if (alignment == 0 && format == null)
1491 | {
1492 | int bytesWritten;
1493 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1494 | {
1495 | GrowCore(0);
1496 | }
1497 | destination = destination.Slice(bytesWritten);
1498 | currentWritten += bytesWritten;
1499 | return;
1500 | }
1501 | else
1502 | {
1503 | AppendFormattedCore(value, alignment, format);
1504 | }
1505 | }
1506 |
1507 |
1508 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1509 | public void AppendFormatted(Decimal? value, int alignment = 0, string? format = null)
1510 | {
1511 | if (!value.HasValue)
1512 | {
1513 | if (alignment != 0)
1514 | {
1515 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1516 | }
1517 | return;
1518 | }
1519 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1520 | }
1521 |
1522 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1523 | public void AppendFormatted(Decimal value, int alignment = 0, string? format = null)
1524 | {
1525 | if (alignment == 0 && format == null)
1526 | {
1527 | int bytesWritten;
1528 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1529 | {
1530 | GrowCore(0);
1531 | }
1532 | destination = destination.Slice(bytesWritten);
1533 | currentWritten += bytesWritten;
1534 | return;
1535 | }
1536 | else
1537 | {
1538 | AppendFormattedCore(value, alignment, format);
1539 | }
1540 | }
1541 |
1542 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1543 | public void AppendFormatted(Double? value, int alignment = 0, string? format = null)
1544 | {
1545 | if (!value.HasValue)
1546 | {
1547 | if (alignment != 0)
1548 | {
1549 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1550 | }
1551 | return;
1552 | }
1553 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1554 | }
1555 |
1556 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1557 | public void AppendFormatted(Double value, int alignment = 0, string? format = null)
1558 | {
1559 | if (alignment == 0 && format == null)
1560 | {
1561 | int bytesWritten;
1562 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1563 | {
1564 | GrowCore(0);
1565 | }
1566 | destination = destination.Slice(bytesWritten);
1567 | currentWritten += bytesWritten;
1568 | return;
1569 | }
1570 | else
1571 | {
1572 | AppendFormattedCore(value, alignment, format);
1573 | }
1574 | }
1575 |
1576 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1577 | public void AppendFormatted(Guid? value, int alignment = 0, string? format = null)
1578 | {
1579 | if (!value.HasValue)
1580 | {
1581 | if (alignment != 0)
1582 | {
1583 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1584 | }
1585 | return;
1586 | }
1587 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1588 | }
1589 |
1590 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1591 | public void AppendFormatted(Guid value, int alignment = 0, string? format = null)
1592 | {
1593 | if (alignment == 0 && format == null)
1594 | {
1595 | int bytesWritten;
1596 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1597 | {
1598 | GrowCore(0);
1599 | }
1600 | destination = destination.Slice(bytesWritten);
1601 | currentWritten += bytesWritten;
1602 | return;
1603 | }
1604 | else
1605 | {
1606 | AppendFormattedCore(value, alignment, format);
1607 | }
1608 | }
1609 |
1610 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1611 | public void AppendFormatted(Int16? value, int alignment = 0, string? format = null)
1612 | {
1613 | if (!value.HasValue)
1614 | {
1615 | if (alignment != 0)
1616 | {
1617 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1618 | }
1619 | return;
1620 | }
1621 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1622 | }
1623 |
1624 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1625 | public void AppendFormatted(Int16 value, int alignment = 0, string? format = null)
1626 | {
1627 | if (alignment == 0 && format == null)
1628 | {
1629 | int bytesWritten;
1630 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1631 | {
1632 | GrowCore(0);
1633 | }
1634 | destination = destination.Slice(bytesWritten);
1635 | currentWritten += bytesWritten;
1636 | return;
1637 | }
1638 | else
1639 | {
1640 | AppendFormattedCore(value, alignment, format);
1641 | }
1642 | }
1643 |
1644 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1645 | public void AppendFormatted(Int32? value, int alignment = 0, string? format = null)
1646 | {
1647 | if (!value.HasValue)
1648 | {
1649 | if (alignment != 0)
1650 | {
1651 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1652 | }
1653 | return;
1654 | }
1655 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1656 | }
1657 |
1658 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1659 | public void AppendFormatted(Int32 value, int alignment = 0, string? format = null)
1660 | {
1661 | if (alignment == 0 && format == null)
1662 | {
1663 | int bytesWritten;
1664 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1665 | {
1666 | GrowCore(0);
1667 | }
1668 | destination = destination.Slice(bytesWritten);
1669 | currentWritten += bytesWritten;
1670 | return;
1671 | }
1672 | else
1673 | {
1674 | AppendFormattedCore(value, alignment, format);
1675 | }
1676 | }
1677 |
1678 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1679 | public void AppendFormatted(Int64? value, int alignment = 0, string? format = null)
1680 | {
1681 | if (!value.HasValue)
1682 | {
1683 | if (alignment != 0)
1684 | {
1685 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1686 | }
1687 | return;
1688 | }
1689 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1690 | }
1691 |
1692 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1693 | public void AppendFormatted(Int64 value, int alignment = 0, string? format = null)
1694 | {
1695 | if (alignment == 0 && format == null)
1696 | {
1697 | int bytesWritten;
1698 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1699 | {
1700 | GrowCore(0);
1701 | }
1702 | destination = destination.Slice(bytesWritten);
1703 | currentWritten += bytesWritten;
1704 | return;
1705 | }
1706 | else
1707 | {
1708 | AppendFormattedCore(value, alignment, format);
1709 | }
1710 | }
1711 |
1712 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1713 | public void AppendFormatted(SByte? value, int alignment = 0, string? format = null)
1714 | {
1715 | if (!value.HasValue)
1716 | {
1717 | if (alignment != 0)
1718 | {
1719 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1720 | }
1721 | return;
1722 | }
1723 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1724 | }
1725 |
1726 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1727 | public void AppendFormatted(SByte value, int alignment = 0, string? format = null)
1728 | {
1729 | if (alignment == 0 && format == null)
1730 | {
1731 | int bytesWritten;
1732 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1733 | {
1734 | GrowCore(0);
1735 | }
1736 | destination = destination.Slice(bytesWritten);
1737 | currentWritten += bytesWritten;
1738 | return;
1739 | }
1740 | else
1741 | {
1742 | AppendFormattedCore(value, alignment, format);
1743 | }
1744 | }
1745 |
1746 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1747 | public void AppendFormatted(Single? value, int alignment = 0, string? format = null)
1748 | {
1749 | if (!value.HasValue)
1750 | {
1751 | if (alignment != 0)
1752 | {
1753 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1754 | }
1755 | return;
1756 | }
1757 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1758 | }
1759 |
1760 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1761 | public void AppendFormatted(Single value, int alignment = 0, string? format = null)
1762 | {
1763 | if (alignment == 0 && format == null)
1764 | {
1765 | int bytesWritten;
1766 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1767 | {
1768 | GrowCore(0);
1769 | }
1770 | destination = destination.Slice(bytesWritten);
1771 | currentWritten += bytesWritten;
1772 | return;
1773 | }
1774 | else
1775 | {
1776 | AppendFormattedCore(value, alignment, format);
1777 | }
1778 | }
1779 |
1780 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1781 | public void AppendFormatted(UInt16? value, int alignment = 0, string? format = null)
1782 | {
1783 | if (!value.HasValue)
1784 | {
1785 | if (alignment != 0)
1786 | {
1787 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1788 | }
1789 | return;
1790 | }
1791 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1792 | }
1793 |
1794 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1795 | public void AppendFormatted(UInt16 value, int alignment = 0, string? format = null)
1796 | {
1797 | if (alignment == 0 && format == null)
1798 | {
1799 | int bytesWritten;
1800 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1801 | {
1802 | GrowCore(0);
1803 | }
1804 | destination = destination.Slice(bytesWritten);
1805 | currentWritten += bytesWritten;
1806 | return;
1807 | }
1808 | else
1809 | {
1810 | AppendFormattedCore(value, alignment, format);
1811 | }
1812 | }
1813 |
1814 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1815 | public void AppendFormatted(UInt32? value, int alignment = 0, string? format = null)
1816 | {
1817 | if (!value.HasValue)
1818 | {
1819 | if (alignment != 0)
1820 | {
1821 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1822 | }
1823 | return;
1824 | }
1825 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1826 | }
1827 |
1828 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1829 | public void AppendFormatted(UInt32 value, int alignment = 0, string? format = null)
1830 | {
1831 | if (alignment == 0 && format == null)
1832 | {
1833 | int bytesWritten;
1834 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1835 | {
1836 | GrowCore(0);
1837 | }
1838 | destination = destination.Slice(bytesWritten);
1839 | currentWritten += bytesWritten;
1840 | return;
1841 | }
1842 | else
1843 | {
1844 | AppendFormattedCore(value, alignment, format);
1845 | }
1846 | }
1847 |
1848 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1849 | public void AppendFormatted(UInt64? value, int alignment = 0, string? format = null)
1850 | {
1851 | if (!value.HasValue)
1852 | {
1853 | if (alignment != 0)
1854 | {
1855 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
1856 | }
1857 | return;
1858 | }
1859 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
1860 | }
1861 |
1862 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
1863 | public void AppendFormatted(UInt64 value, int alignment = 0, string? format = null)
1864 | {
1865 | if (alignment == 0 && format == null)
1866 | {
1867 | int bytesWritten;
1868 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
1869 | {
1870 | GrowCore(0);
1871 | }
1872 | destination = destination.Slice(bytesWritten);
1873 | currentWritten += bytesWritten;
1874 | return;
1875 | }
1876 | else
1877 | {
1878 | AppendFormattedCore(value, alignment, format);
1879 | }
1880 | }
1881 |
1882 | #endif
1883 |
1884 | // `if typeof(T) == typeof()` will eliminate in JIT.
1885 | public void AppendFormatted(T value, int alignment = 0, string? format = null)
1886 | {
1887 | if (typeof(T) == typeof(bool))
1888 | {
1889 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1890 | }
1891 | else if (typeof(T) == typeof(char))
1892 | {
1893 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1894 | }
1895 | #if !NET8_0_OR_GREATER
1896 | else if (typeof(T) == typeof(byte))
1897 | {
1898 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1899 | }
1900 | else if (typeof(T) == typeof(Decimal))
1901 | {
1902 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1903 | }
1904 | else if (typeof(T) == typeof(Double))
1905 | {
1906 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1907 | }
1908 | else if (typeof(T) == typeof(Guid))
1909 | {
1910 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1911 | }
1912 | else if (typeof(T) == typeof(Int16))
1913 | {
1914 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1915 | }
1916 | else if (typeof(T) == typeof(Int32))
1917 | {
1918 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1919 | }
1920 | else if (typeof(T) == typeof(Int64))
1921 | {
1922 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1923 | }
1924 | else if (typeof(T) == typeof(SByte))
1925 | {
1926 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1927 | }
1928 | else if (typeof(T) == typeof(Single))
1929 | {
1930 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1931 | }
1932 | else if (typeof(T) == typeof(UInt16))
1933 | {
1934 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1935 | }
1936 | else if (typeof(T) == typeof(UInt32))
1937 | {
1938 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1939 | }
1940 | else if (typeof(T) == typeof(UInt64))
1941 | {
1942 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1943 | }
1944 | else if (typeof(T) == typeof(DateTime))
1945 | {
1946 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1947 | }
1948 | else if (typeof(T) == typeof(DateTimeOffset))
1949 | {
1950 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1951 | }
1952 | else if (typeof(T) == typeof(TimeSpan))
1953 | {
1954 | AppendFormatted(Unsafe.As(ref value), alignment, format);
1955 | }
1956 | #else
1957 | else if (typeof(T) == typeof(bool))
1958 | {
1959 | if (alignment == 0 && format == null)
1960 | {
1961 | AppendFormatted(Unsafe.As(ref value));
1962 | }
1963 | else
1964 | {
1965 | AppendFormattedCore(value, alignment, format);
1966 | }
1967 | }
1968 | else if (typeof(T) == typeof(byte))
1969 | {
1970 | if (alignment == 0 && format == null)
1971 | {
1972 | AppendFormatted(Unsafe.As(ref value));
1973 | }
1974 | else
1975 | {
1976 | AppendFormattedCore(value, alignment, format);
1977 | }
1978 | }
1979 | else if (typeof(T) == typeof(Decimal))
1980 | {
1981 | if (alignment == 0 && format == null)
1982 | {
1983 | AppendFormatted(Unsafe.As(ref value));
1984 | }
1985 | else
1986 | {
1987 | AppendFormattedCore(value, alignment, format);
1988 | }
1989 | }
1990 | else if (typeof(T) == typeof(Double))
1991 | {
1992 | if (alignment == 0 && format == null)
1993 | {
1994 | AppendFormatted(Unsafe.As(ref value));
1995 | }
1996 | else
1997 | {
1998 | AppendFormattedCore(value, alignment, format);
1999 | }
2000 | }
2001 | else if (typeof(T) == typeof(Guid))
2002 | {
2003 | if (alignment == 0 && format == null)
2004 | {
2005 | AppendFormatted(Unsafe.As(ref value));
2006 | }
2007 | else
2008 | {
2009 | AppendFormattedCore(value, alignment, format);
2010 | }
2011 | }
2012 | else if (typeof(T) == typeof(Int16))
2013 | {
2014 | if (alignment == 0 && format == null)
2015 | {
2016 | AppendFormatted(Unsafe.As(ref value));
2017 | }
2018 | else
2019 | {
2020 | AppendFormattedCore(value, alignment, format);
2021 | }
2022 | }
2023 | else if (typeof(T) == typeof(Int32))
2024 | {
2025 | if (alignment == 0 && format == null)
2026 | {
2027 | AppendFormatted(Unsafe.As(ref value));
2028 | }
2029 | else
2030 | {
2031 | AppendFormattedCore(value, alignment, format);
2032 | }
2033 | }
2034 | else if (typeof(T) == typeof(Int64))
2035 | {
2036 | if (alignment == 0 && format == null)
2037 | {
2038 | AppendFormatted(Unsafe.As(ref value));
2039 | }
2040 | else
2041 | {
2042 | AppendFormattedCore(value, alignment, format);
2043 | }
2044 | }
2045 | else if (typeof(T) == typeof(SByte))
2046 | {
2047 | if (alignment == 0 && format == null)
2048 | {
2049 | AppendFormatted(Unsafe.As(ref value));
2050 | }
2051 | else
2052 | {
2053 | AppendFormattedCore(value, alignment, format);
2054 | }
2055 | }
2056 | else if (typeof(T) == typeof(Single))
2057 | {
2058 | if (alignment == 0 && format == null)
2059 | {
2060 | AppendFormatted(Unsafe.As(ref value));
2061 | }
2062 | else
2063 | {
2064 | AppendFormattedCore(value, alignment, format);
2065 | }
2066 | }
2067 | else if (typeof(T) == typeof(UInt16))
2068 | {
2069 | if (alignment == 0 && format == null)
2070 | {
2071 | AppendFormatted(Unsafe.As(ref value));
2072 | }
2073 | else
2074 | {
2075 | AppendFormattedCore(value, alignment, format);
2076 | }
2077 | }
2078 | else if (typeof(T) == typeof(UInt32))
2079 | {
2080 | if (alignment == 0 && format == null)
2081 | {
2082 | AppendFormatted(Unsafe.As(ref value));
2083 | }
2084 | else
2085 | {
2086 | AppendFormattedCore(value, alignment, format);
2087 | }
2088 | }
2089 | else if (typeof(T) == typeof(UInt64))
2090 | {
2091 | if (alignment == 0 && format == null)
2092 | {
2093 | AppendFormatted(Unsafe.As(ref value));
2094 | }
2095 | else
2096 | {
2097 | AppendFormattedCore(value, alignment, format);
2098 | }
2099 | }
2100 | #endif
2101 | else
2102 | {
2103 | AppendFormattedCore(value, alignment, format);
2104 | }
2105 | }
2106 | }
2107 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8StringWriter.AppendFormatted.tt:
--------------------------------------------------------------------------------
1 | <#@ template debug="false" hostspecific="false" language="C#" #>
2 | <#@ assembly name="System.Core" #>
3 | <#@ import namespace="System.Linq" #>
4 | <#@ import namespace="System.Text" #>
5 | <#@ import namespace="System.Collections.Generic" #>
6 | <#@ output extension=".cs" #>
7 | <#
8 | // https://learn.microsoft.com/ja-jp/dotnet/api/system.buffers.text.utf8formatter.tryformat
9 | // without DateTime, DateTimeOffset, TimeSpan
10 | var utf8FormatterTypes = "bool,byte,Decimal,Double,Guid,Int16,Int32,Int64,SByte,Single,UInt16,UInt32,UInt64".Split(',');
11 | var tryFormatTypes = "DateTime,DateTimeOffset,TimeSpan,char".Split(',');
12 |
13 | var utf8FormatterWrite1 = "Utf8Formatter.TryFormat(value, destination, out bytesWritten, StandardFormat.Parse(format))";
14 | var utf8FormatterWrite2 = "Utf8Formatter.TryFormat(value, buffer, out bytesWritten, StandardFormat.Parse(format))";
15 |
16 | var tryFormatWrite1 = "value.TryFormat(destination, out bytesWritten, format, formatProvider)";
17 | var tryFormatWrite2 = "value.TryFormat(buffer, out bytesWritten, format, formatProvider)";
18 |
19 | var generateTypes = utf8FormatterTypes.Select(x => (type: x, format1: utf8FormatterWrite1, format2: utf8FormatterWrite2))
20 | .Concat(tryFormatTypes.Select(x => (type: x, format1: tryFormatWrite1, format2: tryFormatWrite2)))
21 | .ToArray();
22 | #>
23 | using System.Buffers;
24 | using System.Buffers.Text;
25 | using System.Runtime.CompilerServices;
26 |
27 | namespace Utf8StringInterpolation;
28 |
29 | public ref partial struct Utf8StringWriter
30 | {
31 | <# foreach(var x in generateTypes) { #>
32 | <#= (x.type is not "bool" and not "char") ? "#if !NET8_0_OR_GREATER" : "#if true" #>
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public void AppendFormatted(<#= x.type #>? value, int alignment = 0, string? format = null)
35 | {
36 | if (!value.HasValue)
37 | {
38 | if (alignment != 0)
39 | {
40 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
41 | }
42 | return;
43 | }
44 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
45 | }
46 |
47 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
48 | public void AppendFormatted(<#= x.type #> value, int alignment = 0, string? format = null)
49 | {
50 | if (alignment == 0)
51 | {
52 | int bytesWritten;
53 | while (!<#= x.format1 #>)
54 | {
55 | GrowCore(0);
56 | }
57 | destination = destination.Slice(bytesWritten);
58 | currentWritten += bytesWritten;
59 | return;
60 | }
61 |
62 | AppendFormattedAlignment(value, alignment, format);
63 | }
64 |
65 | void AppendFormattedAlignment(<#= x.type #> value, int alignment, string? format)
66 | {
67 | int bytesWritten;
68 |
69 | // add left whitespace
70 | if (alignment > 0)
71 | {
72 | Span buffer = stackalloc byte[32];
73 | while (!<#= x.format2 #>)
74 | {
75 | if (buffer.Length < 512)
76 | {
77 | #pragma warning disable CA2014 // Do not use stackalloc in loops
78 | buffer = stackalloc byte[buffer.Length * 2];
79 | #pragma warning restore CA2014 // Do not use stackalloc in loops
80 | }
81 | else
82 | {
83 | buffer = new byte[buffer.Length * 2]; // too large
84 | }
85 | }
86 |
87 | var space = alignment - bytesWritten;
88 | if (space > 0)
89 | {
90 | AppendWhitespace(space);
91 | }
92 |
93 | TryGrow(bytesWritten);
94 | buffer.Slice(0, bytesWritten).CopyTo(destination);
95 | destination = destination.Slice(bytesWritten);
96 | currentWritten += bytesWritten;
97 | return;
98 | }
99 | else
100 | {
101 | // add right whitespace
102 | while (!<#= x.format1 #>)
103 | {
104 | GrowCore(0);
105 | }
106 | destination = destination.Slice(bytesWritten);
107 | currentWritten += bytesWritten;
108 |
109 | var space = bytesWritten + alignment;
110 | if (space < 0)
111 | {
112 | AppendWhitespace(-space);
113 | }
114 | }
115 | }
116 | #endif
117 |
118 | <# } #>
119 |
120 | #if NET8_0_OR_GREATER
121 | <# foreach(var x in utf8FormatterTypes.Where(x => x is not "bool" and not "char")) { #>
122 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
123 | public void AppendFormatted(<#= x #>? value, int alignment = 0, string? format = null)
124 | {
125 | if (!value.HasValue)
126 | {
127 | if (alignment != 0)
128 | {
129 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
130 | }
131 | return;
132 | }
133 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
134 | }
135 |
136 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
137 | public void AppendFormatted(<#= x #> value, int alignment = 0, string? format = null)
138 | {
139 | if (alignment == 0 && format == null)
140 | {
141 | int bytesWritten;
142 | while (!Utf8Formatter.TryFormat(value, destination, out bytesWritten, default))
143 | {
144 | GrowCore(0);
145 | }
146 | destination = destination.Slice(bytesWritten);
147 | currentWritten += bytesWritten;
148 | return;
149 | }
150 | else
151 | {
152 | AppendFormattedCore(value, alignment, format);
153 | }
154 | }
155 |
156 | <# } #>
157 | #endif
158 |
159 | // `if typeof(T) == typeof()` will eliminate in JIT.
160 | public void AppendFormatted(T value, int alignment = 0, string? format = null)
161 | {
162 | if (typeof(T) == typeof(bool))
163 | {
164 | AppendFormatted(Unsafe.As(ref value), alignment, format);
165 | }
166 | else if (typeof(T) == typeof(char))
167 | {
168 | AppendFormatted(Unsafe.As(ref value), alignment, format);
169 | }
170 | #if !NET8_0_OR_GREATER
171 | <# foreach(var x in generateTypes.Where(x => x.type is not "bool" and not "char")) { #>
172 | else if (typeof(T) == typeof(<#= x.type #>))
173 | {
174 | AppendFormatted(Unsafe.As>(ref value), alignment, format);
175 | }
176 | <# } #>
177 | #else
178 | <# foreach(var x in utf8FormatterTypes) { #>
179 | else if (typeof(T) == typeof(<#= x #>))
180 | {
181 | if (alignment == 0 && format == null)
182 | {
183 | AppendFormatted(Unsafe.As>(ref value));
184 | }
185 | else
186 | {
187 | AppendFormattedCore(value, alignment, format);
188 | }
189 | }
190 | <# } #>
191 | #endif
192 | else
193 | {
194 | AppendFormattedCore(value, alignment, format);
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/Utf8StringInterpolation/Utf8StringWriter.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable CA2014 // Do not use stackalloc in loops
2 |
3 | using System.Buffers;
4 | using System.Runtime.CompilerServices;
5 | using System.Text;
6 | using Utf8StringInterpolation.Internal;
7 |
8 | #if NET6_0_OR_GREATER
9 | using System.Diagnostics;
10 | using System.Text.Unicode;
11 | #endif
12 |
13 | namespace Utf8StringInterpolation;
14 |
15 | [InterpolatedStringHandler]
16 | public ref partial struct Utf8StringWriter
17 | where TBufferWriter : IBufferWriter
18 | {
19 | static readonly byte[] NewLineUtf8Bytes = Encoding.UTF8.GetBytes(Environment.NewLine);
20 |
21 | const int DefaultInitialSize = 256;
22 | const int GuessedLengthPerHole = 11;
23 |
24 | Span destination;
25 | int allocatedDestinationSize;
26 | TBufferWriter bufferWriter;
27 | int currentWritten;
28 | IFormatProvider? formatProvider;
29 | bool calculateStringJustSize;
30 |
31 | public TBufferWriter GetBufferWriter() => bufferWriter;
32 | public int GetCurrentWritten() => currentWritten;
33 | public int GetAllocatedDestinationSize() => allocatedDestinationSize;
34 |
35 | // create directly
36 | public Utf8StringWriter(TBufferWriter bufferWriter, IFormatProvider? formatProvider = default)
37 | {
38 | this.bufferWriter = bufferWriter;
39 | this.formatProvider = formatProvider;
40 | TryGrow(DefaultInitialSize);
41 | }
42 |
43 | // from interpolated string
44 | public Utf8StringWriter(int literalLength, int formattedCount, TBufferWriter bufferWriter, IFormatProvider? formatProvider = default)
45 | {
46 | this.bufferWriter = bufferWriter;
47 | this.formatProvider = formatProvider;
48 | var initialSize = literalLength + (formattedCount * GuessedLengthPerHole);
49 | TryGrow(initialSize);
50 | }
51 |
52 | // from byte[] Format, use ThreadStatic ArrayBufferWriter
53 | public Utf8StringWriter(int literalLength, int formattedCount, IFormatProvider? formatProvider = default)
54 | {
55 | this.bufferWriter = (TBufferWriter)(object)ArrayBufferWriterPool.GetThreadStaticInstance();
56 | this.formatProvider = formatProvider;
57 | var initialSize = literalLength + (formattedCount * GuessedLengthPerHole);
58 | TryGrow(initialSize);
59 | }
60 |
61 | // from bool TryFormat, use ThreadStatic ArrayBufferWriter
62 | public Utf8StringWriter(int literalLength, int formattedCount, Span destination, IFormatProvider? formatProvider = default)
63 | {
64 | this.bufferWriter = (TBufferWriter)(object)ArrayBufferWriterPool.GetThreadStaticInstance();
65 | this.formatProvider = formatProvider;
66 | this.destination = destination;
67 | this.allocatedDestinationSize = destination.Length;
68 | this.calculateStringJustSize = true;
69 | this.bufferWriter.GetSpan(destination.Length); // allocate dummy
70 | }
71 |
72 | // from AppendFormat extension methods.
73 | public Utf8StringWriter(int literalLength, int formattedCount, scoped ref Utf8StringWriter parent)
74 | {
75 | parent.ClearState();
76 | this.bufferWriter = parent.bufferWriter;
77 | this.formatProvider = parent.formatProvider;
78 | var initialSize = literalLength + (formattedCount * GuessedLengthPerHole);
79 | TryGrow(initialSize);
80 | }
81 |
82 | public void AppendLiteral(string s)
83 | {
84 | AppendString(s.AsSpan());
85 | }
86 |
87 | public void AppendWhitespace(int count)
88 | {
89 | TryGrow(count);
90 | destination.Slice(0, count).Fill((byte)' ');
91 | destination = destination.Slice(count);
92 | currentWritten += count;
93 | }
94 |
95 | public void Append(string? s)
96 | {
97 | if (s == null) return;
98 | AppendLiteral(s);
99 | }
100 |
101 | public void Append(char c)
102 | {
103 | Span xs = stackalloc char[1];
104 | xs[0] = c;
105 | AppendFormatted(xs);
106 | }
107 |
108 | public void Append(char c, int repeatCount)
109 | {
110 | Span xs = stackalloc char[1];
111 | xs[0] = c;
112 | Span ys = stackalloc byte[16];
113 | var written = Encoding.UTF8.GetBytes(xs, ys);
114 | if (written == 1 && ys[0] == (byte)c)
115 | {
116 | TryGrow(repeatCount);
117 | destination.Slice(0, repeatCount).Fill((byte)c);
118 | destination = destination.Slice(repeatCount);
119 | currentWritten += repeatCount;
120 | }
121 | else
122 | {
123 | var encodedChar = ys.Slice(0, written);
124 | var total = repeatCount * written;
125 | TryGrow(total);
126 | for (int i = 0; i < repeatCount; i++)
127 | {
128 | encodedChar.CopyTo(destination);
129 | destination = destination.Slice(written);
130 | }
131 | currentWritten += total;
132 | }
133 | }
134 |
135 | public void AppendUtf8(scoped ReadOnlySpan utf8String)
136 | {
137 | if (utf8String.Length == 0) return;
138 | TryGrow(utf8String.Length);
139 | utf8String.CopyTo(destination);
140 | var bytesWritten = utf8String.Length;
141 | destination = destination.Slice(bytesWritten);
142 | currentWritten += bytesWritten;
143 | }
144 |
145 | public void AppendFormatted(scoped ReadOnlySpan utf8String)
146 | {
147 | AppendUtf8(utf8String);
148 | }
149 |
150 | public void AppendFormatted(scoped ReadOnlySpan s)
151 | {
152 | AppendString(s);
153 | }
154 |
155 | int AppendString(scoped ReadOnlySpan s)
156 | {
157 | if (s.Length == 0) return 0;
158 | var max = GetStringByteCount(s);
159 | TryGrow(max);
160 | var bytesWritten = Encoding.UTF8.GetBytes(s, destination);
161 | destination = destination.Slice(bytesWritten);
162 | currentWritten += bytesWritten;
163 | return bytesWritten;
164 | }
165 |
166 | public void AppendFormatted(string value, int alignment = 0, string? format = null)
167 | {
168 | if (alignment == 0)
169 | {
170 | AppendLiteral(value);
171 | return;
172 | }
173 |
174 | // add left whitespace
175 | if (alignment > 0)
176 | {
177 | var max = GetStringByteCount(value.AsSpan());
178 | var rentArray = ArrayPool.Shared.Rent(max);
179 | var buffer = rentArray.AsSpan();
180 | var bytesWritten = Encoding.UTF8.GetBytes(value.AsSpan(), buffer);
181 |
182 | int charCount;
183 | #if NETSTANDARD2_0
184 | unsafe
185 | {
186 | fixed (byte* ptr = &buffer[0])
187 | {
188 | charCount = Encoding.UTF8.GetCharCount(ptr, bytesWritten);
189 | }
190 | }
191 | #else
192 | charCount = Encoding.UTF8.GetCharCount(buffer.Slice(0, bytesWritten));
193 | #endif
194 | var space = alignment - charCount;
195 | if (space > 0)
196 | {
197 | AppendWhitespace(space);
198 | }
199 |
200 | TryGrow(bytesWritten);
201 | buffer.Slice(0, bytesWritten).CopyTo(destination);
202 | destination = destination.Slice(bytesWritten);
203 | currentWritten += bytesWritten;
204 | ArrayPool.Shared.Return(rentArray);
205 | }
206 | else
207 | {
208 | // add right whitespace
209 | var max = GetStringByteCount(value.AsSpan());
210 | TryGrow(max);
211 | var bytesWritten = Encoding.UTF8.GetBytes(value.AsSpan(), destination);
212 |
213 | int charCount;
214 | #if NETSTANDARD2_0
215 | unsafe
216 | {
217 | fixed (byte* ptr = &destination[0])
218 | {
219 | charCount = Encoding.UTF8.GetCharCount(ptr, bytesWritten);
220 | }
221 | }
222 | #else
223 | charCount = Encoding.UTF8.GetCharCount(destination.Slice(0, bytesWritten));
224 | #endif
225 | destination = destination.Slice(bytesWritten);
226 | currentWritten += bytesWritten;
227 |
228 | var space = charCount + alignment;
229 | if (space < 0)
230 | {
231 | AppendWhitespace(-space);
232 | }
233 | }
234 | }
235 |
236 | public void AppendFormatted(T? value, int alignment = 0, string? format = null)
237 | where T : struct
238 | {
239 | if (!value.HasValue)
240 | {
241 | if (alignment != 0)
242 | {
243 | AppendWhitespace(alignment < 0 ? -alignment : alignment);
244 | }
245 | return;
246 | }
247 | AppendFormatted(value.GetValueOrDefault(), alignment, format);
248 | }
249 |
250 | void AppendFormattedCore(T value, int alignment = 0, string? format = null)
251 | {
252 | // no alignment or add right whitespace
253 | if (alignment <= 0)
254 | {
255 | int bytesWritten;
256 |
257 | #if NET8_0_OR_GREATER
258 | if (typeof(T).IsEnum)
259 | {
260 | bytesWritten = AppendEnum(value, format);
261 | goto WRITE_WHITESPACE;
262 | }
263 |
264 | // .NET 8
265 | if (value is IUtf8SpanFormattable)
266 | {
267 | // constrained call avoiding boxing for value types
268 | while (!((IUtf8SpanFormattable)value).TryFormat(destination, out bytesWritten, format, formatProvider))
269 | {
270 | GrowCore(0);
271 | }
272 | destination = destination.Slice(bytesWritten);
273 | currentWritten += bytesWritten;
274 | goto WRITE_WHITESPACE;
275 | }
276 | #endif
277 |
278 | #if NET6_0_OR_GREATER
279 | // .NET 6, better than ToString
280 | if (value is ISpanFormattable)
281 | {
282 | bytesWritten = AppendSpanFormattable(value, format);
283 | goto WRITE_WHITESPACE;
284 | }
285 | #endif
286 |
287 | // String fallbacks
288 | string? s;
289 | if (value is IFormattable)
290 | {
291 | s = ((IFormattable)value).ToString(format, formatProvider);
292 | }
293 | else
294 | {
295 | s = value?.ToString();
296 | }
297 |
298 | bytesWritten = AppendString(s.AsSpan());
299 | goto WRITE_WHITESPACE;
300 |
301 | WRITE_WHITESPACE:
302 | if (alignment != 0)
303 | {
304 | var space = bytesWritten + alignment;
305 | if (space < 0)
306 | {
307 | AppendWhitespace(-space);
308 | }
309 | }
310 | }
311 | else
312 | {
313 | // add left whitespace
314 | // first, write to temp buffer
315 | using var buffer = Utf8String.CreateWriter(out var builder);
316 | builder.AppendFormatted(value, 0, format); // no alignment
317 | builder.Flush();
318 |
319 | var bytesWritten = buffer.WrittenCount;
320 |
321 | var space = alignment - bytesWritten;
322 | if (space > 0)
323 | {
324 | AppendWhitespace(space);
325 | }
326 |
327 | TryGrow(bytesWritten);
328 | buffer.WrittenSpan.CopyTo(destination);
329 | destination = destination.Slice(bytesWritten);
330 | currentWritten += bytesWritten;
331 | }
332 | }
333 |
334 | #if NET6_0_OR_GREATER
335 |
336 | int AppendSpanFormattable(T value, string? format)
337 | {
338 | Debug.Assert(value is ISpanFormattable);
339 |
340 | Span charDest = stackalloc char[256];
341 | int charWritten;
342 | while (!((ISpanFormattable)value).TryFormat(charDest, out charWritten, format, formatProvider))
343 | {
344 | if (charDest.Length < 512)
345 | {
346 | charDest = stackalloc char[charDest.Length * 2];
347 | }
348 | else
349 | {
350 | charDest = new char[charDest.Length * 2]; // too large
351 | }
352 | }
353 |
354 | var slice = charDest.Slice(0, charWritten);
355 | var count = Encoding.UTF8.GetByteCount(slice);
356 | TryGrow(count);
357 | var bytesWritten = Encoding.UTF8.GetBytes(slice, destination);
358 | destination = destination.Slice(bytesWritten);
359 | currentWritten += bytesWritten;
360 | return bytesWritten;
361 | }
362 |
363 | #endif
364 |
365 | #if NET8_0_OR_GREATER
366 |
367 | int AppendEnum(T value, string? format)
368 | {
369 | // Enum.TryFormat is constrained, TryWriteInterpolatedStringHandler uses unsconstrained version internally.
370 | Span dest = stackalloc byte[256];
371 | var written = 0;
372 | while (!Utf8.TryWrite(dest, formatProvider, $"{value}", out written))
373 | {
374 | if (dest.Length < 512)
375 | {
376 | dest = stackalloc byte[dest.Length * 2];
377 | }
378 | else
379 | {
380 | dest = new byte[dest.Length * 2]; // too large
381 | }
382 | }
383 |
384 | AppendUtf8(dest.Slice(0, written));
385 | return written;
386 | }
387 |
388 | #endif
389 |
390 | public void AppendLine()
391 | {
392 | AppendUtf8(NewLineUtf8Bytes);
393 | }
394 |
395 | public void AppendLine(string s)
396 | {
397 | AppendLiteral(s);
398 | AppendUtf8(NewLineUtf8Bytes);
399 | }
400 |
401 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
402 | public void TryGrow(int len)
403 | {
404 | if (destination.Length < len)
405 | {
406 | GrowCore(len);
407 | }
408 | }
409 |
410 | void GrowCore(int len)
411 | {
412 | if (currentWritten != 0)
413 | {
414 | bufferWriter.Advance(currentWritten);
415 | currentWritten = 0;
416 | }
417 | destination = bufferWriter.GetSpan(Math.Max(allocatedDestinationSize * 2, len));
418 | allocatedDestinationSize = destination.Length;
419 | }
420 |
421 | public void ClearState()
422 | {
423 | Flush();
424 | destination = default;
425 | allocatedDestinationSize = 0;
426 | currentWritten = 0;
427 | }
428 |
429 | int GetStringByteCount(scoped ReadOnlySpan str)
430 | {
431 | return calculateStringJustSize ? Encoding.UTF8.GetByteCount(str) : Encoding.UTF8.GetMaxByteCount(str.Length);
432 | }
433 |
434 | public void Flush()
435 | {
436 | if (currentWritten != 0)
437 | {
438 | bufferWriter.Advance(currentWritten);
439 | currentWritten = 0;
440 | }
441 | }
442 |
443 | public void Dispose()
444 | {
445 | if (bufferWriter != null && destination.Length != 0)
446 | {
447 | Flush();
448 | }
449 | bufferWriter = default!;
450 | destination = default!;
451 | }
452 | }
453 |
454 | public static class Utf8StringExtensions
455 | {
456 | // hack for use nested InterpolatedStringHandler.
457 |
458 | public static void AppendFormat(
459 | this ref Utf8StringWriter parent,
460 | [InterpolatedStringHandlerArgument("parent")] ref Utf8StringWriter format)
461 | where TBufferWriter : IBufferWriter
462 | {
463 | format.Flush();
464 | }
465 |
466 | public static void AppendLine(
467 | this ref Utf8StringWriter parent,
468 | [InterpolatedStringHandlerArgument("parent")] ref Utf8StringWriter format)
469 | where TBufferWriter : IBufferWriter
470 | {
471 | format.Flush();
472 | parent.AppendLine();
473 | }
474 | }
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/CompositeFormatTest.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Buffers;
4 | using System.Text;
5 | using Xunit;
6 | using static FluentAssertions.FluentActions;
7 |
8 | using static Utf8StringInterpolation.Tests.FormatTest;
9 | namespace Utf8StringInterpolation.Tests
10 | {
11 | public class CompositeFormatTest
12 | {
13 | [Fact]
14 | public void AlignmentComponentInt()
15 | {
16 | Utf8String.Format($"{long.MaxValue,-1}{long.MinValue,1}").Should().Be($"{long.MaxValue,-1}{long.MinValue,1}");
17 | Utf8String.Format($"{1,1}{1,-1}").Should().Be($"{1,1}{1,-1}");
18 | Utf8String.Format($"{1,10}{1,-10}").Should().Be($"{1,10}{1,-10}");
19 | }
20 |
21 | [Fact]
22 | public void AlignmentComponentString()
23 | {
24 | Utf8String.Format($"{"left",0}{"right",0}").Should().Be($"{"left",0}{"right",0}");
25 | Utf8String.Format($"{"Foo",3}{"Foo",-3}").Should().Be($"{"Foo",3}{"Foo",-3}");
26 | Utf8String.Format($"{"Foo",4}{"Foo",-4}").Should().Be($"{"Foo",4}{"Foo",-4}");
27 | }
28 |
29 | [Fact]
30 | public void AlignmentComponent()
31 | {
32 | Utf8String.Format($"{long.MaxValue,-1000}{"",1000}").Should().Be($"{long.MaxValue,-1000}{"",1000}");
33 |
34 | #if NET8_0_OR_GREATER
35 | var guid = Guid.NewGuid();
36 | var neg = DateTime.Now.TimeOfDay.Negate();
37 | Utf8String.Format($"{guid,10:X}{guid}{neg,-10:c}").Should().Be($"{guid,10:X}{guid}{neg,-10:c}");
38 | #endif
39 |
40 | string[] names = { "Adam", "Bridgette", "Carla", "Daniel", "Ebenezer", "Francine", "George" };
41 | decimal[] hours = { 40, 6.667m, 40.39m, 82, 40.333m, 80, 16.75m };
42 |
43 | for (int ctr = 0; ctr < names.Length; ctr++)
44 | {
45 | Utf8String.Format($"{names[ctr],-20} {hours[ctr],5:f}").Should().Be($"{names[ctr],-20} {hours[ctr],5:f}");
46 | }
47 |
48 | }
49 |
50 |
51 | #if NET8_0_OR_GREATER
52 |
53 | // fail on .NET 6(because StandardFormat does not equal of format)
54 | [Fact]
55 | public void Spaces()
56 | {
57 | // var format = "Prime numbers less than 10: {00 , 01 }, {01 ,02 }, {2 ,3 :D }, {3 ,4: X }";
58 | var expected = $"Prime numbers less than 10: {2,01}, {3,02}, {5,3:D}, {7,4: X}";
59 | var actual = Utf8String.Format($"Prime numbers less than 10: {2,01}, {3,02}, {5,3:D}, {7,4: X}");
60 | actual.Should().Be(expected);
61 | }
62 |
63 | #endif
64 |
65 | [Fact]
66 | public void CompsiteFormats()
67 | {
68 | Utf8String.Format($"Name = {"Fred"}, {500_0000_0000_0000m:f}({500_0000_0000_0000m:E})").Should().Be($"Name = {"Fred"}, {500_0000_0000_0000m:f}({500_0000_0000_0000m:E})");
69 | }
70 |
71 | }
72 | }
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/EnumTest.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using Xunit;
6 |
7 | namespace Utf8StringInterpolation.Tests
8 | {
9 | public enum DuplicateEnum
10 | {
11 | A = 1,
12 | B = 2,
13 | BB = 2,
14 | C = 3
15 | }
16 |
17 | public enum StandardEnum
18 | {
19 | Abc = 1,
20 | Def = 2,
21 | Ghi = 3,
22 | }
23 |
24 | [Flags]
25 | public enum FlagsEnum
26 | {
27 | None = 0,
28 | Abc = 1,
29 | Bcd = 2,
30 | Efg = 4,
31 | }
32 |
33 | public class EnumTest
34 | {
35 | [Fact]
36 | public void Duplicate()
37 | {
38 | Utf8String.Format($"{DuplicateEnum.A}").Should().Be("A");
39 | Utf8String.Format($"{DuplicateEnum.B}").Should().Be("B");
40 | Utf8String.Format($"{DuplicateEnum.BB}").Should().Be("B");
41 | Utf8String.Format($"{DuplicateEnum.C}").Should().Be("C");
42 | }
43 |
44 | [Fact]
45 | public void Standard()
46 | {
47 | Utf8String.Format($"{StandardEnum.Abc}").Should().Be("Abc");
48 | Utf8String.Format($"{StandardEnum.Def}").Should().Be("Def");
49 | Utf8String.Format($"{StandardEnum.Ghi}").Should().Be("Ghi");
50 | }
51 |
52 | [Fact]
53 | public void Flags()
54 | {
55 | Utf8String.Format($"{FlagsEnum.Abc | FlagsEnum.Bcd}").Should().Be("Abc, Bcd");
56 | Utf8String.Format($"{FlagsEnum.None}").Should().Be("None");
57 | Utf8String.Format($"{FlagsEnum.Efg}").Should().Be("Efg");
58 | }
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/FormatTest.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 |
3 | namespace Utf8StringInterpolation.Tests
4 | {
5 | public class FormatTest
6 | {
7 | [Fact]
8 | public void EmptyFormat()
9 | {
10 | Utf8String.Format($"").Should().Be($"");
11 | }
12 |
13 | [Fact]
14 | public void NoFormat()
15 | {
16 | Utf8String.Format($"abcdefg").Should().Be($"abcdefg");
17 | }
18 |
19 | [Fact]
20 | public void SingleFormat()
21 | {
22 | Utf8String.Format($"{100}").Should().Be($"{100}");
23 | }
24 |
25 | [Fact]
26 | public void DoubleFormat()
27 | {
28 | Utf8String.Format($"{100}{200}").Should().Be($"{100}{200}");
29 | }
30 |
31 | [Fact]
32 | public void Nullable()
33 | {
34 | var guid = (Guid?)Guid.NewGuid();
35 | Utf8String.Format($"abc{(int?)100}def{(int?)1}ghi").Should().Be($"abc{(int?)100}def{(int?)1}ghi");
36 | Utf8String.Format($"abc{(int?)100:X}def{(int?)1:X}ghi").Should().Be($"abc{(int?)100:X}def{(int?)1:X}ghi");
37 | Utf8String.Format($"abc{guid}def{(Guid?)null}ghi").Should().Be($"abc{guid}def{(Guid?)null}ghi");
38 | Utf8String.Format($"abc{(double?)Math.PI:e}def{(double?)null:e}ghi").Should().Be($"abc{(double?)Math.PI:e}def{(double?)null:e}ghi");
39 | }
40 |
41 | [Fact]
42 | public void NullableWithAlignment()
43 | {
44 | var guid = (Guid?)Guid.NewGuid();
45 | Utf8String.Format($"abc{(int?)100,10}def{(int?)null,10}ghi").Should().Be($"abc{(int?)100,10}def{(int?)null,10}ghi");
46 | Utf8String.Format($"abc{(int?)100,-5:X}def{(int?)null,-5:X}ghi").Should().Be($"abc{(int?)100,-5:X}def{(int?)null,-5:X}ghi");
47 | Utf8String.Format($"abc{guid}def{(Guid?)null,10}ghi").Should().Be($"abc{guid}def{(Guid?)null,10}ghi");
48 | Utf8String.Format($"abc{(double?)null,5}def{(double?)null,5:e}ghi").Should().Be($"abc{(double?)null,5}def{(double?)null,5:e}ghi");
49 | }
50 |
51 | [Fact]
52 | public void Comment()
53 | {
54 | Utf8String.Format($"abc{{100}}def{200}ghi").Should().Be($"abc{{100}}def{200}ghi");
55 | Utf8String.Format($"}}{{{123}{{}}{456}{{").Should().Be($"}}{{{123}{{}}{456}{{");
56 | }
57 |
58 | #if NET8_0_OR_GREATER
59 |
60 | [Fact]
61 | public void FormatArgs()
62 | {
63 | Utf8String.Format($"{100:00000000}-{200:00000000}").Should().Be("00000100-00000200");
64 | }
65 |
66 | #endif
67 |
68 | [Fact]
69 | public void FormattableObject()
70 | {
71 | Utf8String.Format($"abc{default(Vector2)}def{new Vector2(MathF.PI):F3}").Should().Be($"abc{default(Vector2)}def{new Vector2(MathF.PI):F3}");
72 | Utf8String.Format($"abc{new Vector3(float.MinValue, float.NaN, float.MaxValue):E0}def{new Vector3(MathF.PI):N}").Should().Be($"abc{new Vector3(float.MinValue, float.NaN, float.MaxValue):E0}def{new Vector3(MathF.PI):N}");
73 | }
74 |
75 | [Fact]
76 | public void Escape()
77 | {
78 | TimeSpan span = new TimeSpan(12, 34, 56);
79 | var reference = string.Format(@"{0:h\,h\:mm\:ss}", span);
80 | var actual = Utf8String.Format($@"{span:h\,h\:mm\:ss}").Should().Be(reference);
81 | }
82 |
83 | [Fact]
84 | public void Unicode()
85 | {
86 | Utf8String.Format($"\u30cf\u30fc\u30c8: {"\u2764"}, \u5bb6\u65cf: {"\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67"}(\u7d75\u6587\u5b57)")
87 | .Should().Be($"\u30cf\u30fc\u30c8: {"\u2764"}, \u5bb6\u65cf: {"\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67"}(\u7d75\u6587\u5b57)");
88 | }
89 |
90 | [Fact]
91 | public void MultibyteStringAlignment()
92 | {
93 | Utf8String.Format($"abc{"あいう",10}").Should().Be($"abc{"あいう",10}");
94 | Utf8String.Format($"def{"えおか",-10}").Should().Be($"def{"えおか",-10}");
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/JoinTest.cs:
--------------------------------------------------------------------------------
1 | namespace Utf8StringInterpolation.Tests
2 | {
3 |
4 |
5 | public class JoinTest
6 | {
7 | [Fact]
8 | public void JoinOverloads()
9 | {
10 | Utf8String.Join("_,_", new string[0]).Should().Be(string.Join("_,_", new string[0]));
11 | Utf8String.Join("_,_", new[] { 1 }).Should().Be(string.Join("_,_", new[] { 1 }));
12 | Utf8String.Join("_,_", new[] { 1, 2 }).Should().Be(string.Join("_,_", new[] { 1, 2 }));
13 | Utf8String.Join("_,_", new[] { 1, 2, 3 }).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
14 |
15 | // Utf8String.Join("_,_", new string[] { }.AsEnumerable()).Should().Be(string.Join("_,_", new string[0]));
16 | Utf8String.Join("_,_", new[] { 1 }.AsEnumerable()).Should().Be(string.Join("_,_", new[] { 1 }));
17 | Utf8String.Join("_,_", new[] { 1, 2 }.AsEnumerable()).Should().Be(string.Join("_,_", new[] { 1, 2 }));
18 | Utf8String.Join("_,_", new[] { 1, 2, 3 }.AsEnumerable()).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
19 |
20 | Utf8String.Join(",", new string[0]).Should().Be(string.Join(",", new string[0]));
21 | Utf8String.Join(",", new[] { 1 }).Should().Be(string.Join(",", new[] { 1 }));
22 | Utf8String.Join(",", new[] { 1, 2 }).Should().Be(string.Join(",", new[] { 1, 2 }));
23 | Utf8String.Join(",", new[] { 1, 2, 3 }).Should().Be(string.Join(",", new[] { 1, 2, 3 }));
24 |
25 | // Utf8String.Join(",", new string[0].AsEnumerable()).Should().Be(string.Join(",", new string[0]));
26 | Utf8String.Join(",", new[] { 1 }.AsEnumerable()).Should().Be(string.Join(",", new[] { 1 }));
27 | Utf8String.Join(",", new[] { 1, 2 }.AsEnumerable()).Should().Be(string.Join(",", new[] { 1, 2 }));
28 | Utf8String.Join(",", new[] { 1, 2, 3 }.AsEnumerable()).Should().Be(string.Join(",", new[] { 1, 2, 3 }));
29 | }
30 |
31 | [Fact]
32 | public void JoinOverloads2()
33 | {
34 | // Utf8String.Join("_,_", new string[] { }.ToList()).Should().Be(string.Join("_,_", new string[0]));
35 | Utf8String.Join("_,_", new[] { 1 }.ToList()).Should().Be(string.Join("_,_", new[] { 1 }));
36 | Utf8String.Join("_,_", new[] { 1, 2 }.ToList()).Should().Be(string.Join("_,_", new[] { 1, 2 }));
37 | Utf8String.Join("_,_", new[] { 1, 2, 3 }.ToList()).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
38 |
39 | Utf8String.Join("_,_", new int[] { }).Should().Be(string.Join("_,_", new string[0]));
40 | Utf8String.Join("_,_", new[] { 1 }).Should().Be(string.Join("_,_", new[] { 1 }));
41 | Utf8String.Join("_,_", new[] { 1, 2 }).Should().Be(string.Join("_,_", new[] { 1, 2 }));
42 | Utf8String.Join("_,_", new[] { 1, 2, 3 }).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
43 |
44 | Utf8String.Join("_,_", new int[] { }).Should().Be(string.Join("_,_", new string[0]));
45 | Utf8String.Join("_,_", new[] { 1 }).Should().Be(string.Join("_,_", new[] { 1 }));
46 | Utf8String.Join("_,_", new[] { 1, 2 }).Should().Be(string.Join("_,_", new[] { 1, 2 }));
47 | Utf8String.Join("_,_", new[] { 1, 2, 3 }).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
48 |
49 | Utf8String.Join("_,_", new int[] { }).Should().Be(string.Join("_,_", new string[0]));
50 | Utf8String.Join("_,_", new[] { 1 }).Should().Be(string.Join("_,_", new[] { 1 }));
51 | Utf8String.Join("_,_", new[] { 1, 2 }).Should().Be(string.Join("_,_", new[] { 1, 2 }));
52 | Utf8String.Join("_,_", new[] { 1, 2, 3 }).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
53 |
54 | Utf8String.Join("_,_", new int[] { }).Should().Be(string.Join("_,_", new string[0]));
55 | Utf8String.Join("_,_", new[] { 1 }).Should().Be(string.Join("_,_", new[] { 1 }));
56 | Utf8String.Join("_,_", new[] { 1, 2 }).Should().Be(string.Join("_,_", new[] { 1, 2 }));
57 | Utf8String.Join("_,_", new[] { 1, 2, 3 }).Should().Be(string.Join("_,_", new[] { 1, 2, 3 }));
58 | }
59 |
60 | [Fact]
61 | public void JoinOverloads3()
62 | {
63 | // Utf8String.Join(",", new string[] { }.ToList()).Should().Be(string.Join(',', new string[0]));
64 | Utf8String.Join(",", new[] { 1 }.ToList()).Should().Be(Shims.Join(',', new[] { 1 }));
65 | Utf8String.Join(",", new[] { 1, 2 }.ToList()).Should().Be(Shims.Join(',', new[] { 1, 2 }));
66 | Utf8String.Join(",", new[] { 1, 2, 3 }.ToList()).Should().Be(Shims.Join(',', new[] { 1, 2, 3 }));
67 |
68 | Utf8String.Join(",", new int[] { }).Should().Be(Shims.Join(',', new string[0]));
69 | Utf8String.Join(",", new[] { 1 }).Should().Be(Shims.Join(',', new[] { 1 }));
70 | Utf8String.Join(",", new[] { 1, 2 }).Should().Be(Shims.Join(',', new[] { 1, 2 }));
71 | Utf8String.Join(",", new[] { 1, 2, 3 }).Should().Be(Shims.Join(',', new[] { 1, 2, 3 }));
72 |
73 | Utf8String.Join(",", new int[] { }).Should().Be(Shims.Join(',', new string[0]));
74 | Utf8String.Join(",", new[] { 1 }).Should().Be(Shims.Join(',', new[] { 1 }));
75 | Utf8String.Join(",", new[] { 1, 2 }).Should().Be(Shims.Join(',', new[] { 1, 2 }));
76 | Utf8String.Join(",", new[] { 1, 2, 3 }).Should().Be(Shims.Join(',', new[] { 1, 2, 3 }));
77 |
78 | Utf8String.Join(",", new int[] { }).Should().Be(Shims.Join(',', new string[0]));
79 | Utf8String.Join(",", new[] { 1 }).Should().Be(Shims.Join(',', new[] { 1 }));
80 | Utf8String.Join(",", new[] { 1, 2 }).Should().Be(Shims.Join(',', new[] { 1, 2 }));
81 | Utf8String.Join(",", new[] { 1, 2, 3 }).Should().Be(Shims.Join(',', new[] { 1, 2, 3 }));
82 |
83 | Utf8String.Join(",", new int[] { }).Should().Be(Shims.Join(',', new string[0]));
84 | Utf8String.Join(",", new[] { 1 }).Should().Be(Shims.Join(',', new[] { 1 }));
85 | Utf8String.Join(",", new[] { 1, 2 }).Should().Be(Shims.Join(',', new[] { 1, 2 }));
86 | Utf8String.Join(",", new[] { 1, 2, 3 }).Should().Be(Shims.Join(',', new[] { 1, 2, 3 }));
87 | }
88 |
89 | [Fact]
90 | public void CooncatNullTest()
91 | {
92 | var str = Utf8String.Concat(default, "foo", "bar");
93 | str.Should().Be(string.Concat(default, "foo", "bar"));
94 | }
95 |
96 | [Fact]
97 | public void ConcatHugeString()
98 | {
99 | var a = new string('a', 10000);
100 | var b = new string('b', 1000000);
101 |
102 | var actrual = Utf8String.Join(",", new string[] { a, b });
103 | var expected = Shims.Join(',', new string[] { a, b });
104 | actrual.Should().Be(expected);
105 | }
106 |
107 | [Fact]
108 | public void JoinStrings()
109 | {
110 | var values = new[] { "abc", null, "def" };
111 | {
112 | var sep = ",";
113 | var expected = string.Join(sep, values);
114 | //Utf8String.Join(sep, new ReadOnlySpan(values)).Should().Be(expected);
115 | Utf8String.Join(sep, values).Should().Be(expected);
116 | //Utf8String.Join(sep, values.ToList()).Should().Be(expected);
117 | //Utf8String.Join(sep, values.AsEnumerable()).Should().Be(expected);
118 | }
119 |
120 | {
121 | const string sep = "_,_";
122 | var expected = string.Join(sep, values);
123 | //Utf8String.Join(sep, new ReadOnlySpan(values)).Should().Be(expected);
124 | Utf8String.Join(sep, values).Should().Be(expected);
125 | //Utf8String.Join(sep, values.ToList()).Should().Be(expected);
126 | //Utf8String.Join(sep, values.AsEnumerable()).Should().Be(expected);
127 | }
128 | }
129 |
130 | [Fact]
131 | public void ConcatStrings()
132 | {
133 | var values = new[] { "abc", null, "def" };
134 |
135 | var expected = string.Concat(values);
136 | //Utf8String.Concat(new ReadOnlySpan(values)).Should().Be(expected);
137 | Utf8String.Concat(values).Should().Be(expected);
138 | //Utf8String.Concat(values.ToList()).Should().Be(expected);
139 | //Utf8String.Concat(values.AsEnumerable()).Should().Be(expected);
140 | }
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/MathF.cs:
--------------------------------------------------------------------------------
1 | #if NETFRAMEWORK
2 |
3 | using System;
4 |
5 | namespace Utf8StringInterpolation.Tests
6 | {
7 | internal static class MathF
8 | {
9 | public const float PI = 3.14159265f;
10 | }
11 | }
12 |
13 | #endif
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/Primitives.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Buffers;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using Xunit;
7 |
8 | namespace Utf8StringInterpolation.Tests
9 | {
10 | public enum MoreMyEnum
11 | {
12 | Fruit, Apple, Orange
13 | }
14 |
15 | public class Primitives
16 | {
17 | [Theory]
18 | [InlineData(int.MinValue, int.MinValue)]
19 | [InlineData(0, -1)]
20 | [InlineData(-1, 1)]
21 | [InlineData(-12, 12)]
22 | [InlineData(-123, 123)]
23 | [InlineData(-1234, 1234)]
24 | [InlineData(-12345, 12345)]
25 | [InlineData(-123456, 123456)]
26 | [InlineData(-1234567, 1234567)]
27 | [InlineData(-12345678, 12345678)]
28 | [InlineData(-123456789, 123456789)]
29 | [InlineData(-1234567890, 1234567890)]
30 | public void Integer(int x, int y)
31 | {
32 | using (var buffer = Utf8String.CreateWriter(out var sb1))
33 | {
34 | var sb5 = new StringBuilder();
35 | sb1.AppendFormatted(x); sb1.AppendFormatted(y);
36 | sb5.Append(x); sb5.Append(y);
37 |
38 | sb1.Flush();
39 | buffer.ToString().Should().Be(sb5.ToString());
40 | }
41 | }
42 |
43 | [Theory]
44 | [InlineData(ulong.MinValue, ulong.MinValue)]
45 | [InlineData(0UL, 1UL)]
46 | [InlineData(1UL, 1UL)]
47 | [InlineData(12UL, 12UL)]
48 | [InlineData(123UL, 123UL)]
49 | [InlineData(1234UL, 1234UL)]
50 | [InlineData(12345UL, 12345UL)]
51 | [InlineData(123456UL, 123456UL)]
52 | [InlineData(1234567UL, 1234567UL)]
53 | [InlineData(12345678UL, 12345678UL)]
54 | [InlineData(123456789UL, 123456789UL)]
55 | [InlineData(1234567890UL, 1234567890UL)]
56 | [InlineData(12345678901UL, 12345678901UL)]
57 | [InlineData(123456789012UL, 123456789012UL)]
58 | public void UInt64(ulong x, ulong y)
59 | {
60 | using (var buffer = Utf8String.CreateWriter(out var sb1))
61 | {
62 | var sb5 = new StringBuilder();
63 | sb1.AppendFormatted(x); sb1.AppendFormatted(y);
64 | sb5.Append(x); sb5.Append(y);
65 |
66 | sb1.Flush();
67 | buffer.ToString().Should().Be(sb5.ToString());
68 | }
69 | }
70 |
71 | [Theory]
72 | //[InlineData(double.MinValue, double.MinValue)]
73 | //[InlineData(double.Epsilon, double.NaN)]
74 | [InlineData(0.1, -0.1)]
75 | [InlineData(0.0, 0.0)]
76 | [InlineData(0.12, 0.12)]
77 | [InlineData(0.123, 0.123)]
78 | [InlineData(0.1234, 0.1234)]
79 | [InlineData(0.12345, 0.12345)]
80 | [InlineData(1.12345, 1.12345)]
81 | [InlineData(12.12345, 12.12345)]
82 | [InlineData(123.12345, 123.12345)]
83 | [InlineData(1234.12345, 1234.12345)]
84 | [InlineData(12345.12345, 12345.12345)]
85 | [InlineData(1234512345d, 1234512345d)]
86 | public void Double(double x, double y)
87 | {
88 | using (var buffer = Utf8String.CreateWriter(out var sb1))
89 | {
90 | var sb5 = new StringBuilder();
91 | sb1.AppendFormatted(x); sb1.AppendFormatted(y);
92 | sb5.Append(x); sb5.Append(y);
93 |
94 | sb1.Flush();
95 | buffer.ToString().Should().Be(sb5.ToString());
96 | }
97 | }
98 |
99 | [Theory]
100 | //[InlineData(double.MinValue, double.MinValue)]
101 | //[InlineData(double.Epsilon, double.NaN)]
102 | [InlineData(0.1f, -0.1f)]
103 | [InlineData(0.0f, 0.0f)]
104 | [InlineData(0.12f, 0.12f)]
105 | [InlineData(0.123f, 0.123f)]
106 | [InlineData(0.1234f, 0.1234f)]
107 | [InlineData(0.12345f, 0.12345f)]
108 | [InlineData(1.12345f, 1.12345f)]
109 | [InlineData(12.12345f, 12.12345f)]
110 | [InlineData(123.123f, 123.123f)]
111 | public void Single(float x, float y)
112 | {
113 | using (var buffer = Utf8String.CreateWriter(out var sb1))
114 | {
115 | var sb5 = new StringBuilder();
116 | sb1.AppendFormatted(x); sb1.AppendFormatted(y);
117 | sb5.Append(x); sb5.Append(y);
118 |
119 | sb1.Flush();
120 | buffer.ToString().Should().Be(sb5.ToString());
121 | }
122 | }
123 |
124 | [Fact]
125 | public void Others()
126 | {
127 | using (var buffer = Utf8String.CreateWriter(out var sb1))
128 | {
129 | var x = DateTime.Now;
130 | var y = DateTimeOffset.Now;
131 | var z = TimeSpan.FromMilliseconds(12345.6789);
132 | var g = Guid.NewGuid();
133 |
134 | var sb5 = new StringBuilder();
135 | sb1.AppendFormatted(x); sb1.AppendFormatted(y); sb1.AppendFormatted(z); sb1.AppendFormatted(g);
136 | sb5.Append(x); sb5.Append(y); sb5.Append(z); sb5.Append(g);
137 |
138 | sb1.Flush();
139 | buffer.ToString().Should().Be(sb5.ToString());
140 | }
141 | }
142 |
143 | [Fact]
144 | public void EnumTest()
145 | {
146 | var x = MoreMyEnum.Apple;
147 | var y = MoreMyEnum.Orange;
148 |
149 | using (var buffer = Utf8String.CreateWriter(out var sb1))
150 | {
151 | var sb5 = new StringBuilder();
152 | sb1.AppendFormatted(x); sb1.AppendFormatted(y);
153 | sb5.Append(x); sb5.Append(y);
154 |
155 | sb1.Flush();
156 | buffer.ToString().Should().Be(sb5.ToString());
157 | }
158 | }
159 |
160 | [Theory]
161 | [InlineData(true)]
162 | [InlineData(false)]
163 | public void BoolTest(bool x)
164 | {
165 | using (var buffer = Utf8String.CreateWriter(out var sb1))
166 | {
167 | var sb5 = new StringBuilder();
168 | sb1.AppendFormatted(x);
169 | sb5.Append(x);
170 |
171 | sb1.Flush();
172 | buffer.ToString().Should().Be(sb5.ToString());
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/Shims.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Utf8StringInterpolation.Tests
4 | {
5 | internal static class Shims
6 | {
7 | #if NETFRAMEWORK
8 | public static string Join(char separator, IEnumerable values) => string.Join(separator.ToString(), values);
9 | #else
10 | public static string Join(char separator, IEnumerable values) => string.Join(separator, values);
11 | #endif
12 | }
13 | }
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/Utf8StringBuilderTest.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Buffers;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using Xunit;
7 |
8 | namespace Utf8StringInterpolation.Tests
9 | {
10 | public class Utf8StringBuilderTest
11 | {
12 |
13 | [Fact]
14 | public void AppendCharRepeat()
15 | {
16 | using (var buffer = Utf8String.CreateWriter(out var zsb))
17 | {
18 | var text = "foo";
19 | zsb.AppendLiteral(text);
20 | var bcl = new StringBuilder(text);
21 |
22 | // ASCII
23 | zsb.Append('\x7F', 10);
24 | bcl.Append('\x7F', 10);
25 | zsb.Flush();
26 | buffer.ToString().Should().Be(bcl.ToString());
27 |
28 | // Non-ASCII
29 | zsb.Append('\x80', 10);
30 | bcl.Append('\x80', 10);
31 | zsb.Flush();
32 | buffer.ToString().Should().Be(bcl.ToString());
33 |
34 | zsb.Append('\u9bd6', 10);
35 | bcl.Append('\u9bd6', 10);
36 | zsb.Flush();
37 | buffer.ToString().Should().Be(bcl.ToString());
38 | }
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/Utf8StringInterpolation.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net48
5 | $(TargetFrameworks);net6.0;net8.0
6 | enable
7 | enable
8 | 12.0
9 | false
10 | true
11 |
12 |
13 | $(NoWarn);CS1591;CS8604;CS8321;CS0414;CS0169;CS0649
14 |
15 |
16 |
17 | $(NoWarn);CS1591;CS8604;CS8321;CS0414;CS0169;CS0649
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 | all
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/Utf8StringTest.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Utf8StringInterpolation.Tests
9 | {
10 | public class Utf8StringTest
11 | {
12 | [Fact]
13 | public void TryFormat()
14 | {
15 | Span bytes = stackalloc byte[10];
16 | Utf8String.TryFormat(bytes, out var written, $"aaaa{10}").Should().BeTrue();
17 | bytes.Slice(0, written).ToArray().Should().Be("aaaa10");
18 | Utf8String.TryFormat(bytes, out _, $"aaaa{10}bbbb{4}ccccc{5}").Should().BeFalse();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Utf8StringInterpolation.Tests/_ShouldExtensions.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using FluentAssertions.Primitives;
3 | using System.Text;
4 |
5 | public static class Extensions
6 | {
7 | public static StringAssertions Should(this byte[] xs)
8 | {
9 | return Encoding.UTF8.GetString(xs).Should();
10 | }
11 | }
--------------------------------------------------------------------------------