Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Breaking change]: C# overload resolution prefers params *span type* overloads which cannot be used in Expression lambdas #43949

Open
1 of 3 tasks
cston opened this issue Dec 12, 2024 · 0 comments
Assignees
Labels
breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 source incompatible Source code may encounter a breaking change in behavior when targeting the new version. ⌚ Not Triaged Not triaged

Comments

@cston
Copy link
Member

cston commented Dec 12, 2024

Description

C# 13 adds 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 will prefer a params span type over a params array type when both are applicable.

.NET 9 adds params span overloads for various methods in the BCL, in addition to the existing overloads with params arrays. When recompiling existing callers of those methods, where arguments are passed in expanded form, the compiler will now bind to the params span overload.

That leads to a potential breaking change for existing calls to those overloads within Expression lambda expressions, where ref struct instances are not supported. In those cases, the C# 13 compiler will report an error when binding to the params span overload.

For instance, consider string.Join() (see dotnet/runtime#110592):

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 String.Join(string? separator, params string?[] value), without errors.

When compiled with C# 13 and .NET 9, the call binds to String.Join(string? separator, params ReadOnlySpan<string?> value), 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 collection parameter.

Version

.NET 9

Previous behavior

Previously, params overloads were limited to array types only, so calls to those methods in expanded form resulted in implicit arrays.

New behavior

With C#13 and .NET 9, for methods with overloads that take params array and params span, overload resolution will prefer the params span overload, resulting in an implicit span instance. For calls within Expression lambda expressions, the implicit ref struct span instance will be reported as an error.

Type of breaking change

  • Binary incompatible: Existing binaries might encounter a breaking change in behavior, such as failure to load or execute, and if so, require recompilation.
  • Source incompatible: When recompiled using the new SDK or component or to target the new runtime, existing source code might require source changes to compile successfully.
  • Behavioral change: Existing binaries might behave differently at run time.

Reason for change

params span support allows the compiler to avoid unnecessary allocation for the params argument.

Recommended action

The recommended work around is to call the method with an explicit array, so the call is bound to the params array overload.

For the example above, use new string[] { ... }:

Expression<Func<string, string, string>> join = (x, y) => string.Join("", new string[] { x, y });

Feature area

Other (please put exact area in description textbox)

Affected APIs

String.Join(string? separator, params string?[] value)
// ...
@cston cston added breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 labels Dec 12, 2024
@dotnetrepoman dotnetrepoman bot added ⌚ Not Triaged Not triaged source incompatible Source code may encounter a breaking change in behavior when targeting the new version. labels Dec 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 source incompatible Source code may encounter a breaking change in behavior when targeting the new version. ⌚ Not Triaged Not triaged
Projects
None yet
Development

No branches or pull requests

2 participants