Package Description
$ dotnet add package StringWeaverThe StringWeaver package exposes a custom high-performance builder for strings with a mutable, directly accessible buffer and a versatile API for manipulating the contents.
The assembly multi-targets netstandard2.0 and several newer .NETs to support performance-focused APIs introduced in later versions.
netstandard2.0 compilation, meaning any conforming project platform can use it.PCRE.NET is introduced to facilitate all regex operations on StringWeaver to meet performance goals/allocation minimums for < net7.0.ISpanFormattable on >= net6.0.>= net7.0, for the Replace* methods that take a PcreRegex, analogous methods that take Regex instance and utilize the Span-based APIs introduced in System.Text.RegularExpressions are also exposed.net8.0 introduced many Span-based APIs throughout the entire .NET ecosystem, allowing streamlined code paths on >= net8.0.Unfortunately, neither PCRE.NET nor System.Text.RegularExpressions expose allocating APIs that would easily allow producing match details object instances using a Span input. This also means that typical (allocating) replacement operations that allow dynamically producing replacement content using a MatchEvaluator cannot be implemented.
Namespace: global::StringWeaver
StringWeaver: Default and most versatile implementation. Offers the full API surface and sources its backing buffers by new'ing and resizing char[]s on demand.
StringWeaver should be the first choice for the ordinary consumer. Where profiling shows that a different implementation would be more beneficial, consider the alternatives described below.UnsafeStringWeaver: Implements a StringWeaver that sources its backing buffers from unmanaged instead of managed memory. This has the benefit of not causing any GC pressure while exposing exactly the same APIs as the default StringWeaver.
StringWeavers with capacities near int.MaxValue or instances which are very long-lived.UnsafeStringWeaver implements IDisposable (StringWeaver does not). Not disposing of instances of UnsafeStringWeaver is a memory leak.PooledStringWeaver: Implements a StringWeaver that sources its backing buffers from ArrayPool<char>.Shared (or your own passed implementation of ArrayPool<char>). This allows keeping GC pressure low while still using managed memory.
StringWeavers with moderate capacities (e.g. a few dozen kB or more).PooledStringWeaver implements IDisposable (StringWeaver does not). Not disposing of instances of PooledStringWeaver causes the buffer backing it to be lost to the pool, which is a memory leak and degrades performance of every other user of that ArrayPool<char> in your application.StringWeaver itself is not sealed because UnsafeStringWeaver inherits from it. As such, inheritance from StringWeaver is allowed for external types as well. There are only a select few members you must override to make your implementation function correctly, everything else (functionality-wise) is handled for you (and not virtual, for that matter):
Memory<char> FullMemory: Gets a Memory<char> instance over the entire backing storage (NOT just the used portion).void Grow(int requiredCapacity): WITHOUT checking whether it is required, expands the backing storage to at least the specified capacity (those checks are done for you before this virtual method is ever called). It is recommended you decorate your override with [MethodImpl(MethodImplOptions.NoInlining)].StringWeaver.ToString (the base version) is sealed override.IBufferWriter<char> implementations and Stream support are both handled internally as well. Do not re-implement either of those.Adding functionality on top of anything already handled for you is straightforward. All the base functionality can be used to compose your own methods or just use (Full)Memory/(Full)Span to access the backing storage directly. The Length property has an accessible setter that controls which portion of the buffer is considered "used".
(!) The above statement is true for disposal. As mentioned, StringWeaver does not implement IDisposable, so derived types must do so themselves if they require it. Ensure cleanup for your own types is sound.
The static class StringWeaverConfiguration exposes global configuration options for all StringWeaver variants where applicable. The options are usually strongly-typed and implemented in a thread-safe manner, however, altering options while the affected variants are in use will very likely lead to undefined behavior. I recommend very carefully thinking about what you're changing, why you're doing it and if it's really a good idea, then setting them on application startup and never touching the entire class again.
Opening issues and submitting PRs are welcome. All changes must be appropriately covered by tests. Tests run exclusively under net9.0.
Support for netstandard2.0 must always be maintained. If possible, new functionality should be added to all target frameworks. New dependencies may be introduced after I vet the decision to do so.
Or get in touch on Discord @eyeoftheenemy