Skip to content

[Breaking change]: C# overload resolution prefers span overloads which might not work in Expression lambdas #43952

@jjonescz

Description

@jjonescz

Description

C# 14 introduces new built-in span conversions and type inference rules making overloads with span parameters applicable in more scenarios.

However, Expression lambdas cannot be interpreted when they involve ref structs like Span and ReadOnlySpan. For example:

using System;
using System.Linq;
using System.Linq.Expressions;

Expression<Func<int[], int, bool>> exp = (array, num) => array.Contains(num);
exp.Compile(preferInterpretation: true); // fails at runtime in C# 14

The array.Contains binds to Enumerable.Contains in C# 13 but binds to MemoryExtensions.Contains in C# 14. The latter involves ReadOnlySpan and hence crashes when Expression.Compile(preferInterpretation: true) is called.

Version

.NET 10 Preview 1

Previous behavior

In C# 13 and earlier, an extension method taking a ReadOnlySpan<T> or Span<T> receiver is not applicable to a value of type T[]. Therefore, only non-span extension methods like the ones from the System.Linq.Enumerable class are usually bound inside Expression lambdas.

New behavior

In C# 14 and later, methods with ReadOnlySpan<T> or Span<T> parameters can participate in type inference or be used as extension methods in more scenarios. This makes span-based methods like the ones from the System.MemoryExtensions class bind in more scenarios, including inside Expression lambdas where they will cause runtime exceptions when compiled with interpretation.

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

The C# language feature allows simplified API design and usage (e.g., one ReadOnlySpan extension method can apply to both spans and arrays).

Recommended action

If you need to continue using Expression interpretation, you should make sure the non-span overloads are bound, e.g., by casting arguments to the exact types the method signature takes or calling the extension methods as explicit static invocations:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

M((array, num) => array.Contains(num)); // fails, binds to MemoryExtensions.Contains
M((array, num) => ((IEnumerable<int>)array).Contains(num)); // ok, binds to Enumerable.Contains
M((array, num) => array.AsEnumerable().Contains(num)); // ok, binds to Enumerable.Contains
M((array, num) => Enumerable.Contains(array, num)); // ok, binds to Enumerable.Contains

void M(Expression<Func<int[], int, bool>> e) => e.Compile(preferInterpretation: true);

Feature area

Other (please put exact area in description textbox)

Affected APIs

System.Linq.Expressions.Expression.Compile

Associated WorkItem - 360646

Metadata

Metadata

Labels

🏁 Release: .NET 10Work items for the .NET 10 release📌 seQUESTeredIdentifies that an issue has been imported into Quest.breaking-changeIndicates a .NET Core breaking changein-prThis issue will be closed (fixed) by an active pull request.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions