Skip to content

Commit cd16d3a

Browse files
authored
c# params span overloads breaking change (#44000)
1 parent 9ddfdcc commit cd16d3a

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

docs/core/compatibility/9.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ If you're migrating an app to .NET 9, the breaking changes listed here might aff
4141
| [API obsoletions with custom diagnostic IDs](core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md) | Source incompatible | (Multiple) |
4242
| [BigInteger maximum length](core-libraries/9.0/biginteger-limit.md) | Behavioral change | Preview 6 |
4343
| [BinaryReader.GetString() returns "\uFFFD" on malformed sequences](core-libraries/9.0/binaryreader.md) | Behavioral change | Preview 7 |
44+
| [C# overload resolution prefers `params` span-type overloads](core-libraries/9.0/params-overloads.md) | Source incompatible | |
4445
| [Creating type of array of System.Void not allowed](core-libraries/9.0/type-instance.md) | Behavioral change | Preview 1 |
4546
| [Default `Equals()` and `GetHashCode()` throw for types marked with `InlineArrayAttribute`](core-libraries/9.0/inlinearrayattribute.md) | Behavioral change | Preview 6 |
4647
| [EnumConverter validates registered types to be enum](core-libraries/9.0/enumconverter.md) | Behavioral change | Preview 7 |
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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)

docs/core/compatibility/toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ items:
3636
href: core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md
3737
- name: BigInteger maximum length
3838
href: core-libraries/9.0/biginteger-limit.md
39+
- name: C# overload resolution prefers `params` span-type overloads
40+
href: core-libraries/9.0/params-overloads.md
3941
- name: Creating type of array of System.Void not allowed
4042
href: core-libraries/9.0/type-instance.md
4143
- name: EnumConverter validates registered types to be enum
@@ -1304,6 +1306,8 @@ items:
13041306
href: core-libraries/9.0/biginteger-limit.md
13051307
- name: BinaryReader.GetString() returns "/uFFFD" on malformed sequences
13061308
href: core-libraries/9.0/binaryreader.md
1309+
- name: C# overload resolution prefers `params` span-type overloads
1310+
href: core-libraries/9.0/params-overloads.md
13071311
- name: Creating type of array of System.Void not allowed
13081312
href: core-libraries/9.0/type-instance.md
13091313
- name: EnumConverter validates registered types to be enum

0 commit comments

Comments
 (0)