Working with fixed strings
editThe built-in types and the types you generate have the same members.
Capacity
MaxLength is the capacity of the type, and Length the number of characters of the value. An operation that would exceed the capacity throws an ArgumentException, whether it is a conversion from a string, an interpolated string or AppendLiteral:
FixedStringBuilder8 code = "ABC";
Assert.Equal(3, code.Length);
Assert.Equal(8, FixedStringBuilder8.MaxLength);
// 9 characters do not fit in a FixedStringBuilder8
Assert.Throws<ArgumentException>(() => new FixedStringBuilder8("ABCDEFGHI"));Format values
Interpolated strings support format strings and alignment. A positive alignment pads on the left, a negative one on the right:
var quantity = 42;
FixedStringBuilder32 line = $"[{quantity:D5}] [{"A",3}] [{"B",-3}]";
Assert.Equal("[00042] [ A] [B ]", line.ToString());A value that implements ISpanFormattable, such as a number or a date, is formatted directly into the remaining characters, without an intermediate string. The values are formatted with the current culture: there is no parameter to pass an IFormatProvider, so format culture-sensitive values (dates, decimal numbers) yourself when the result must not depend on the culture.
Compare
==, != and Equals(T) compare the characters with an ordinal comparison, and GetHashCode() is consistent with them. Equals(T, StringComparison) uses the comparison you choose:
FixedStringBuilder16 first = "Hello";
FixedStringBuilder16 second = "HELLO";
Assert.False(first == second);
Assert.True(first.Equals(second, StringComparison.OrdinalIgnoreCase));
Assert.True(first == "Hello"); // The string is converted to a FixedStringBuilder16Read the characters
AsSpan() returns the characters of the value as a ReadOnlySpan<char>, without copying them:
FixedStringBuilder16 value = "Hello World";
ReadOnlySpan<char> span = value.AsSpan();
Assert.HasCount(11, span);
Assert.True(span.StartsWith("Hello", StringComparison.Ordinal));The span points into the struct, so the compiler prevents it from outliving the variable. The types also implement ISpanFormattable: TryFormat copies the characters into a span you provide, and a fixed string inside another interpolated string is copied without creating a string.
Clear
Clear() resets the length to 0. The characters stay in the struct, where IFixedString.GetUnsafeFullSpan() can still read them. For sensitive data, use Clear(zeroBuffer: true), which also overwrites every character with '\0':
FixedStringBuilder16 sb = $"secret";
sb.Clear(zeroBuffer: true);
Assert.Equal(0, sb.Length);Write generic code
All the types implement IFixedString, which exposes MaxLength (a static member), Length, Clear and GetUnsafeFullSpan, and IFixedString<T>, which adds IEquatable<T> and the conversion from string. Use them as generic constraints to write code that works with any capacity:
Assert.Equal(8, GetMaxLength<FixedStringBuilder8>());
Assert.Equal(64, GetMaxLength<FixedStringBuilder64>());
var value = Create<FixedStringBuilder16>("Hello");
Assert.Equal(5, value.Length);
static int GetMaxLength<T>() where T : IFixedString => T.MaxLength;
static T Create<T>(string value) where T : IFixedString<T> => value;GetUnsafeFullSpan() returns all the characters up to MaxLength, including those after Length. It is only available through the interface, and requires an unsafe context when the code is compiled with the updated memory safety rules: the span is not tied to the struct, so the caller must not use it after the struct goes out of scope. Prefer AsSpan().
Pass fixed strings around
A fixed string is a struct that contains all its characters: assigning it or passing it by value copies them all. Pass large types by in or ref in hot paths. The members that do not change the value (Length, AsSpan, Equals, ToString, TryFormat...) are readonly, so calling them on an in parameter or a readonly field does not create a defensive copy.