C# overload resolution prefers params
span-type overloads
C# 13 added support for params
parameters declared with collection types other than arrays. In particular, params ReadOnlySpan<T>
and params Span<T>
are supported, and overload resolution prefers a params
span type over a params
array type when both are applicable.
.NET 9 added params
span overloads for various methods in the core .NET libraries. Those methods had pre-existing overloads that took params
arrays. When you recompile code with existing calls to those methods where arguments are passed in expanded form, the compiler will now bind to the params
span overload.
The new binding leads to a potential breaking change for existing calls to those overloads within Expression lambda expressions, which don't support ref struct
instances. In those cases, the C# 13 compiler reports an error when binding to the params
span overload.
For example, consider string.Join()
:
using System;
using System.Linq.Expressions;
Expression<Func<string, string, string>> join =
(x, y) => string.Join("", x, y);
When compiled with .NET 8, the call binds to Join(String, String[]), without errors.
When compiled with C# 13 and .NET 9, the call binds to Join(String, ReadOnlySpan<String>), and because the call is within an expression tree, the following errors are reported:
error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. error CS9226: An expression tree may not contain an expanded form of non-array params
Version introduced
.NET 9
Previous behavior
Prior to C# 13, params
parameters were limited to array types only. Calls to those methods in expanded form resulted in implicit array instances only, which are supported in Expression lambda expressions.
New behavior
With C# 13 and .NET 9, for methods with overloads that take params
array types and params
span types, overload resolution prefers the params
span overload. Such a call creates an implicit span instance at the call site. For calls within Expression lambda expressions, the implicit ref struct
span instance is reported as a compiler error.
Type of breaking change
This change can affect source compatibility.
Reason for change
The new method overloads were added for performance reasons. params
span support allows the compiler to avoid an allocation for the params
argument at the call site.
Recommended action
If your code is affected, the recommended workaround is to call the method with an explicit array so the call binds to the params
array overload.
For the previous example, use new string[] { ... }
:
Expression<Func<string, string, string>> join =
(x, y) => string.Join("", new string[] { x, y });
Affected APIs
- System.Collections.Immutable.ImmutableArray.Create<T>(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableArray<T>.AddRange(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableArray<T>.InsertRange(Int32, ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableArray<T>.Builder.AddRange(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableArray<T>.Builder.AddRange<TDerived>(ReadOnlySpan<TDerived>)
- System.Collections.Immutable.ImmutableHashSet.Create<T>(IEqualityComparer<T>, ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableHashSet.Create<T>(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableList.Create<T>(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableQueue.Create<T>(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableSortedSet.Create<T>(IComparer<T>, ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableSortedSet.Create<T>(ReadOnlySpan<T>)
- System.Collections.Immutable.ImmutableStack.Create<T>(ReadOnlySpan<T>)
- System.Console.Write(String, ReadOnlySpan<Object>)
- System.Console.WriteLine(String, ReadOnlySpan<Object>)
- System.Diagnostics.Metrics.Counter<T>.Add(T, ReadOnlySpan<KeyValuePair<String,Object>>)
- System.Diagnostics.Metrics.Gauge<T>.Record(T, ReadOnlySpan<KeyValuePair<String,Object>>)
- System.Diagnostics.Metrics.UpDownCounter<T>.Add(T, ReadOnlySpan<KeyValuePair<String,Object>>)
- System.Diagnostics.Metrics.Histogram<T>.Record(T, ReadOnlySpan<KeyValuePair<String,Object>>)
- System.MemoryExtensions.TryWrite(Span<Char>, IFormatProvider, CompositeFormat, Int32, ReadOnlySpan<Object>)
- System.Delegate.Combine(ReadOnlySpan<Delegate>)
- System.String.Concat(ReadOnlySpan<Object>)
- System.String.Concat(ReadOnlySpan<String>)
- System.String.Format(IFormatProvider, CompositeFormat, ReadOnlySpan<Object>)
- System.String.Format(IFormatProvider, String, ReadOnlySpan<Object>)
- System.String.Format(String, ReadOnlySpan<Object>)
- System.String.Join(Char, ReadOnlySpan<Object>)
- System.String.Join(Char, ReadOnlySpan<String>)
- System.String.Join(String, ReadOnlySpan<Object>)
- System.String.Join(String, ReadOnlySpan<String>)
- System.String.Split(ReadOnlySpan<Char>)
- System.CodeDom.Compiler.IndentedTextWriter.Write(String, ReadOnlySpan<Object>)
- System.CodeDom.Compiler.IndentedTextWriter.WriteLine(String, ReadOnlySpan<Object>)
- System.IO.Path.Combine(ReadOnlySpan<String>)
- System.IO.Path.Join(ReadOnlySpan<String>)
- System.IO.StreamWriter.Write(String, ReadOnlySpan<Object>)
- System.IO.StreamWriter.WriteLine(String, ReadOnlySpan<Object>)
- System.IO.TextWriter.Write(String, ReadOnlySpan<Object>)
- System.IO.TextWriter.WriteLine(String, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendFormat(IFormatProvider, CompositeFormat, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendFormat(IFormatProvider, String, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendFormat(String, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendJoin(Char, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendJoin(Char, ReadOnlySpan<String>)
- System.Text.StringBuilder.AppendJoin(String, ReadOnlySpan<Object>)
- System.Text.StringBuilder.AppendJoin(String, ReadOnlySpan<String>)
- System.Threading.CancellationTokenSource.CreateLinkedTokenSource(ReadOnlySpan<CancellationToken>)
- System.Threading.Tasks.Task.WaitAll(ReadOnlySpan<Task>)
- System.Threading.Tasks.Task.WhenAll(ReadOnlySpan<Task>)
- System.Threading.Tasks.Task.WhenAll<TResult>(ReadOnlySpan<Task<TResult>>)
- System.Threading.Tasks.Task.WhenAny(ReadOnlySpan<Task>)
- System.Threading.Tasks.Task.WhenAny<TResult>(ReadOnlySpan<Task<TResult>>)
- JsonArray(JsonNodeOptions, ReadOnlySpan<JsonNode>)
- JsonArray(ReadOnlySpan<JsonNode>)
- System.Text.Json.Serialization.Metadata.JsonTypeInfoResolver.Combine(ReadOnlySpan<IJsonTypeInfoResolver>)