Skip to content

ReadOnlySpan<char> overload of Uri.UnescapeDataString #54828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SteveSandersonMS opened this issue Jun 28, 2021 · 12 comments
Closed

ReadOnlySpan<char> overload of Uri.UnescapeDataString #54828

SteveSandersonMS opened this issue Jun 28, 2021 · 12 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net
Milestone

Comments

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Jun 28, 2021

Background and Motivation

For context, this emerged in ASP.NET Core at dotnet/aspnetcore#33840 (comment)

Currently, Uri.UnescapeDataString both accepts and returns string. If the source data you're working with is a ReadOnlySpan<char>, then the caller is forced to allocate a string. This is especially inefficient if the caller only needs back a ReadOnlySpan<char>, since in most cases this seems like it should be achievable without any allocations (because the original argument could be returned directly if no transformation was required - see below).

Proposed API

namespace System
{
     public class Uri
     {
+        ReadOnlySpan<char> UnescapeDataString(ReadOnlySpan<char> stringToUnescape) { ... }
     }
}

Of course, if the input data contains escaped characters, then the implementation will need to allocate in order to provide a return value. However the existing logic within string UnescapeDataString(string) recognizes the extremely common case where the input string does not contain escaped characters and knows to return the original string directly. Similarly, the new ReadOnlySpan<char> overload could return the original argument value in this case.

Usage Examples

var unescapedValue = Uri.UnescapeDataString(someCharSpanFromAUri);
@SteveSandersonMS SteveSandersonMS added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 28, 2021
@ghost
Copy link

ghost commented Jun 28, 2021

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jun 28, 2021
@SteveSandersonMS SteveSandersonMS changed the title ReadOnlySpan<char> overload of UnescapeDataString ReadOnlySpan<char> overload of Uri.UnescapeDataString Jun 28, 2021
@ghost
Copy link

ghost commented Jun 28, 2021

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

For context, this emerged in ASP.NET Core at dotnet/aspnetcore#33840 (comment)

Currently, Uri.UnescapeDataString both accepts and returns string. If the source data you're working with is a ReadOnlySpan<char>, then the caller is forced to allocate a string. This is especially inefficient if the caller only needs back a ReadOnlySpan<char>, since in most cases this seems like it should be achievable without any allocations (because the original argument could be returned directly if no transformation was required - see below).

Proposed API

namespace System
{
     public class Uri
     {
+        ReadOnlySpan<char> UnescapeDataString(ReadOnlySpan<char> stringToUnescape) { ... }
     }
}

Of course, if the input data contains escaped characters, then the implementation will need to allocate in order to provide a return value. However the existing logic within string UnescapeDataString(string) recognizes the extremely common case where the input string does not contain escaped characters and knows to return the original string directly. Similarly, the new ReadOnlySpan<char> overload could return the original argument value in this case.

Usage Examples

var unescapedValue = Uri.UnescapeDataString(someCharSpanFromAUri);
Author: SteveSandersonMS
Assignees: -
Labels:

api-suggestion, area-System.Runtime, untriaged

Milestone: -

@GrabYourPitchforks
Copy link
Member

We try to avoid APIs which allocate then return the allocated memory as a ROS<T> rather than as a concrete type, even if there's only a slight chance of it doing so. Most APIs that return values try to return them as concrete types since these are generally more usable.

One way to address this could be to return a string? instead of a ROS<char>, with a null return value meaning "the original data did not require transformation." It's a little bit weirder of an API shape since the input and output types are different, but it does provide fuller control for the caller. Alternatively, you could have the caller specify both an input and an output buffer, which is more common for transformation-style APIs.

@SteveSandersonMS
Copy link
Member Author

Thanks @GrabYourPitchforks. I wasn't aware of the convention to avoid allocating and returning ROS<T> but I see how that does work against this suggestion.

Alternatively, you could have the caller specify both an input and an output buffer, which is more common for transformation-style APIs.

TBH I don't think the caller would want to supply a buffer given that it's hard to predict the correct size (without an understanding of the decoding rules), and because it leads the caller into a complex world of needing to pool buffers etc.

One way to address this could be to return a string? instead of a ROS, with a null return value meaning "the original data did not require transformation."

It's slightly unusual, but I agree that does match the scenario. Perhaps the API would be something like:

bool DecodeIfRequired(ReadOnlySpan<char> value, [MaybeNullWhen(false)] out string result)

