-
Notifications
You must be signed in to change notification settings - Fork 6k
Description
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