ZString

repository·master·Indexed 25 days ago

https://github.com/cysharp/zstring

A high-performance, zero-allocation StringBuilder library for .NET Core and Unity (version 2.6.0). It minimizes heap allocations and avoids boxing of value types using struct-based builders, buffer pooling via ThreadStatic or ArrayPool, and generic append methods. Features include Utf16ValueStringBuilder, Utf8ValueStringBuilder, and specialized support for Unity TextMeshPro to achieve completely zero-allocation output.

Tokens
2.1K
Snippets
5
Records
9
Agent score
34%

What's inside ZString

  1. Overview of ZString

    master

    ZString is a zero-allocation StringBuilder designed for .NET Core and Unity. It aims to replace standard string operations to minimize memory allocations and avoid boxing of value types.

    Key features include:

    • Struct-based StringBuilder: Avoids allocating the builder object itself.
    • Buffer Renting: Uses ThreadStatic or ArrayPool for write buffers.
    • Generic Append Methods: Append<T>(T value) writes directly to the buffer, avoiding value.ToString() allocations.
    • Boxing-free Formatting: AppendFormat<T1...T16> and Concat<T1...T16> avoid boxing struct arguments and ToString allocations.
    • Direct Buffer Access: Supports building both UTF-16 (Span<char>) and UTF-8 (Span<byte>) directly.
    • Zero-Allocation Output: Can use the inner buffer to write directly to stringless APIs (e.g., Unity TextMeshPro's SetCharArray), achieving completely zero allocation.
  2. Use ZString for high-performance string operations

    master

    ZString provides allocation-free alternatives to standard string operations like concatenation, formatting, and joining.

    Common Operations

    • Concatenation: Use ZString.Concat(args) to join multiple values.
    • Formatting: Use ZString.Format(format, args) for standard string formatting.
    • Joining: Use ZString.Join(separator, elements) to join arrays or collections.
    • Prepared Formatting: For repeated operations, use ZString.PrepareUtf16<T1...>(format) to pre-parse the template, then call .Format() on the result to avoid re-parsing the template every time.
  3. Handle mutable ValueStringBuilder structs with ref

    master

    Both Utf16ValueStringBuilder and Utf8ValueStringBuilder are mutable structs. To avoid expensive copying or incorrect behavior, pass them using the ref keyword.

    Warning: When passing a builder by ref, you cannot use a using block in the calling method; you must manually call .Dispose() in a finally block to ensure the buffer is returned to the pool.

    void Build()
    {
        var sb = ZString.CreateStringBuilder();
        try
        {
            BuildHeader(ref sb);
            BuildMessage(ref sb);
        }
        finally
        {
            // When using with `ref`, you cannot use `using`.
            sb.Dispose();
        }
    }
    
    void BuildHeader(ref Utf16ValueStringBuilder builder)
    {
        //..
    }
    
    void BuildMessage(ref Utf16ValueStringBuilder builder)
    {
        //..
    }
  4. Install ZString for .NET Core or Unity

    master

    .NET Core

    Install via NuGet Package Manager:

    PM> Install-Package ZString

    Unity

    Download the ZString.Unity.unitypackage from the releases page or install via UPM using the following Git URL:

    https://github.com/Cysharp/ZString.git?path=src/ZString.Unity/Assets/Scripts/ZString

    You can specify a version using the # suffix, for example: ...#2.4.0.

    Requirements & Notes:

    • Minimum Unity version: 2021.3.
    • If using the Git URL, you must manually add the System.Runtime.CompilerServices.Unsafe dependency (version 6.0.0 or similar) via NuGet or by extracting the DLL from the unitypackage.
    • TextMeshPro Support: Automatically enabled if com.unity.textmeshpro is installed via Package Manager. If not using the Package Manager, define the scripting define symbol ZSTRING_TEXTMESHPRO_SUPPORT.
  5. Use Utf16ValueStringBuilder

    master

    The Utf16ValueStringBuilder is a high-performance, disposable builder for UTF-16 strings. It rents a 64K buffer from ArrayPool, so it must be used within a using block or manually disposed.

    Key Methods

    • Append<T>(T value): Appends a value.
    • AppendFormat<T1,..,T16>(string, T1,..,T16): Appends a formatted string.
    • AppendLine<T>(T value): Appends a value followed by a line terminator.
    • TryCopyTo(Span<char>, out int): Copies the buffer to a destination span.
    • ToString(): Converts the builder content to a standard System.String.
    using(var sb = ZString.CreateStringBuilder())
    {
        sb.Append("foo");
        sb.AppendLine(42);
        sb.AppendFormat("{0} {1:.###}", "bar", 123.456789);
    
        var str = sb.ToString();
        sb.TryCopyTo(dest, out var written);
    }
  6. Use ZString with Unity TextMeshPro

    master

    ZString provides extensions for Unity's TextMeshPro to allow direct writing from builders to UI components, completely avoiding string allocations.

    Methods

    • SetText(Utf16ValueStringBuilder): Sets the text directly from a builder.
    • SetTextFormat<T1,..,T16>(string, T1,..,T16): Sets a formatted string without allocation.
  7. Register custom formatters for ZString

    master

    By default, ZString uses optimized formatters for common types (e.g., int, double, Guid). For custom types, you can register a custom formatter to avoid .ToString() allocations.

    Use RegisterTryFormat on the respective builder type.

    Utf16ValueStringBuilder.RegisterTryFormat((MyStruct value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format) =>
    {
        // write value to destination and set charsWritten
        charsWritten = 0;
        return true;
    });
    
    Utf8ValueStringBuilder.RegisterTryFormat((MyStruct value, Span<byte> destination, out int written, StandardFormat format) =>
    {
        written = 0;
        return true;
    });
  8. Use Utf8ValueStringBuilder

    master

    The Utf8ValueStringBuilder is a high-performance, disposable builder for UTF-8 data (Span<byte>). It is ideal for writing directly to streams or network buffers.

    Key Methods

    • Append<T>(T value): Appends a value.
    • AppendFormat<T1,..,T16>(string, T1,..,T16): Appends a formatted string.
    • WriteToAsync(Stream stream): Writes the buffer directly to a stream asynchronously.
    • TryCopyTo(Span<byte>, out int): Copies the buffer to a destination span.
    • ToString(): Encodes the UTF-8 buffer into a System.String (note: this involves an allocation/conversion).

    Note on Formatting: UTF-8 formatting uses StandardFormat (similar to Utf8Formatter.TryFormat) rather than standard C# format strings. Supported symbols include G, D, N, X for integers, etc.

    using var sb2 = ZString.CreateUtf8StringBuilder();
    
    sb2.AppendFormat("foo:{0} bar:{1}", x, y);
    
    // Write directly to stream or destination to avoid allocation
    await sb2.WriteToAsync(stream);
    sb2.CopyTo(bufferWriter);
    sb2.TryCopyTo(dest, out var written);
  9. Optimize with ThreadStatic buffers

    master

    You can use ZString.CreateStringBuilder(notNested: true) or ZString.CreateUtf8StringBuilder(notNested: true) to use a ThreadStatic buffer instead of renting from ArrayPool. This is faster but comes with strict usage constraints:

    1. No Nesting: You cannot use a notNested: true builder inside another notNested: true builder or inside a ZString.Concat/Join/Format call, as they share the same thread-static buffer.
    2. Immediate Return: The buffer must be returned/disposed immediately (e.g., within the same method scope).
    // OK: return buffer immediately
    using(var sb = ZString.CreateStringBuilder(true))
    {
        sb.Append("foo");
        return sb.ToString();
    }
    
    // NG: nested stringbuilder uses conflicted same buffer
    using(var sb = ZString.CreateStringBuilder(true))
    {
        using var sb2 = ZString.CreateStringBuilder(true); 
        var str = ZString.Concat("x", 100); 
    }