Skip to content

[Proposal] Add new overload to Uri.EscapeDataString #40603

Closed
@stevejgordon

Description

@stevejgordon

Updated by @MihaZupan on 2024-01-15.

Background and Motivation

In low allocation scenarios, it may be useful to support escaping URI data into an existing Span<char> buffer, rather than allocating a new string.

As an example, see this performance improvement suggestion for Elasticsearch.NET. The proposed change removes allocations on a hot path for query string building. It uses the existing Uri.EscapeDataString method which allocates a new string, only when the input contains characters that need encoding. This could be avoided if I were able to encode directly into the buffer rented from the ArrayPool.

Proposed API

namespace System
{
    partial class Uri
    {
        // Existing
        public static string EscapeDataString(string stringToEscape);
        public static string UnescapeDataString(string stringToUnescape);

        // New
        public static string EscapeDataString(ReadOnlySpan<char> charsToEscape);
        public static bool TryEscapeDataString(ReadOnlySpan<char> charsToEscape, Span<char> destination, out int charsWritten);

        public static string UnescapeDataString(ReadOnlySpan<char> charsToUnescape);
        public static bool TryUnescapeDataString(ReadOnlySpan<char> charsToUnescape, Span<char> destination, out int charsWritten);
    }
}

Usage Examples

The foreach loop from the linked issue could then be re-written along the lines of:

foreach (var key in nv.AllKeys)
{
    if (position != 1)
        bufferSpan[position++] = '&';

    // assume we are sure we have space in the destination
    if (Uri.TryEscapeDataString(key, bufferSpan.Slice(position), out var charsWritten))
        position += charsWritten;

    var value = nv[key];
    if (!value.IsNullOrEmpty())
    {
        bufferSpan[position++] = '=';
        if (Uri.TryEscapeDataString(value, bufferSpan.Slice(position), out charsWritten))
            position += charsWritten;
    }
}

In cases where the keys/values contain reserved characters, this avoids the temporary string allocations.

Alternative Designs

  1. The existing method names contain string.
    Is it okay for the new span-based overloads to share the same name for better discoverability, or should we consider alternative names, e.g. EscapeData / EscapeDataChars?
  2. The above design follows other Span<T> based overloads I have seen in common use and assumes that the operation could fail if the destination is too small. An alternative would be to throw in that case and instead apply the following signature (where the return value indicates the chars written):
    public static int EscapeDataString(ReadOnlySpan<char> stringToEscape, Span<char> destination);

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions