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

本文档列出了在 .NET 6 常规版本(.NET SDK 版本 6.0.100)到 .NET 7 常规版本(.NET SDK 版本 7.0.100)之后的 Roslyn 中已知的中断性变更。

异步方法中不允许所有受限类型的局部变量

Visual Studio 2022 版本 17.6p1 中引入

异步方法中不允许使用受限制类型的局部变量。 但在早期版本中,编译器无法注意到一些隐式声明的局部变量。 例如,在 foreachusing 语句或解构中。
现在,这种隐式声明的本地变量也不被允许。

ref struct RefStruct { public void Dispose() { } }
public class C 
{
    public async Task M() 
    {
        RefStruct local = default; // disallowed
        using (default(RefStruct)) { } // now disallowed too ("error CS9104: A using statement resource of this type cannot be used in async methods or async lambda expressions")
    }
}

请参见https://github.com/dotnet/roslyn/pull/66264

指针必须始终处于不安全的上下文中。

Visual Studio 2022 版本 17.6 中引入

在前面的 SDK 中,编译器偶尔会允许引用指针的位置,而不会将该位置显式标记为不安全。 现在, unsafe 修饰符必须存在。
例如 using Alias = List<int*[]>; ,应更改为 using unsafe Alias = List<int*[]>; 合法。
应将用法(例如 void Method(Alias a) ... )更改为 unsafe void Method(Alias a) ...

