本文件列出了从 .NET 9 常规版本(.NET SDK 版本 9.0.100)到 .NET 10 常规版本(.NET SDK 版本 10.0.100)期间 Roslyn 中已知的重大变更。
lambda 参数列表中的 scoped
现在始终是修饰符。
在 Visual Studio 2022 版本 17.13 中引入
C# 14 引入了使用参数修饰符编写 lambda 的功能,而无需指定参数类型:https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md
作为这项工作的一部分,已接受了一个重大更改,其中 scoped
始终被视为 lambda 参数中的修饰符,即使在过去它可能被作为类型名称接受。 例如:
var v = (scoped scoped s) => { ... };
ref struct @scoped { }
在 C# 14 中,这是一个错误,因为两个 scoped
令牌都被视为修饰符。 解决方法是在类型名称位置使用 @
,如下所示:
var v = (scoped @scoped s) => { ... };
ref struct @scoped { }
Span<T>
和 ReadOnlySpan<T>
重载适用于 C# 14 及更高版本中的更多方案
在 Visual Studio 2022 版本 17.13 中引入
C# 14 引入了新的内置跨度转换和类型推理规则。 这意味着,与 C# 13 相比,可能会选择不同的重载。有时可能会出现编译时不明确错误,这是因为某个新重载可以使用,但没有单个最佳重载。
以下示例演示了一些歧义和可能的解决方法。
请注意,API 作者的另一个解决方法是使用 OverloadResolutionPriorityAttribute
。
var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround
var y = new int[] { 1, 2 };
var s = new ArraySegment<int>(y, 1, 1);
Assert.Equal(y, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(y.AsSpan(), s); // workaround
在 C# 14 中可能会选择一个 Span<T>
重载,而在 C# 13 中选择了一个采用 T[]
(如 IEnumerable<T>
)实现的接口的重载;如果与协变数组一起使用,这可能会导致运行时出现 ArrayTypeMismatchException
:
string[] s = new[] { "a" };
object[] o = s; // array variance
C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround
static class C
{
public static void R<T>(IEnumerable<T> e) => Console.Write(1);
public static void R<T>(Span<T> s) => Console.Write(2);
// another workaround:
public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}
因此,在 C# 14 的重载解析中,ReadOnlySpan<T>
通常比 Span<T>
更受青睐。
在某些情况下,这可能会导致编译中断,例如,当 Span<T>
和 ReadOnlySpan<T>
都有重载,并且都接受和返回相同的跨度类型时:
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now compilation error
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround
static class MemoryMarshal
{
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}
使用 C# 14 或更高版本,并将 .NET 目标设定为早于 net10.0
或 .NET Framework,同时具有 System.Memory
引用时,对 Enumerable.Reverse
和数组有一个重大更改:
int[] x = new[] { 1, 2, 3 };
var y = x.Reverse(); // previously Enumerable.Reverse, now MemoryExtensions.Reverse
在 net10.0
上,由于 Enumerable.Reverse(this T[])
优先,因此避免了中断。
否则,MemoryExtensions.Reverse(this Span<T>)
将被解析,其语义不同于 Enumerable.Reverse(this IEnumerable<T>)
(从前在 C# 13 及更低版本中得到解析)。
具体而言,Span
扩展将在原地进行反转,并返回 void
。
解决方法是,可以自己定义 Enumerable.Reverse(this T[])
或显式使用 Enumerable.Reverse
:
int[] x = new[] { 1, 2, 3 };
var y = Enumerable.Reverse(x); // instead of 'x.Reverse();'
现在在 foreach
中报告基于模式的处置方法的诊断
在 Visual Studio 2022 版本 17.13 中引入
例如,一个过时的 DisposeAsync
方法现在在 await foreach
中被报告。
await foreach (var i in new C()) { } // 'C.AsyncEnumerator.DisposeAsync()' is obsolete
class C
{
public AsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default)
{
throw null;
}
public sealed class AsyncEnumerator : System.IAsyncDisposable
{
public int Current { get => throw null; }
public Task<bool> MoveNextAsync() => throw null;
[System.Obsolete]
public ValueTask DisposeAsync() => throw null;
}
}
在处置过程中将枚举器对象的状态设置为“after”
在 Visual Studio 2022 版本 17.13 中引入
枚举器的状态机错误地允许在处置枚举器后恢复执行。
现在,在已经释放的枚举器上,MoveNext()
能够正确返回 false
,而无需再执行任何用户代码。
var enumerator = C.GetEnumerator();
Console.Write(enumerator.MoveNext()); // prints True
Console.Write(enumerator.Current); // prints 1
enumerator.Dispose();
Console.Write(enumerator.MoveNext()); // now prints False
class C
{
public static IEnumerator<int> GetEnumerator()
{
yield return 1;
Console.Write("not executed after disposal")
yield return 2;
}
}
UnscopedRefAttribute
不能与旧的 ref 安全性规则一起使用
在 Visual Studio 2022 版本 17.13 中引入
即使代码是在早期的 ref 安全性规则的上下文中编译的(即,针对 C# 10 或更早版本以及 net6.0 或更早版本),UnscopedRefAttribute
也会无意中影响新 Roslyn 编译器版本编译的代码。
然而,该属性在该上下文中不应产生影响,现在已经解决了这个问题。
以前在 C# 10 或更早版本以及 net6.0 或更早版本中没有报告任何错误的代码,现在可能无法编译。
using System.Diagnostics.CodeAnalysis;
struct S
{
public int F;
// previously allowed in C# 10 with net6.0
// now fails with the same error as if the [UnscopedRef] wasn't there:
// error CS8170: Struct members cannot return 'this' or other instance members by reference
[UnscopedRef] public ref int Ref() => ref F;
}
为了防止误解(认为该属性有效果,但它实际上没有,因为代码是使用早期 ref 安全规则编译的),当属性在 C# 10 或更早版本与 net6.0 或更早版本一起使用时,将报告警告:
using System.Diagnostics.CodeAnalysis;
struct S
{
// both are errors in C# 10 with net6.0:
// warning CS9269: UnscopedRefAttribute is only valid in C# 11 or later or when targeting net7.0 or later.
[UnscopedRef] public ref int Ref() => throw null!;
public static void M([UnscopedRef] ref int x) { }
}
Microsoft.CodeAnalysis.EmbeddedAttribute
在声明时被验证
在 Visual Studio 2022 版本 17.13 中引入
编译器现在在源代码中声明时验证 Microsoft.CodeAnalysis.EmbeddedAttribute
的外形。 以前,编译器会允许用户自定义声明此属性,但只有在编译器自身不需要生成该声明时才可以。 我们现在验证:
- 它必须是内部的
- 必须是类
- 它必须密封
- 它必须是非静态
- 它必须具有内部或公共无参数构造函数
- 它必须继承自 System.Attribute。
- 必须允许在任何类型声明(类、结构、接口、枚举或委托)上使用它
namespace Microsoft.CodeAnalysis;
// Previously, sometimes allowed. Now, CS9271
public class EmbeddedAttribute : Attribute {}
属性访问器中的表达式 field
表示合成支持字段
于 Visual Studio 2022 版本 17.12 中引入
表达式 field
在属性访问器中使用时,指的是属性的合成支持字段。
当标识符在语言版本 13 或更早版本中绑定到不同的符号时,会报告警告 CS9258。
若要避免生成合成后盾字段并引用现有成员,请改用“this.field”或“@field”。
或者,重命名现有成员和对该成员的引用以避免与 field
冲突。
class MyClass
{
private int field = 0;
public object Property
{
get
{
// warning CS9258: The 'field' keyword binds to a synthesized backing field for the property.
// To avoid generating a synthesized backing field, and to refer to the existing member,
// use 'this.field' or '@field' instead.
return field;
}
}
}
命名为 field
的变量在属性访问器中不被允许
在 Visual Studio 2022 版本 17.14 中引入
表达式 field
在属性访问器中使用时,指的是属性的合成支持字段。
当在属性访问器中声明名称为 field
的局部或嵌套函数参数时,将报告错误 CS9272。
若要避免此错误,请重命名变量,或在声明中使用 @field
。
class MyClass
{
public object Property
{
get
{
// error CS9272: 'field' is a keyword within a property accessor.
// Rename the variable or use the identifier '@field' instead.
int field = 0;
return @field;
}
}
}
即使提供自己的 Equals 实现,record
和 record struct
类型也不能定义指针类型成员
在 Visual Studio 2022 版本 17.14 中引入
record class
和 record struct
类型的规范指示不允许任何指针类型作为实例字段。
尽管如此,当 record class
或 record struct
类型定义了其自己的 Equals
实现时,这一点并未得到正确执行。
编译器现在正确地禁止这样做。
unsafe record struct R(
int* P // Previously fine, now CS8908
)
{
public bool Equals(R other) => true;
}
生成仅包含元数据的可执行文件需要一个入口点
在 Visual Studio 2022 版本 17.14 中引入
以前,在仅元数据模式(也称为 ref 程序集)中发出可执行文件时,入口点被无意中取消设置。 这现已更正,但也意味着缺少的入口点是编译错误:
// previously successful, now fails:
CSharpCompilation.Create("test").Emit(new MemoryStream(),
options: EmitOptions.Default.WithEmitMetadataOnly(true))
CSharpCompilation.Create("test",
// workaround - mark as DLL instead of EXE (the default):
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.Emit(new MemoryStream(),
options: EmitOptions.Default.WithEmitMetadataOnly(true))
同样,在使用命令行参数 /refonly
或 ProduceOnlyReferenceAssembly
MSBuild 属性时,可以观察到这种情况。
partial
不能是方法的返回类型
在 Visual Studio 2022 版本 17.14 中引入
部分事件和构造函数语言功能允许在更多位置使用 partial
修饰符,因此除非被转义,否则它不能是返回类型:
class C
{
partial F() => new partial(); // previously worked
@partial F() => new partial(); // workaround
}
class partial { }
extension
被视为上下文关键字
Visual Studio 2022 版本 17.14 中引入。 从 C# 14 开始, extension
关键字在表示扩展容器时提供特殊用途。
这会更改编译器解释某些代码构造的方式。
如果需要使用“extension”作为标识符而不是关键字,请使用@
前缀对其进行转义: @extension
这会告知编译器将其视为常规标识符而不是关键字。
编译器将此分析为扩展容器,而不是构造函数。
class @extension
{
extension(object o) { } // parsed as an extension container
}
编译器无法将此分析为返回类型 extension
的方法。
class @extension
{
extension M() { } // will not compile
}
Visual Studio 2022 版本 17.15 中引入。 “扩展”标识符不能用作类型名称,因此以下标识符不会编译:
using extension = ...; // alias may not be named "extension"
class extension { } // type may not be named "extension"
class C<extension> { } // type parameter may not be named "extension"
部分属性和事件现在隐式虚拟和公共
Visual Studio 2022 版本 17.15 中引入
我们修复了一个不一致之处,即部分接口属性和事件不会隐式virtual
public
且与非部分等效项不同。
但是,对于分部接口方法 , 保留这种不一致,以避免发生更大的中断性变更。
请注意,Visual Basic 和其他不支持默认接口成员的语言将开始要求实现隐式虚拟 partial
接口成员。
若要保留以前的行为,请显式标记 partial
接口成员 private
(如果他们没有任何辅助功能修饰符),并且 sealed
(如果他们没有 private
暗示 sealed
的修饰符,并且它们还没有修饰 virtual
符或 sealed
)。
System.Console.Write(((I)new C()).P); // wrote 1 previously, writes 2 now
partial interface I
{
public partial int P { get; }
public partial int P => 1; // implicitly virtual now
}
class C : I
{
public int P => 2; // implements I.P
}
System.Console.Write(((I)new C()).P); // inaccessible previously, writes 1 now
partial interface I
{
partial int P { get; } // implicitly public now
partial int P => 1;
}
class C : I;