自 .NET 7.0.100 以来到 .NET 8.0.100 的 Roslyn 重大更改

本文档列出了 .NET 7 正式发布(.NET SDK 版本 7.0.100)至 .NET 8 正式发布(.NET SDK 版本 8.0.100)之后 Roslyn 中已知的重大变化。

动态参数的 Ref 修饰符应与相应参数的 ref 修饰符兼容

Visual Studio 2022 版本 17.10 中引入

动态参数的 Ref 修饰符应与编译时相应参数的 ref 修饰符兼容。 这可能会导致涉及动态参数的重载解析在编译时(而不是运行时)失败。

以前,编译时允许不匹配,从而将重载解析失败延迟到运行时。

例如,以下代码用于在没有错误的情况下进行编译,但失败并出现异常:“Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:'C.f(ref object)' 的最佳重载方法匹配具有一些无效的参数“。现在将生成编译错误。

public class C
{
    public void f(ref dynamic a) 
    {
    }
    
    public void M(dynamic d)
    {
        f(d); // error CS1620: Argument 1 must be passed with the 'ref' keyword
    }
}

实现类型的 IEnumerable 的集合表达式中,必须包含可以隐式转换为 object 的元素。

Visual Studio 2022 版本 17.10 中引入

将集合表达式转换structclass,其中实现了 System.Collections.IEnumerable不具有强类型化 GetEnumerator(),这要求集合表达式中的元素能够隐式转换为 object。 以前,假定面向 IEnumerable 实现的集合表达式的元素可转换为 object,并且仅在绑定到适用 Add 方法时转换。

这一附加要求意味着集合表达式转换为 IEnumerable 实现时,它们与其他目标类型的处理方式一致,其中集合表达式中的元素必须能够隐式转换为目标类型的迭代类型。

此更改会影响面向 IEnumerable 实现的集合表达式,其中元素依赖于强类型 Add 方法参数类型的目标类型。 在下面的示例中,报告错误的是 _ => { } 无法隐式转换为 object

class Actions : IEnumerable
{
    public void Add(Action<int> action);
    // ...
}

Actions a = [_ => { }]; // error CS8917: The delegate type could not be inferred.

若要解决此错误,可以显式键入元素表达式。

a = [(int _) => { }];          // ok
a = [(Action<int>)(_ => { })]; // ok

集合表达式目标类型必须具有构造函数和 Add 方法

Visual Studio 2022 版本 17.10 中引入

将集合表达式转换structclass,实现 System.Collections.IEnumerable没有CollectionBuilderAttribute,要求目标类型具有可访问构造函数,该构造函数可以在没有参数的情况下调用,并且,如果集合表达式不为空,则目标类型必须具有可使用单个参数调用的可访问 Add 方法。

以前,构造函数和 Add 方法是 构造 集合实例所必需的,但不需要 进行转换。 这意味着以下调用不明确,因为两者都是char[]string集合表达式的有效目标类型。 调用不再模糊,因为 string 没有无参数构造函数或 Add 方法。

Print(['a', 'b', 'c']); // calls Print(char[])

static void Print(char[] arg) { }
static void Print(string arg) { }

ref 参数可以传递给 in 参数

Visual Studio 2022 版本 17.8p2 中引入

功能 ref readonly 参数放宽了重载解析,允许在将 ref 设置为 12 或更高时将 in 个参数传递给 LangVersion 个参数。 这可能会导致行为或源代码破坏性更改:

var i = 5;
System.Console.Write(new C().M(ref i)); // prints "E" in C# 11, but "C" in C# 12
System.Console.Write(E.M(new C(), ref i)); // workaround: prints "E" always

class C
{
    public string M(in int i) => "C";
}
static class E
{
    public static string M(this C c, ref int i) => "E";
}
var i = 5;
System.Console.Write(C.M(null, ref i)); // prints "1" in C# 11, but fails with an ambiguity error in C# 12
System.Console.Write(C.M((I1)null, ref i)); // workaround: prints "1" always

interface I1 { }
interface I2 { }
static class C
{
    public static string M(I1 o, ref int x) => "1";
    public static string M(I2 o, in int x) => "2";
}

在异步 using 中更倾向于基于模式而不是基于接口的处理

Visual Studio 2022 版本 17.10p3 中引入

异步 using 更倾向于使用基于模式的 DisposeAsync() 方法而不是基于接口的 IAsyncDisposable.DisposeAsync() 方法进行绑定。

例如,将选取公共 DisposeAsync() 方法,而不是专用接口实现:

await using (var x = new C()) { }

public class C : System.IAsyncDisposable
{
    ValueTask IAsyncDisposable.DisposeAsync() => throw null; // no longer picked

    public async ValueTask DisposeAsync()
    {
        Console.WriteLine("PICKED");
        await Task.Yield();
    }
}