Arrays and strings could already be passed to Span<T> and ReadOnlySpan<T> parameters, but only through user-defined implicit operators. Those were ignored for extension method receivers, did not help generic type inference and could not break ties in overload resolution, so span-based APIs often needed explicit AsSpan() calls or extra array overloads.
C# 14.0 makes span conversions first-class in the language. Arrays, strings, and Span<T> now have implicit language conversions to Span<T> and ReadOnlySpan<T>, so they work with extension method receivers, overload resolution and type inference.
Code
csharp
int[] numbers = [1, 2, 3];
// Overload resolution prefers the span overload
Numbers.Print(numbers); // Calls the ReadOnlySpan<int> overload
// Span-based extension methods work directly on arrays
bool startsWithOne = numbers.StartsWith(1); // MemoryExtensions.StartsWith
// Type inference works through the conversion
var first = Numbers.First(numbers); // T inferred as int
static class Numbers
{
public static void Print(IEnumerable<int> items) { }
public static void Print(ReadOnlySpan<int> items) { }
public static T First<T>(ReadOnlySpan<T> items) => items[0];
}csharp
int[] numbers = [1, 2, 3];
// Numbers.Print(numbers) was ambiguous (error CS0121)
Numbers.Print(numbers.AsSpan()); // Explicit conversion required
// Array receivers were not converted for extension methods
bool startsWithOne = ((ReadOnlySpan<int>)numbers).StartsWith(1);
// Type inference failed, so the type argument was required
var first = Numbers.First<int>(numbers);
static class Numbers
{
public static void Print(IEnumerable<int> items) { }
public static void Print(ReadOnlySpan<int> items) { }
public static T First<T>(ReadOnlySpan<T> items) => items[0];
}Notes
- The new conversions are from
T[]toSpan<T>andReadOnlySpan<T>, fromSpan<T>toReadOnlySpan<T>, and fromstringtoReadOnlySpan<char> - An identity conversion still wins: for an
int[]argument,M(int[])is still chosen overM(ReadOnlySpan<int>) - When both apply,
ReadOnlySpan<T>overloads are preferred overSpan<T>overloads - Can change which method binds:
array.Reverse()can now bind to the in-place,void-returningMemoryExtensions.Reverseinstead of LINQ'sEnumerable.Reverse(.NET 10 adds an array overload toEnumerableto keep the LINQ behavior)