crozone / FormatWith

String extensions for named parameterized string formatting.
MIT License
73 stars 13 forks source link

Span<T> goodness #8

Open crozone opened 6 years ago

crozone commented 6 years ago

Need to investigate updating the parser to return ReadOnlySpan<T> instead of the current struct. Probably won't make much of a difference to performance, since the current implementation is already using structs on the stack, but it should be good for code style points.

JeremySkinner commented 4 years ago

I've put together a PR with an initial implementation for your review (#20)

crozone commented 3 years ago

@JeremySkinner I've finally got your PR merged, apologies it took so long!

It's still a work in progress, but I've overhauled the public FormatWith API to enable ReadOnlySpan<char> based scenarios. All of the existing FormatWith overloads are also now wrapping the lower level Span based overload.

The work is happening in the release-4.0 branch.

The main Span API usage looks like this:

HandlerAction handlerAction = static (key, format, result) =>
{
    if (key.Equals("KeyNumber1".AsSpan(), StringComparison.Ordinal))
    {
        result("A Nice Value".AsSpan());
        return true;
    }
    return false;
};

FallbackAction fallbackAction = static (result) =>
{
    result("A good fallback value".AsSpan());
};

string replacement = "abc {KeyNumber1} {DoesntExist}".AsSpan().FormatWith(handlerAction, MissingKeyBehaviour.ReplaceWithFallback, fallbackAction);
Assert.Equal("abc A Nice Value A good fallback value", replacement);

Currently there aren't any more "convenient" Span overloads (that take a dictionary or object lookup, for example), but I think they'll probably be needed. Also, the string returning overloads use a cache of ThreadLocal WeakReference'd StringBuilders which should provide a balance of performance and memory consumption.

There is also a lower level overload that accepts a callback delegate to write the result, and doesn't use StringBuilder at all:

 public static void FormatWith(
            ReadOnlySpan<char> formatString,
            HandlerAction handlerAction,
            ResultAction destinationWriterAction,
            MissingKeyBehaviour missingKeyBehaviour,
            FallbackAction fallbackReplacementAction = null,
            char openBraceChar = '{',
            char closeBraceChar = '}')

Please let me know if you have any thoughts on the shape of these APIs, I'd love to get some feedback on their practicality and usability.