Skip to content

Implicit span conversions C# 14.0code reductionperformance

Implicitly convert arrays and strings to spans without explicit casting.

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[] to Span<T> and ReadOnlySpan<T>, from Span<T> to ReadOnlySpan<T>, and from string to ReadOnlySpan<char>
  • An identity conversion still wins: for an int[] argument, M(int[]) is still chosen over M(ReadOnlySpan<int>)
  • When both apply, ReadOnlySpan<T> overloads are preferred over Span<T> overloads
  • Can change which method binds: array.Reverse() can now bind to the in-place, void-returning MemoryExtensions.Reverse instead of LINQ's Enumerable.Reverse (.NET 10 adds an array overload to Enumerable to keep the LINQ behavior)

More information ​

Powered by Intracia