Utf8StringInterpolation

repository·main·Indexed 42 days ago

https://github.com/cysharp/utf8stringinterpolation

A high-performance, zero-allocation library for UTF8 string interpolation and StringBuilder-like utilities in C#. It allows writing directly to byte buffers, arrays, or IBufferWriter<byte> using standard interpolation syntax. Supports .NET Standard 2.0, 2.1, .NET 6, 7, and 8+, with full IUtf8SpanFormattable support in .NET 8.

Tokens
3.7K
Snippets
15
Records
16
Agent score
3%

What's inside Utf8StringInterpolation

  1. Understand formatting behavior in Utf8StringInterpolation

    main

    Formatting in string interpolation follows standard .NET alignment and format specifiers, but the level of support depends on your target runtime:

    • .NET 8 and above: Supports all standard .NET formatting because IUtf8SpanFormattable is implemented.
    • .NET Standard 2.1, .NET 6, and .NET 7: Uses Utf8Formatter.TryFormat with StandardFormat. This is more restrictive and may not be compatible with all standard formats for numeric types. Supported types include bool, byte, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64.

    Exception: DateTime, DateTimeOffset, and TimeSpan support regular custom format specifiers across all supported platforms (including .NET Standard 2.1 and .NET 6/7) to overcome the limitations of StandardFormat.

    // .NET 8 supports all numeric custom format strings, but .NET Standard 2.1, .NET 6, and .NET 7 do not.
    Utf8String.Format($"Double value is {123.456789:.###}");
    
    // DateTime, DateTimeOffset, and TimeSpan support custom format strings on all target platforms.
    Utf8String.Format($"Today is {DateTime.Now:yyyy-MM-dd}");
  2. Formatting support in Utf8String

    main

    Utf8String supports custom format strings within interpolated strings:

    • DateTime, DateTimeOffset, TimeSpan: Supported on all target platforms.
    • Numeric types: .NET 8 supports all numeric custom format strings. Note that .NET Standard 2.1, .NET 6, and .NET 7 may have limitations compared to .NET 8.
    // Custom numeric format
    Utf8String.Format($"Double value is {123.456789:.###}");
    
    // Custom DateTime format
    Utf8String.Format($"Today is {DateTime.Now:yyyy-MM-dd}");
  3. Use Utf8String.CreateWriter for StringBuilder-like behavior

    main

    For continuous writing, use Utf8String.CreateWriter. This returns a Utf8StringWriter that behaves like a StringBuilder but manages an underlying IBufferWriter<byte>.

    Important: You must manually call .Flush() or .Dispose() to ensure the buffer is advanced and data is written. For large messages, call .Flush() periodically to stream data (e.g., to a PipeWriter) instead of holding a large internal buffer.

    var bufferWriter = new ArrayBufferWriter<byte>();
    var writer = Utf8String.CreateWriter(bufferWriter);
    
    writer.Append("My Name...");
    writer.AppendFormat($"is...? {name}");
    writer.AppendLine();
    
    writer.Flush(); // Ensure data is written to bufferWriter
  4. Use Utf8StringBuffer for pooled buffer management

    main

    If you want to use a StringBuilder-like pattern without managing your own IBufferWriter<byte>, use the CreateWriter overload that returns a Utf8StringBuffer. This uses an internally pooled buffer.

    Note: You MUST wrap the Utf8StringBuffer in a using block to release the pooled buffer.

    // Use 'using' to ensure the pooled buffer is released
    using (var buffer = Utf8String.CreateWriter(out var writer)) 
    {
        writer.Append("foo");
        writer.AppendFormat($"bar {Guid.NewGuid}");
        writer.Flush();
    
        // Access the results
        var bytes = buffer.ToArray();
        var span = buffer.WrittenSpan;
        buffer.CopyTo(otherBufferWriter);
    }
  5. Use Utf8String.Concat and Join for UTF8 sequences

    main

    Similar to String.Concat and String.Join, these methods combine values into UTF8. They provide overloads to either return a byte[] or write directly to an IBufferWriter<byte>.

    // Concat examples
    byte[] utf8seq = Utf8String.Concat("a", "b", "c");
    
    // Join examples
    var seq = Enumerable.Range(1, 10);
    byte[] utf8seq = Utf8String.Join(", ", seq);
  6. Use Utf8String.Format for high-performance UTF8 interpolation

    main

    The Utf8String.Format method allows you to generate UTF8 data using standard C# string interpolation syntax ($"...") with zero allocation. It supports writing directly to a byte[], an IBufferWriter<byte> (like ArrayBufferWriter<byte> or PipeWriter), or attempting to write to a provided Span<byte>.

    using Utf8StringInterpolation;
    
    // 1. Create a byte[] directly
    byte[] utf8 = Utf8String.Format($"Hello, {name}, Your id is {id}!");
    
    // 2. Write to IBufferWriter<byte> (e.g., ASP.NET HttpResponse.BodyWriter)
    var bufferWriter = new ArrayBufferWriter<byte>();
    Utf8String.Format(bufferWriter, $"Today is {DateTime.Now:yyyy-MM-dd}");
    
    // 3. Write to PipeWriter (e.g., FileStream)
    using var fs = File.OpenWrite("foo.txt");
    var pipeWriter = PipeWriter.Create(fs);
    Utf8String.Format(pipeWriter, $"Foo: {id,10} {name,-5}");
    
    // 4. TryFormat into a Span<byte>
    Span<byte> destination = stackalloc byte[100];
    if (Utf8String.TryFormat(destination, out int bytesWritten, ref myUtf8StringWriter)) { /* ... */ }
  7. Reference: Utf8String API Methods

    main

    The entry point for the library is the Utf8String class. The following methods are available for formatting and concatenation.

    // Format methods
    byte[] Format(ref Utf8StringWriter format);
    void Format<TBufferWriter>(TBufferWriter bufferWriter, ref Utf8StringWriter format) where TBufferWriter : IBufferWriter<byte>;
    bool TryFormat(Span<byte> destination, out int bytesWritten, ref Utf8StringWriter format);
    
    // Concat methods
    byte[] Concat(params string?[] values);
    void Concat<TBufferWriter>(TBufferWriter bufferWriter, params string?[] values) where TBufferWriter : IBufferWriter<byte>;
    byte[] Concat<T>(IEnumerable<T> values);
    void Concat<TBufferWriter, T>(TBufferWriter bufferWriter, IEnumerable<T> values) where TBufferWriter : IBufferWriter<byte>;
    
    // Join methods
    byte[] Join(string separator, params string?[] values);
    void Join<TBufferWriter>(TBufferWriter bufferWriter, string separator, params string?[] values) where TBufferWriter : IBufferWriter<byte>;
    byte[] Join<T>(string separator, IEnumerable<T> values);
    void Join<TBufferWriter, T>(TBufferWriter bufferWriter, string separator, IEnumerable<T> values) where TBufferWriter : IBufferWriter<byte>;
  8. Reference: Utf8StringWriter and Utf8StringBuffer Members

    main

    These types provide the low-level writing capabilities used by the interpolation engine and manual writers.

    // Utf8StringWriter members
    void AppendLiteral(string s);
    void AppendWhitespace(int count);
    void Append(string? s);
    void Append(char c);
    void Append(char c, int repeatCount);
    void AppendUtf8(scoped ReadOnlySpan<byte> utf8String);
    void AppendFormatted(scoped ReadOnlySpan<byte> utf8String);
    void AppendFormatted(scoped ReadOnlySpan<char> s);
    void AppendFormatted(string value, int alignment = 0, string? format = null);
    void AppendFormatted<T>(T value, int alignment = 0, string? format = null);
    void AppendFormat(ref Utf8StringWriter format); // extension
    void AppendLine(ref Utf8StringWriter format);   // extension
    void AppendLine();
    void AppendLine(string s);
    void Flush();
    void Dispose();
    
    // Utf8StringBuffer members
    int WrittenCount { get; }
    ReadOnlySpan<byte> WrittenSpan { get; }
    ReadOnlyMemory<byte> WrittenMemory { get; }
    byte[] ToArray();
    void CopyTo<TBufferWriter>(TBufferWriter bufferWriter) where TBufferWriter : IBufferWriter<byte>;
    void Dispose();
  9. Use Utf8StringWriter for StringBuilder-like operations

    main

    For complex string construction, use Utf8String.CreateWriter(IBufferWriter<byte>). This provides a Utf8StringWriter that allows you to append literals and formatted strings sequentially, similar to how StringBuilder works, but writing directly to a UTF8 buffer.

    var bufferWriter = new ArrayBufferWriter<byte>();
    var writer = Utf8String.CreateWriter(bufferWriter);
    
    writer.Append("My Name...");
    writer.AppendFormat($"is...? {name}");
    writer.AppendLine();
    writer.Flush();
  10. Use the Utf8StringWriter pattern with CreateWriter(out writer)

    main

    You can use Utf8String.CreateWriter(out var writer) which returns a disposable writer. This pattern is recommended for managing the lifecycle of the writer. Once finished, you can copy the contents to another IBufferWriter<byte>, convert it to an array, or access the WrittenSpan.

    // buffer must Dispose after used (recommend to use using)
    using var buffer = Utf8String.CreateWriter(out var writer);
    
    writer.Append("foo");
    writer.AppendFormat($"bar {Guid.NewGuid()}");
    
    // finally call Flush(or Dispose)
    writer.Flush();
    
    // copy to written data or get byte[] / ReadOnlySpan<byte>
    buffer.CopyTo(otherBufferWriter);
    var bytes = buffer.ToArray();
    var writtenData = buffer.WrittenSpan;