We're not blocked on this for 6.0 (at least, we're not in any way counting on this happening for 6.0!) but is potentially an opportunity for the future.

@Wraith2
Copy link
Contributor

Wraith2 commented Jun 28, 2021

bool DecodeIfRequired(ReadOnlySpan<char> value, [MaybeNullWhen(false)] out string result)

wouldn't that more naturally fit with the try pattern?

bool TryDecode(ReadOnlySpan<char> value, [MaybeNullWhen(false)] out string result)

or even/also

(bool Success, string DecodedValue) TryDecode(ReadOnlySpan<char> value)

so usage would become:

public void DoSomethingWithUri(ReadOnlySpan<char> urlChars)
{
    if (Uri.TryDecode(uriChars) is { Success: true, DecodedValue: var decoded })
    {
        uriChars = decoded;
    }
    // carry on and use uriChars
}

At a later date you could add an overload which has a Span<char> target parameter so you can attempt to preallocate/share buffers for repeated calls and avoid allocations, 'cause you know @davidfowl is going to be all over it if you allocate anywhere in asp.net

@Sergio0694
Copy link
Contributor

Sergio0694 commented Jun 29, 2021

I'll admit I'm not the biggest far of value-tuples in public APIs in general, but still I'm not sure I get how would:

if (Uri.TryDecode(uriChars) is { Success: true, DecodedValue: var decoded })
{
}

be in any way better than:

if (Uri.TryDecode(uriChars, out var decoded)
{
}

Isn't it just more verbose and awkward compared to the usual TryFoo pattern? 🤔
The value-tuple API would also additionally lack the nullable flow analysis provided by [MaybeNullWhen].

@Wraith2
Copy link
Contributor

Wraith2 commented Jun 29, 2021

Isn't it just more verbose and awkward compared to the usual TryFoo pattern?

I think so, but I also prefer the purity of separate input/output that using a value tuple allows. If they're going to be added I think the out param one is most likely with the valuetuple one a possibility but I wanted to make sure it was there for consideration.

@tannergooding tannergooding removed the untriaged New issue has not been triaged by the area owner label Jul 12, 2021
@tannergooding tannergooding added this to the Future milestone Jul 12, 2021
@tannergooding
Copy link
Member

CC. @karelz, this is another API proposal that impacts Uri. It's likely better handled by the networking team where the domain expertise resides.

@ghost
Copy link

ghost commented Sep 8, 2022

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

For context, this emerged in ASP.NET Core at dotnet/aspnetcore#33840 (comment)

Currently, Uri.UnescapeDataString both accepts and returns string. If the source data you're working with is a ReadOnlySpan<char>, then the caller is forced to allocate a string. This is especially inefficient if the caller only needs back a ReadOnlySpan<char>, since in most cases this seems like it should be achievable without any allocations (because the original argument could be returned directly if no transformation was required - see below).

Proposed API

namespace System
{
     public class Uri
     {
+        ReadOnlySpan<char> UnescapeDataString(ReadOnlySpan<char> stringToUnescape) { ... }
     }
}

Of course, if the input data contains escaped characters, then the implementation will need to allocate in order to provide a return value. However the existing logic within string UnescapeDataString(string) recognizes the extremely common case where the input string does not contain escaped characters and knows to return the original string directly. Similarly, the new ReadOnlySpan<char> overload could return the original argument value in this case.

Usage Examples

var unescapedValue = Uri.UnescapeDataString(someCharSpanFromAUri);
Author: SteveSandersonMS
Assignees: -
Labels:

api-suggestion, area-System.Net

Milestone: Future

@karelz
Copy link
Member

karelz commented Sep 8, 2022

Looks like a need for span overload -- @MihaZupan any thoughts?

@karelz
Copy link
Member

karelz commented Sep 8, 2022

@tannergooding System.Net is the area for System.Uri -- it is called out in area-owners - feel free to move it area-System.Net in future. Thanks!

@karelz karelz added the untriaged New issue has not been triaged by the area owner label Sep 8, 2022
@karelz karelz removed this from the Future milestone Sep 8, 2022
@MihaZupan
Copy link
Member

Duplicate of #40603

@MihaZupan MihaZupan marked this as a duplicate of #40603 Sep 13, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Sep 13, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Oct 13, 2022
@karelz karelz added this to the 8.0.0 milestone Mar 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Net
Projects
None yet
Development

No branches or pull requests

8 participants