规则是无条件的,别名 using 声明除外(C# 12 之前不允许 unsafe 修饰符)。
因此,对于 using 声明,仅当选择语言版本为 C# 12 或更高版本时,规则才会生效。

System.TypedReference 被视为托管

Visual Studio 2022 版本 17.6 中引入

今后,System.TypedReference 类型会被视为托管。

unsafe
{
    TypedReference* r = null; // warning: This takes the address of, gets the size of, or declares a pointer to a managed type
    var a = stackalloc TypedReference[1]; // error: Cannot take the address of, get the size of, or declare a pointer to a managed type
}

Ref 安全错误不会影响从 lambda 表达式到委托的转换。

Visual Studio 2022 版本 17.5 中引入

Lambda 主体中报告的 Ref 安全错误不会再影响 Lambda 表达式是否可转换为委托类型。 这一变化可能会影响过载解析。

在以下示例中,对 M(x => ...) 的调用在 Visual Studio 17.5 中会产生歧义,因为 M(D1)M(D2) 现在都被认为适用,即使在 lambda 主体内对 F(ref x, ref y) 的调用将导致 M(D1) 的引用安全性(有关比较,请参阅 d1d2 中的示例)。 以前,由于认为 M(D2) 重载不适用,调用明确绑定到 M(D1)

using System;

ref struct R { }

delegate R D1(R r);
delegate object D2(object o);

class Program
{
    static void M(D1 d1) { }
    static void M(D2 d2) { }

    static void F(ref R x, ref Span<int> y) { }
    static void F(ref object x, ref Span<int> y) { }

    static void Main()
    {
        // error CS0121: ambiguous between: 'M(D1)' and 'M(D2)'
        M(x =>
            {
                Span<int> y = stackalloc int[1];
                F(ref x, ref y);
                return x;
            });

        D1 d1 = x1 =>
            {
                Span<int> y1 = stackalloc int[1];
                F(ref x1, ref y1); // error CS8352: 'y2' may expose referenced variables
                return x1;
            };

        D2 d2 = x2 =>
            {
                Span<int> y2 = stackalloc int[1];
                F(ref x2, ref y2); // ok: F(ref object x, ref Span<int> y)
                return x2;
            };
    }
}

要解决重载解析更改,请对 lambda 参数或委托使用显式类型。

        // ok: M(D2)
        M((object x) =>
            {
                Span<int> y = stackalloc int[1];
                F(ref x, ref y); // ok: F(ref object x, ref Span<int> y)
                return x;
            });

行首的原始字符串内插。

Visual Studio 2022 版本 17.5 中引入

在 .NET SDK 7.0.100 或更早版本中,错误地允许了以下内容:

var x = $"""
    Hello
{1 + 1}
    World
    """;

这违反了行内容(包括内插开始的位置)必须以与最后 """; 一行相同的空格开头的规则。 现在,需要将上述内容编写为:

var x = $"""
    Hello
    {1 + 1}
    World
    """;

方法的推断委托类型包括默认参数值和 params 修饰符

Visual Studio 2022 版本 17.5 中引入

在 .NET SDK 7.0.100 或更早版本中,从方法推断的委托类型忽略了默认参数值和 params 修饰符,如以下代码所示:

void Method(int i = 0, params int[] xs) { }
var action = Method; // System.Action<int, int[]>
DoAction(action, 1); // ok
void DoAction(System.Action<int, int[]> a, int p) => a(p, new[] { p });

在 .NET SDK 7.0.200 或更高版本中,此类方法被推断为具有相同默认参数值和 params 修饰符的匿名合成委托类型。 此更改可能会中断上述代码,如下所示:

void Method(int i = 0, params int[] xs) { }
var action = Method; // delegate void <anonymous delegate>(int arg1 = 0, params int[] arg2)
DoAction(action, 1); // error CS1503: Argument 1: cannot convert from '<anonymous delegate>' to 'System.Action<int, int[]>'
void DoAction(System.Action<int, int[]> a, int p) => a(p, new[] { p });

可以在相关 建议中了解有关此更改的详细信息。

为了进行明确的赋值分析,异步本地函数的调用不再被视为等待

Visual Studio 2022 版本 17.5 中引入

为了进行明确的赋值分析,不再将异步本地函数的调用视为等待,因此,不会将本地函数视为完全执行。 请参阅 https://github.com/dotnet/roslyn/issues/43697 以了解其基本原理。

下面的代码现在将报告明确的分配错误:

    public async Task M()
    {
        bool a;
        await M1();
        Console.WriteLine(a); // error CS0165: Use of unassigned local variable 'a'  

        async Task M1()
        {
            if ("" == String.Empty)
            {
                throw new Exception();
            }
            else
            {
                a = true;
            }
        }
    }

属性的 INoneOperation 节点现在是 IAttributeOperation 节点。

Visual Studio 2022 版本 17.5、.NET SDK 版本 7.0.200 中引入

在早期版本的编译器中,IOperation 属性的树以 INoneOperation 节点为根节点。 我们添加了对属性的原生支持,因此意味着树的根现在是一个 IAttributeOperation。 某些分析器(包括旧版 .NET SDK 分析器)不需要此树形状,在遇到此树时会错误地发出警告(或可能无法发出警告)。 解决方法如下:

  • 如果可能,请更新分析器版本。 如果使用 .NET SDK 或旧版 Microsoft.CodeAnalysis.FxCopAnalyzers,请更新到 Microsoft.CodeAnalysis.NetAnalyzers 7.0.0-preview1.22464.1 或更高版本。
  • 抑制分析器中的任何误报,直到可以使用考虑到此变化的版本进行更新。

不支持结构 ref 的类型测试。

Visual Studio 2022 版本 17.4 中引入

在某些情况下,当在“is”或“as”运算符中使用结构类型时,编译器之前会错误地报告类型测试在运行时始终失败的警告,省略了实际的类型检查,导致行为不正确。 当执行时可能出现错误行为时,编译器现在会生成错误。

ref struct G<T>
{
    public void Test()
    {
        if (this is G<int>) // Will now produce an error, used to be treated as always `false`.
        {

未使用的引用本地结果是取消引用。

Visual Studio 2022 版本 17.4 中引入

当一个 ref 局部变量按值引用时,但结果未被使用(例如被赋值给一个被丢弃的变量),则该结果此前会被忽略。 编译器现在将对该局部变量进行解引用,以便确保任何副作用都能被观察到。

ref int local = Unsafe.NullRef<int>();
_ = local; // Will now produce a `NullReferenceException`

类型不能命名 scoped

Visual Studio 2022 版本 17.4 中引入。 从 C# 11 开始,无法命名 scoped类型。 编译器将对所有此类类型名称报告错误。 为了解决这一问题,类型名称及其所有用法都必须使用 @进行转义:

class scoped {} // Error CS9056
class @scoped {} // No error
ref scoped local; // Error
ref scoped.nested local; // Error
ref @scoped local2; // No error

这是因为 scoped 现在是变量声明的修饰符,并且在引用类型中保留了 ref 之后的位置。

类型不能命名 file

Visual Studio 2022 版本 17.4 中引入。 从 C# 11 开始,无法命名 file类型。 编译器将对所有此类类型名称报告错误。 为了解决这一问题,类型名称及其所有用法都必须使用 @进行转义:

class file {} // Error CS9056
class @file {} // No error

这是因为 file 现在已成为类型声明的修饰符。

可以在关联的 csharplang 问题中了解有关此更改的详细信息。

#line span 指令中所需的空格

在.NET SDK 6.0.400和Visual Studio 2022版本17.3中引入。

在 C# 10 中引入 #line span 指令时,它没有特定的间距要求。
例如,此值有效: #line(1,2)-(3,4)5"file.cs"

在 Visual Studio 17.3 中,编译器在第一个括号、字符偏移量和文件名之前需要空格。
因此,除非添加了空格,否则上述示例无法分析。 #line (1,2)-(3,4) 5 "file.cs"

System.IntPtr 和 System.UIntPtr 上已验证的运算符

在 .NET SDK 7.0.100 和 Visual Studio 2022 版本 17.3 中引入。

当平台支持数值型IntPtrUIntPtr 类型(如 System.Runtime.CompilerServices.RuntimeFeature.NumericIntPtr 所示)时,来自 nintnuint 的内置运算符适用于这些基础类型。 这意味着在这些平台上,IntPtrUIntPtr 具有内置的 checked 运算符,当发生溢出时,这些运算符现在会引发异常。

IntPtr M(IntPtr x, int y)
{
    checked
    {
        return x + y; // may now throw
    }
}

unsafe IntPtr M2(void* ptr)
{
    return checked((IntPtr)ptr); // may now throw
}

可能的解决方法包括:

  1. 指定 unchecked 上下文
  2. 降级到不支持数字 IntPtr/UIntPtr 类型的平台/TFM

此外,IntPtr/UIntPtr 与其他数值类型之间的隐式转换在此类平台上被视为标准转换。 在某些情况下,这可能会影响重载解析。

如果用户代码依赖于未检查上下文中的溢出异常,或者不希望在已检查的上下文中出现溢出异常,则这些更改可能会导致行为更改。 7.0 中添加了一个分析器,以帮助检测此类行为更改并采取适当的措施。 分析器将生成关于潜在行为变化的诊断信息,这些信息默认设置为信息级别的严重性,但可以通过 editorconfig 将其升级为警告级别。

添加 System.UIntPtr 和 System.Int32

在 .NET SDK 7.0.100 和 Visual Studio 2022 版本 17.3 中引入。

当平台支持数字IntPtrUIntPtr类型(如存在System.Runtime.CompilerServices.RuntimeFeature.NumericIntPtr)时,不能再使用在其中+(UIntPtr, int)定义的运算符System.UIntPtr。 相反,添加类型为 System.UIntPtrSystem.Int32 的表达式会导致错误:

UIntPtr M(UIntPtr x, int y)
{
    return x + y; // error: Operator '+' is ambiguous on operands of type 'nuint' and 'int'
}

可能的解决方法包括:

  1. 使用 UIntPtr.Add(UIntPtr, int) 此方法: UIntPtr.Add(x, y)
  2. 对第二个操作数应用未经检查的强制类型转换至类型 nuintx + unchecked((nuint)y)

方法或本地函数的属性中的 Nameof 运算符

在.NET SDK 6.0.400和Visual Studio 2022版本17.3中引入。

当语言版本为 C# 11 或更高版本时, nameof 方法的属性中的运算符在作用域中引入该方法的类型参数。 这同样适用于本地函数。
方法的属性、类型参数或参数中的 nameof 运算符会将该方法的参数引入范围。 这同样适用于局部函数、lambda 表达式、委托和索引器。

例如,以下情况现在将被视为错误:

class C
{
  class TParameter
  {
    internal const string Constant = """";
  }
  [MyAttribute(nameof(TParameter.Constant))]
  void M<TParameter>() { }
}
class C
{
  class parameter
  {
    internal const string Constant = """";
  }
  [MyAttribute(nameof(parameter.Constant))]
  void M(int parameter) { }
}

可能的解决方法包括:

  1. 重命名类型参数或参数,以避免与外部范围中的名称重复。
  2. 使用字符串文本而不是 nameof 运算符。

无法通过引用返回输出参数

在 .NET SDK 7.0.100 和 Visual Studio 2022 版本 17.3 中引入。

使用语言版本 C# 11 或更高版本或 .NET 7.0 或更高版本时, out 不能通过引用返回参数。

static ref T ReturnOutParamByRef<T>(out T t)
{
    t = default;
    return ref t; // error CS8166: Cannot return a parameter by reference 't' because it is not a ref parameter
}

可能的解决方法包括:

  1. 使用 System.Diagnostics.CodeAnalysis.UnscopedRefAttribute 标记该引用为无范围。

    static ref T ReturnOutParamByRef<T>([UnscopedRef] out T t)
    {
        t = default;
        return ref t; // ok
    }
    
  2. 更改方法签名,以通过 ref 来传递参数。

    static ref T ReturnRefParamByRef<T>(ref T t)
    {
        t = default;
        return ref t; // ok
    }
    

引用结构体上的实例方法可能会捕获未声明范围的引用参数

已在 .NET SDK 7.0.100,Visual Studio 2022 版本 17.4 中推出。

使用 C# 11 或更高版本,或 .NET 7.0 或更高版本时,对 ref struct 实例方法的调用默认会捕获未声明范围的 refin 参数。

R<int> Use(R<int> r)
{
    int i = 42;
    r.MayCaptureArg(ref i); // error CS8350: may expose variables referenced by parameter 't' outside of their declaration scope
    return r;
}

ref struct R<T>
{
    public void MayCaptureArg(ref T t) { }
}

如果实例方法中ref未捕获参数inref struct,则可能的解决方法是将参数声明为scoped refscoped in

R<int> Use(R<int> r)
{
    int i = 42;
    r.CannotCaptureArg(ref i); // ok
    return r;
}

ref struct R<T>
{
    public void CannotCaptureArg(scoped ref T t) { }
}

方法引用结构返回转义分析依赖于引用参数的引用转义

已在 .NET SDK 7.0.100,Visual Studio 2022 版本 17.4 中推出。

使用语言版本 C# 11 或更高版本,或者使用 .NET 7.0 或更高版本,从方法调用返回的 ref struct(作为返回值或 out 参数)只有当方法调用的所有 ref 参数都是 in 时才是 safe-to-escape这些 in 参数可能包括隐式默认参数值。

ref struct R { }

static R MayCaptureArg(ref int i) => new R();

static R MayCaptureDefaultArg(in int i = 0) => new R();

static R Create()
{
    int i = 0;
    // error CS8347: Cannot use a result of 'MayCaptureArg(ref int)' because it may expose
    // variables referenced by parameter 'i' outside of their declaration scope
    return MayCaptureArg(ref i);
}

static R CreateDefault()
{
    // error CS8347: Cannot use a result of 'MayCaptureDefaultArg(in int)' because it may expose
    // variables referenced by parameter 'i' outside of their declaration scope
    return MayCaptureDefaultArg();
}

如果参数refin未在ref struct返回值中捕获,则可能的解决方法是将参数声明为scoped refscoped in

static R CannotCaptureArg(scoped ref int i) => new R();

static R Create()
{
    int i = 0;
    return CannotCaptureArg(ref i); // ok
}

refref struct 参数在 __arglist 中被视为未声明范围

已在 .NET SDK 7.0.100,Visual Studio 2022 版本 17.4 中推出。

使用语言版本 C# 11 或更高版本,或者使用 .NET 7.0 或更高版本,当将 refref struct 类型作为参数传递给 __arglist 时,它们被视为未声明范围的引用。

ref struct R { }

class Program
{
    static void MayCaptureRef(__arglist) { }

    static void Main()
    {
        var r = new R();
        MayCaptureRef(__arglist(ref r)); // error: may expose variables outside of their declaration scope
    }
}

无符号右移运算符

.NET SDK 6.0.400 和 Visual Studio 2022 版本 17.3 中引入了。 该语言添加了对“未签名右移”运算符(>>>)的支持。 这会禁用将实现用户定义的“无符号右移”运算符的方法作为常规方法使用的能力。

例如,存在用某种语言(VB 或 C# 除外)开发的现有库,它为类型 C1 公开了“无符号右移”用户定义运算符。 以下代码以前可以成功编译:

static C1 Test1(C1 x, int y) => C1.op_UnsignedRightShift(x, y); //error CS0571: 'C1.operator >>>(C1, int)': cannot explicitly call operator or accessor

可能的解决方法是切换到使用 >>> 运算符:

static C1 Test1(C1 x, int y) => x >>> y;

Foreach 枚举器作为引用结构

在 .NET SDK 6.0.300、Visual Studio 2022 版本 17.2 中引入。如果语言版本设置为 7.3 或更早版本,则使用引用结构体枚举器类型的 foreach 会报告错误。

这修复了一个 bug,该 bug 在针对支持之前的 C# 版本的较新编译器中受支持。

可能的解决方法包括:

  1. ref struct 类型更改为 structclass 类型。
  2. <LangVersion> 元素升级到 7.3 或更高版本。

异步 foreach 倾向于基于模式 DisposeAsync,而不是 IAsyncDisposable.DisposeAsync() 的显式接口实现

在 .NET SDK 6.0.300 和 Visual Studio 2022 版本 17.2 中引入。 异步foreach更倾向于使用基于模式的DisposeAsync()方法进行绑定,而不是使用IAsyncDisposable.DisposeAsync()

例如,会选择 DisposeAsync(),而不是 IAsyncEnumerator<int>.DisposeAsync() 上的 AsyncEnumerator 方法。

await foreach (var i in new AsyncEnumerable())
{
}

struct AsyncEnumerable
{
    public AsyncEnumerator GetAsyncEnumerator() => new AsyncEnumerator();
}

struct AsyncEnumerator : IAsyncDisposable
{
    public int Current => 0;
    public async ValueTask<bool> MoveNextAsync()
    {
        await Task.Yield();
        return false;
    }
    public async ValueTask DisposeAsync()
    {
        Console.WriteLine("PICKED");
        await Task.Yield();
    }
    ValueTask IAsyncDisposable.DisposeAsync() => throw null; // no longer picked
}

此更改修复了规范违例,其中公共 DisposeAsync 方法在声明的类型上可见,而显式接口实现仅在使用接口类型引用时可见。

若要解决此问题,请从类型中删除基于 DisposeAsync 模式的方法。

禁止将转换后的字符串用作默认参数

.NET SDK 6.0.300 中引入了 Visual Studio 2022 版本 17.2。 C# 编译器会接受涉及字符串常量引用转换的错误默认参数值,并将 null 输出为常量值,而不是源代码中指定的默认值。 在 Visual Studio 17.2 中,这将成为一个错误。 请参阅 roslyn#59806

此更改修复了编译器中的规范冲突。 默认参数必须是编译时间常量。 以前的版本允许以下代码:

void M(IEnumerable<char> s = "hello")

上述声明需要从string转换到IEnumerable<char>。 编译器允许此构造,并将作为参数的值发出 null 。 上述代码从 17.2 开始生成编译器错误。

若要解决此问题,可以进行以下更改之一:

  1. 更改参数类型,以便不需要转换。
  2. 将默认参数的值更改为 null 还原以前的行为。

上下文关键字 var,作为显式 lambda 返回类型

在 .NET SDK 6.0.200、Visual Studio 2022 版本 17.1 中引入。上下文关键字 var 不能用作显式 lambda 返回类型。

此更改通过确保 lambda 表达式的返回类型仍然是自然类型,从而启用var

如果你有名称为 var 的类型,且使用显式返回类型 var 定义 lambda 表达式,则可能遇到此错误。

using System;

F(var () => default);  // error CS8975: The contextual keyword 'var' cannot be used as an explicit lambda return type
F(@var () => default); // ok
F(() => default);      // ok: return type is inferred from the parameter to F()

static void F(Func<var> f) { }

public class var
{
}

解决方法包括以下更改:

  1. 使用 @var 作为返回类型。
  2. 删除显式返回类型,以便编译器确定返回类型。

内插字符串处理程序和索引器初始化

在 .NET SDK 6.0.200、Visual Studio 2022 版本 17.1 中引入。采用内插字符串处理程序并需要接收器作为构造函数输入的索引器不能在对象始化程序中使用。

此更改不允许以下边缘情况:索引器初始化表达式使用内插字符串处理程序,并且内插字符串处理程序将索引器的接收器作为构造函数的参数。 此更改的原因是,此方案可能会导致访问尚未初始化的变量。 请看以下示例:

using System.Runtime.CompilerServices;

// error: Interpolated string handler conversions that reference
// the instance being indexed cannot be used in indexer member initializers.
var c = new C { [$""] = 1 }; 

class C
{
    public int this[[InterpolatedStringHandlerArgument("")] CustomHandler c]
    {
        get => ...;
        set => ...;
    }
}

[InterpolatedStringHandler]
class CustomHandler
{
    // The constructor of the string handler takes a "C" instance:
    public CustomHandler(int literalLength, int formattedCount, C c) {}
}

解决方法包括以下更改:

  1. 从内插字符串处理程序中删除接收方类型。
  2. 将索引器的参数更改为 string

ref、readonly ref、in、out 不允许作为参数,或在仅具有非托管调用方的方法上返回

在 .NET SDK 6.0.200、Visual Studio 2022 版本 17.1 中引入。ref/ref readonly/in/out 不允许用于具有 UnmanagedCallersOnly 属性的方法的返回/参数。

此更改是一个 bug 修复。 返回值和参数不 blittable。 按引用传递参数或返回值可能会导致未定义的行为。 以下任何声明都不进行编译:

using System.Runtime.InteropServices;
[UnmanagedCallersOnly]
static ref int M1() => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'.

[UnmanagedCallersOnly]
static ref readonly int M2() => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'.

[UnmanagedCallersOnly]
static void M3(ref int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'.

[UnmanagedCallersOnly]
static void M4(in int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'.

[UnmanagedCallersOnly]
static void M5(out int o) => throw null; // error CS8977: Cannot use 'ref', 'in', or 'out' in a method attributed with 'UnmanagedCallersOnly'.

变通方法是按引用修饰符删除。

Length、Count 假定为非负模式

在 .NET SDK 6.0.200 和 Visual Studio 2022 版本 17.1 中引入。LengthCount在对模式和开关进行集合和穷尽性分析时,假定可计数和可索引类型的属性为非负值。 这些类型可用于隐式索引器和列表模式。

分析模式时,即使LengthCount属性被类型化为int,它们仍被假定为非负值。 请考虑此示例方法:

string SampleSizeMessage<T>(IList<T> samples)
{
    return samples switch
    {
        // This switch arm prevents a warning before 17.1, but will never happen in practice.
        // Starting with 17.1, this switch arm produces a compiler error.
        // Removing it won't introduce a warning.
        { Count: < 0 }    => throw new InvalidOperationException(),
        { Count:  0 }     => "Empty collection",
        { Count: < 5 }    => "Too small",
        { Count: < 20 }   => "reasonable for the first pass",
        { Count: < 100 }  => "reasonable",
        { Count: >= 100 } => "fine",
    };
}

void M(int[] i)
{
    if (i is { Length: -1 }) {} // error: impossible under assumption of non-negative length
}

在 17.1 前,第一个 switch arm(用于测试 Count 为负)是必需的,用于避免没有覆盖所有可能值的警告。 从 17.1 开始,第一个 switch arm 会生成编译器错误。 变通方法是删除为无效情况添加的 switch arm。

此更改是在添加列表模式的一部分进行的。 如果集合上的每次使用 LengthCount 属性都被视为非负值,则处理规则更加一致。 您可以阅读关于语言设计问题变更的更多详细信息。

解决方法是删除具有无法访问条件的开关臂。

向结构体添加字段初始化器需要明确声明的构造函数。

.NET SDK 6.0.200 和 Visual Studio 2022 版本 17.1 中引入了这一功能。具有字段初始化器的类型声明必须包含显式构造函数。 此外,所有字段必须在没有struct初始值设定项的: this()实例构造函数中必须明确分配,因此,任何过去未分配的字段都必须通过添加的构造函数或字段初始值设定项进行分配。 请参阅 dotnet/csharplang#5552dotnet/roslyn#58581

可通过两种方法将变量初始化为 C# 中的默认值: new()default。 对于类,差异很明显,因为 new 创建了一个新实例并 default 返回 null。 结构的差异更为微妙,因为对于 default结构,结构返回一个实例,每个字段/属性都设置为自己的默认值。 我们在 C# 10 中添加了结构字段初始值设定项。 仅当显式声明的构造函数运行时,才会执行字段初始值设定项。 值得注意的是,在使用 default 或创建任何类型的 struct 数组时,它们不会执行。

在 17.0 中,如果有字段初始值设定项,但没有声明的构造函数,则会合成一个无参数构造函数,用于运行字段初始值设定项。 但是,这意味着添加或删除构造函数声明可能会影响是否合成无参数构造函数,因此,可能会更改其 new()行为。

若要解决此问题,在 .NET SDK 6.0.200(VS 17.1)中,编译器不再合成无参数构造函数。 struct如果包含字段初始值设定项且没有显式构造函数,编译器将生成错误。 struct如果具有字段初始值设定项,则必须声明构造函数,否则永远不会执行字段初始值设定项。

此外,除非构造函数具有struct初始值设定项,否则必须在每个: this()构造函数中分配没有字段初始值设定项的所有字段。

例如:

struct S // error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
{
    int X = 1; 
    int Y;
}

解决方法是声明构造函数。 除非字段以前未分配,否则此构造函数通常可以是空无参数构造函数:

struct S
{
    int X = 1;
    int Y;

    public S() { Y = 0; } // ok
}

格式说明符不能包含花括号

在 .NET SDK 6.0.200、Visual Studio 2022 版本 17.1 中引入。内插字符串中的格式说明符不能包含花括号({})。 在以前的版本中,{{ 被解释为转义的 {,而 }} 被解释为格式说明符中的转义字符 }。 现在,格式说明符中的第一 } 个字符结束内插,任何 { 字符都是错误的。

这使得内插字符串处理与 System.String.Format 的处理保持一致。

using System;
Console.WriteLine($"{{{12:X}}}");
//prints now: "{C}" - not "{X}}"

X 是大写十六进制的格式, C 是 12 的十六进制值。

解决方法是删除格式字符串中的额外大括号。

可以在关联的 roslyn 问题中了解有关此更改的详细信息。

类型不能命名 required

Visual Studio 2022 版本 17.3 中引入。 从 C# 11 开始,无法命名 required类型。 编译器将对所有此类类型名称报告错误。 为了解决这一问题,类型名称及其所有用法都必须使用 @进行转义:

class required {} // Error CS9029
class @required {} // No error

这样做是因为 required 现在是属性和字段的成员修饰符。

可以在关联的 csharplang 问题中了解有关此更改的详细信息。