-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Reevaluate params ReadOnlySpan<string>
overloads from #77873
#101261
Comments
Tagging subscribers to this area: @dotnet/area-system-memory |
params ReadOnlySpan<string>
overloads from https://github.com/dotnet/runtime/issues/77873params ReadOnlySpan<string>
overloads from #77873
It seems like when overload resolution is comparing a conversion from However once an operator I'm not sure what change is most appropriate to fix the case in this issue, though. I will say that choosing |
I would expect conversion of |
cc @dotnet/fxdc |
It looks like "overload resolution priority" was designed to address this specific kind of problem. dotnet/csharplang#7906 |
Isn't this was @333fred had a proposal for? |
What is the basis for these expectations? Usually, expectations should be based on something. |
|
Based on what? I would expect exactly the behavior that we have, and that expectation is based on language rules, not on feelings. From your perspective span types are unconditionally "better" than array types, but compiler cannot get into one's head and read one's mind/feelings. There is a rational behind the language rules. To simplify, language prefers a candidate that takes a "narrower" type for a given argument. For example, an I guess, the point that I am trying to make. There is nothing wrong to have a desire that compiler behaves a certain way. But the desire itself is not a good basis (I would even say it is not a basis at all) to have an expectation that compiler is supposed to behave this way. Basically, there is an analogy between a contract for an API and language rules. The contract defines the expectations, not the other way around. It is, obviously, fine to ask to adjust the language rules to align expected behavior of the compiler with the desired outcome. But, in my opinion, that is quite different from simply expecting things to work a certain way just because there is a desire for that at given moment in time. |
@AlekseyTs the ldm has expressed many times the desire to have the Span based forms "win". So far we've been accomplishing that by patching certain sections of the spec to make that happen. Fred also has a more generalized proposal to nip things in the bud and try to take care of it more uniformly. It seems very easy and reasonable to me that users would take away from this an intuition on how things should behave. That's how we operate as well. I agree the spec defines what will actually happen. But it is our intuitions that drive is to get the spec to the place we want it to be. |
namespace Microsoft.Extensions.Primitives
{
public readonly partial struct StringValues
{
public static implicit operator ReadOnlySpan<string>(in StringValues value);
}
} |
It looks like |
The reference needs to escape into the return value. The argument is public static implicit operator ReadOnlySpan<string>(in StringValues value) =>
value._values is string ? new ReadOnlySpan<string>(in Unsafe.As<object, string>(ref Unsafe.AsRef(in value._values))) :
new ReadOnlySpan<string>((string[])value._values); Does adding |
Oh interesting! So you want result to be able to hold a reference to the struct field in the single item case. I didn't understand this aspect of the internal layout of StringValues. Carry on :)
I would lean toward using it when it is meaningful to do so (when the method has references both as inputs and outputs.) Use it unless you either want to return a reference to an input, or want to reserve the ability to do so in the future. |
We've opted not to do that because we'd end up explicitly adding it to a boatload of APIs where it doesn't actually affect behavior. We only use it when it's not a nop. |
@MihaZupan highlighted that we can't do At that point, we're back to there being no way to make this API safe and keep it allocation-free, since the only way to make it allocation-free for a single string is to take a reference to the original location, which we can't safely do. |
This comment was marked as resolved.
This comment was marked as resolved.
It can't be non-allocating on .NET Core, either. |
Sorry, I missed that. Yeah, that example worries me so much because I have done span-returning methods pointing to readonly structs. Somehow I assumed if it's a readonly struct it's ok. Damn, thanks for the example, but damn... this worries me because this is something you could do with completely safe code. Well, except for the cast. But you'd still be able to overwrite the value of the string by reassigning the struct. EDIT: Although... I guess then that is kind of expected because readonly span only means you can't change the thing it's pointing to, not that it cannot be changed. Hmmm, we need an But I assumed the struct stored the fields separately for some reason (I guess because that's how I would have done it). |
This comment was marked as resolved.
This comment was marked as resolved.
I think that would be unfortunate. There are many reasons where a method takes a span as well as an array/string, and the span overload potentially has to allocate, whether for legacy reasons (e.g. Stream, where the default implementation has to potentially allocate and make a copy and forward to the original array-taking one - and I would guess the majority of real-world stream do not override the span one from what I've seen in other people's code), and I've seen it in cases other than legacy reasons as well where a new API was added with overloads for both either string/array and a span and the span-taking one had to allocate. I think it was for The only case where preferring span overloads adds value is for params, where the array version allocates invisibly by default (but I might be missing other cases as well in the language where there are implicit allocations similar to this that would be mitigated by span). Of course, If C# didn't have invisible allocations from the start, even this wouldn't be a problem. |
Existing compiler rules make that types that define implicit operators for string and string[] incur in a source breaking change.
Example:
One real-world example is
Microsoft.Extensions.Primitives.StringValues
which is passed intostring.Join(string, string[])
on aspnetcore, and that it failed to compile when the changes in #100898 flowed into their repo.API Proposal
Not quite a new API proposal, but a proposal for keeping as is the APIs on #77873.
These APIs would make existing callsites of their analogous
params string[]
overloads to fail if aStringValues
is being passed as argument.Considering that having the ambiguity of string and string[] implicit operators feels like is against FDGs 5.7.2 Conversion Operators
I think the best path would be to accept the source breaking change and expect users to update their codebase, preferably picking the new
ReadOnlySpan<string>
overloads.Alternatively
@dotnet/roslyn could the compiler rules be adjusted to accommodate
StringValues
? Why adding apublic static implicit operator ReadonlySpan<string>
toStringValues
doesn't make the compiler selectstring.Join(string, ReadonlySpan<string>)
? It does make the error go away, though, by selecting thestring[]
overload.The text was updated successfully, but these errors were encountered: