Skip to content

[Proposal]: Prefer spans over interfaces in overload resolution #7276

@cston

Description

@cston

Prefer spans over interfaces in overload resolution

  • Proposed
  • Prototype: Not Started
  • Implementation: Not Started
  • Specification: Not Started

Summary

Overload resolution should prefer an overload where the corresponding parameter has a span type over an overload where the parameter has a collection interface type.

Motivation

For APIs that take a collection of items, it may be useful to provide overloads for collection interfaces and for spans. Supporting collection interfaces such as IEnumerable<T> is particularly useful for callers using LINQ; supporting spans such as ReadOnlySpan<T> is useful for callers focused on performance.

However, arrays are implicitly convertible to collection interfaces and spans, so calls with array arguments may be considered ambiguous with the current overload resolution rules.

var ia = ImmutableArray.CreateRange<int>(new[] { 1, 2, 3 }); // error: CreateRange() is ambiguous

public static class ImmutableArray
{
    public static ImmutableArray<T> CreateRange<T>(IEnumerable<T> items) { ... }
    public static ImmutableArray<T> CreateRange<T>(ReadOnlySpan<T> items) { ... }
}

In cases such as arrays where both overloads are applicable, overload resolution should prefer the span overload.

Detailed design

The overload resolution rules for 11.6.4.6 better conversion target could be updated with an additional rule:

Given two types T₁ and T₂, T₁ is a better conversion target than T₂ if one of the following holds:

  • ...
  • T₁ is ReadOnlySpan<S₁> or Span<S₁>, and T₂ is IEnumerable<S₂> or ICollection<S₂> or IList<S₂> or IReadOnlyCollection<S₂> or IReadOnlyList<S₂>, and an implicit conversion exists from S₂ to S₁

Drawbacks

  • Preferring ReadOnlySpan<T> over IEnumerable<T> in overload resolution requires choosing a user-defined conversion over an implicit reference conversion.

  • For generic overloads such as ImmutableArray.CreateRange<T>() above, an array argument is only ambiguous when the generic type argument is explicit. By contrast, when the generic type argument is inferred, method type inference is required which will ignore conversions and fail to infer the type argument for ReadOnlySpan<T>.

    var ia = ImmutableArray.CreateRange<int>(new[] { 1, 2, 3 }); // C#11: ambiguous, C#12: ReadOnlySpan<int>
    var ib = ImmutableArray.CreateRange(new[] { 1, 2, 3 });      // C#11/C#12: IEnumerable<int>
  • Overloads for collection interfaces and spans are still ambiguous for arrays when compiled with older compilers.

    Mixing older compilers and newer TFMs is not strictly a supported scenario but it is used. Perhaps we could add a custom modifier to new overloads, so those overloads are ignored by older C# and VB compilers. What is the behavior from F#?

Alternatives

  • Use distinct method names rather than overloads.

    This alternative is not ideal for the BCL which already uses overloads for scenarios such as collection construction where a ReadOnlySpan<T> overload might be useful. And using distinct names will not work for constructors.

  • Use an extension method to overload an instance method. The extension method will only be considered when the instance method is inapplicable which avoids ambiguities when both are applicable.

    This alternative prevents the compiler from choosing the better overload, however. And using extension methods will not work for constructors.

Unresolved questions

  • Should the updated behavior apply to array arguments only rather than all expressions that are implicitly convertible to both collection interfaces and spans?

Design meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-06-19.md#prefer-spans-over-interfaces-in-overload-resolution

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions