|
| 1 | +--- |
| 2 | +title: "Breaking change: C# overload resolution prefers `params` span-type overloads" |
| 3 | +description: Learn about the breaking change in .NET 9 where C# overload resolution prefers `params` span-type overloads, which can't be used in `Expression` lambdas. |
| 4 | +ms.date: 12/16/2024 |
| 5 | +--- |
| 6 | + |
| 7 | +# C# overload resolution prefers `params` span-type overloads |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +.NET 9 [added `params` span overloads for various methods](../../../whats-new/dotnet-9/libraries.md#params-readonlyspant-overloads) 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. |
| 12 | + |
| 13 | +The new binding leads to a potential breaking change for existing calls to those overloads within <xref:System.Linq.Expressions.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. |
| 14 | + |
| 15 | +For example, consider `string.Join()`: |
| 16 | + |
| 17 | +```csharp |
| 18 | +using System; |
| 19 | +using System.Linq.Expressions; |
| 20 | + |
| 21 | +Expression<Func<string, string, string>> join = |
| 22 | + (x, y) => string.Join("", x, y); |
| 23 | +``` |
| 24 | + |
| 25 | +When compiled with .NET 8, the call binds to <xref:System.String.Join(System.String,System.String[])>, without errors. |
| 26 | + |
| 27 | +When compiled with C# 13 and .NET 9, the call binds to <xref:System.String.Join(System.String,System.ReadOnlySpan{System.String})>, and because the call is within an [expression tree](xref:System.Linq.Expressions.Expression), the following errors are reported: |
| 28 | + |
| 29 | +> error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. |
| 30 | +> error CS9226: An expression tree may not contain an expanded form of non-array params |
| 31 | +
|
| 32 | +## Version introduced |
| 33 | + |
| 34 | +.NET 9 |
| 35 | + |
| 36 | +## Previous behavior |
| 37 | + |
| 38 | +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 <xref:System.Linq.Expressions.Expression> lambda expressions. |
| 39 | + |
| 40 | +## New behavior |
| 41 | + |
| 42 | +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 <xref:System.Linq.Expressions.Expression> lambda expressions, the implicit `ref struct` span instance is reported as a compiler error. |
| 43 | + |
| 44 | +## Type of breaking change |
| 45 | + |
| 46 | +This change can affect [source compatibility](../../categories.md#source-compatibility). |
| 47 | + |
| 48 | +## Reason for change |
| 49 | + |
| 50 | +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. |
| 51 | + |
| 52 | +## Recommended action |
| 53 | + |
| 54 | +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. |
| 55 | + |
| 56 | +For the previous example, use `new string[] { ... }`: |
| 57 | + |
| 58 | +```csharp |
| 59 | +Expression<Func<string, string, string>> join = |
| 60 | + (x, y) => string.Join("", new string[] { x, y }); |
| 61 | +``` |
| 62 | + |
| 63 | +## Affected APIs |
| 64 | + |
| 65 | +- <xref:System.Collections.Immutable.ImmutableArray.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 66 | +- <xref:System.Collections.Immutable.ImmutableArray`1.AddRange(System.ReadOnlySpan{`0})?displayProperty=fullName> |
| 67 | +- <xref:System.Collections.Immutable.ImmutableArray`1.InsertRange(System.Int32,System.ReadOnlySpan{`0})?displayProperty=fullName> |
| 68 | +- <xref:System.Collections.Immutable.ImmutableArray`1.Builder.AddRange(System.ReadOnlySpan{`0})?displayProperty=fullName> |
| 69 | +- <xref:System.Collections.Immutable.ImmutableArray`1.Builder.AddRange``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 70 | +- <xref:System.Collections.Immutable.ImmutableHashSet.Create``1(System.Collections.Generic.IEqualityComparer{``0},System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 71 | +- <xref:System.Collections.Immutable.ImmutableHashSet.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 72 | +- <xref:System.Collections.Immutable.ImmutableList.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 73 | +- <xref:System.Collections.Immutable.ImmutableQueue.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 74 | +- <xref:System.Collections.Immutable.ImmutableSortedSet.Create``1(System.Collections.Generic.IComparer{``0},System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 75 | +- <xref:System.Collections.Immutable.ImmutableSortedSet.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 76 | +- <xref:System.Collections.Immutable.ImmutableStack.Create``1(System.ReadOnlySpan{``0})?displayProperty=fullName> |
| 77 | +- <xref:System.Console.Write(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 78 | +- <xref:System.Console.WriteLine(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 79 | +- <xref:System.Diagnostics.Metrics.Counter`1.Add(`0,System.ReadOnlySpan{System.Collections.Generic.KeyValuePair{System.String,System.Object}})?displayProperty=fullName> |
| 80 | +- <xref:System.Diagnostics.Metrics.Gauge`1.Record(`0,System.ReadOnlySpan{System.Collections.Generic.KeyValuePair{System.String,System.Object}})?displayProperty=fullName> |
| 81 | +- <xref:System.Diagnostics.Metrics.UpDownCounter`1.Add(`0,System.ReadOnlySpan{System.Collections.Generic.KeyValuePair{System.String,System.Object}})?displayProperty=fullName> |
| 82 | +- <xref:System.Diagnostics.Metrics.Histogram`1.Record(`0,System.ReadOnlySpan{System.Collections.Generic.KeyValuePair{System.String,System.Object}})?displayProperty=fullName> |
| 83 | +- <xref:System.MemoryExtensions.TryWrite(System.Span{System.Char},System.IFormatProvider,System.Text.CompositeFormat,System.Int32@,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 84 | +- <xref:System.Delegate.Combine(System.ReadOnlySpan{System.Delegate})?displayProperty=fullName> |
| 85 | +- <xref:System.String.Concat(System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 86 | +- <xref:System.String.Concat(System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 87 | +- <xref:System.String.Format(System.IFormatProvider,System.Text.CompositeFormat,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 88 | +- <xref:System.String.Format(System.IFormatProvider,System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 89 | +- <xref:System.String.Format(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 90 | +- <xref:System.String.Join(System.Char,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 91 | +- <xref:System.String.Join(System.Char,System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 92 | +- <xref:System.String.Join(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 93 | +- <xref:System.String.Join(System.String,System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 94 | +- <xref:System.String.Split(System.ReadOnlySpan{System.Char})?displayProperty=fullName> |
| 95 | +- <xref:System.CodeDom.Compiler.IndentedTextWriter.Write(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 96 | +- <xref:System.CodeDom.Compiler.IndentedTextWriter.WriteLine(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 97 | +- <xref:System.IO.Path.Combine(System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 98 | +- <xref:System.IO.Path.Join(System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 99 | +- <xref:System.IO.StreamWriter.Write(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 100 | +- <xref:System.IO.StreamWriter.WriteLine(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 101 | +- <xref:System.IO.TextWriter.Write(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 102 | +- <xref:System.IO.TextWriter.WriteLine(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 103 | +- <xref:System.Text.StringBuilder.AppendFormat(System.IFormatProvider,System.Text.CompositeFormat,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 104 | +- <xref:System.Text.StringBuilder.AppendFormat(System.IFormatProvider,System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 105 | +- <xref:System.Text.StringBuilder.AppendFormat(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 106 | +- <xref:System.Text.StringBuilder.AppendJoin(System.Char,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 107 | +- <xref:System.Text.StringBuilder.AppendJoin(System.Char,System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 108 | +- <xref:System.Text.StringBuilder.AppendJoin(System.String,System.ReadOnlySpan{System.Object})?displayProperty=fullName> |
| 109 | +- <xref:System.Text.StringBuilder.AppendJoin(System.String,System.ReadOnlySpan{System.String})?displayProperty=fullName> |
| 110 | +- <xref:System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.ReadOnlySpan{System.Threading.CancellationToken})?displayProperty=fullName> |
| 111 | +- <xref:System.Threading.Tasks.Task.WaitAll(System.ReadOnlySpan{System.Threading.Tasks.Task})?displayProperty=fullName> |
| 112 | +- <xref:System.Threading.Tasks.Task.WhenAll(System.ReadOnlySpan{System.Threading.Tasks.Task})?displayProperty=fullName> |
| 113 | +- <xref:System.Threading.Tasks.Task.WhenAll``1(System.ReadOnlySpan{System.Threading.Tasks.Task{``0}})?displayProperty=fullName> |
| 114 | +- <xref:System.Threading.Tasks.Task.WhenAny(System.ReadOnlySpan{System.Threading.Tasks.Task})?displayProperty=fullName> |
| 115 | +- <xref:System.Threading.Tasks.Task.WhenAny``1(System.ReadOnlySpan{System.Threading.Tasks.Task{``0}})?displayProperty=fullName> |
| 116 | +- <xref:System.Text.Json.Nodes.JsonArray.%23ctor(System.Text.Json.Nodes.JsonNodeOptions,System.ReadOnlySpan{System.Text.Json.Nodes.JsonNode})> |
| 117 | +- <xref:System.Text.Json.Nodes.JsonArray.%23ctor(System.ReadOnlySpan{System.Text.Json.Nodes.JsonNode})> |
| 118 | +- <xref:System.Text.Json.Serialization.Metadata.JsonTypeInfoResolver.Combine(System.ReadOnlySpan{System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver})?displayProperty=fullName> |
| 119 | + |
| 120 | +## See also |
| 121 | + |
| 122 | +- [What's new: `params ReadOnlySpan<T>` overloads](../../../whats-new/dotnet-9/libraries.md#params-readonlyspant-overloads) |
0 commit comments