- "I live in a double-wide future"
Our first question for today is around type inference from spread elements contributing to inferring the element type of a collection expression. This is how the working group spec'd the feature, and how it was implemented, but we're confirming with LDM that the rules behave as we'd desire. We do have some concerns with the specificity needed for this feature: it seems like we have a similar feature in tuples, and it would be great if there was a general rule about inferring element types from nested element expressions that subsumed all of these cases. We do generally think that behavior specified is correct, however, and will proceed with spec'ing it as having a single set of rules that can affect all the scenarios like this in the language.
Behavior is approved. We will work on making a general spec rule that covers this, tuple cases, and other similar scenarios without having to make bespoke rules for each location.
Our second and final topic today relates to overload resolution in ambiguous scenarios with the ref struct preferencing rule. For example, this scenario is ambiguous today:
static void A(Span<object> value) { }
static void A(string[] value) { }
A([string.Empty]); // error: ambiguous
There's some precedent for this type of behavior in the language, with how conversions are handled between an API like this:
static void B(IReadOnlyList<object> e) => Console.WriteLine("List<object>");
static void B(IEnumerable<string> e) => Console.WriteLine("IEnumerable<string>");
B(new List<string>() { "one", "two" });
For both of these scenarios, the LDM is in general agreement that we don't want the scenario to work. It's not immediately obvious to anyone, reader or compiler, which of these two overloads is the better one to call. We also don't believe API patterns like this are realistic. However, there are similar scenarios where we do need to have some sort of element convertibility rule in order to have the right API chosen. As an example of this:
static void C(ReadOnlySpan<string> r);
static void C(object[] r);
C(["1", "2"]); // Ambiguous today, because object[] and ROS<string> cannot be converted between
In this scenario, the LDM believes it's perfectly reasonable for such an API pattern to arise as a library evolves. We also believe that it's unambiguous which one is the "better" API; the
ReadOnlySpan<string>
can be allocation free, and the inner type is also more specific than the element type of object[]
. However, since we cannot convert between ReadOnlySpan<string>
and
object[]
, the rules as written today would make this ambiguous. We have a few different rule proposals that might make this possible, given our previous rule of preferencing ref structs over
other types:
- Require that there be an identity conversion between the iteration types. This would enable
string[]
vsReadOnlySpan<string>
, but would preventobject[]
andReadOnlySpan<string>
. - Require that there be an implicit conversion from the ref struct's iteration type to the other type's iteration type. This would enable
object[]
vsReadOnlySpan<string>
, but notstring[]
vsReadOnlySpan<object>
, as there is no implicit conversion fromobject
tostring
. - Always prefer the ref struct, no consideration of element type convertibility at all.
- No special casing whatsoever.
After some deliberation, we settled on 2, feeling that it's a good match for user expectations of the feature. However, we then looked at how far we want to apply this rule. So far, all of the
examples we've looked at relate to (ReadOnly)Span
vs one of the interfaces that arrays implement. We're concerned that applying the rule more broadly, particularly with so little time left in C#
12, will have some unintended consequences. We don't think we can categorically say that all ref structs should be preferred over non-ref struct counterparts, so we'll restrict the rule to just
being for (ReadOnly)Span
vs arrays and all the interfaces arrays implement.
We will prefer (ReadOnly)Span
over arrays and interfaces implemented by arrays in overload resolution for an argument with a collection expression conversion when there is an implicit conversion from the element type of the Span
to the element type
of the array or array interface.