C# 编译器的每个版本中可能会引入新的警告和错误。 如果现有代码可以报告新的警告,这些警告将在一个称为“警告波 (Warning Wave)”的可选系统下引入。 选择加入系统意味着,如果不采取操作来启用这些警告,就不应在现有代码中看到新的警告。 指定 <TreatWarningsAsErrors>true</TreatWarningsAsErrors> 后,启用的警告波警告将生成错误。 C# 9 中添加了警告波5诊断功能。 C# 10 中添加了第6波警告诊断。 C# 11 中添加了第 7 级警告波诊断。 C# 12 中添加了警告波 8 诊断。 C# 13 中添加了第 9 波警告诊断。 C# 14 中添加了第 10 波警告诊断。
从 .NET 7 SDK(C# 11 开始),生成系统使用以下规则设置警告波:
- AnalysisLevel 跟踪当前 TFM(如果未指定)
- 如果当前 TFM 是“latest”TFM(根据我们需要更新的属性定义),则 AnalysisLevel 会被设置为最新。
- WarningLevel 应跟踪当前 TFM(如果未指定)
- WarningLevel 不应替代用户提供的值
- 如果项目是 .NET Framework 项目,则应将 WarningLevel 设置为 4
对于早于 .NET 7 的 SDK,AnalysisLevel 始终重写 WarningLevel。
CS9265 - 字段永远不会被重新分配给,并且将始终具有其默认值
警告波 10
在ref struct中,ref字段如果从未被 ref 分配,始终具有其默认值,即 null 引用。 以下代码生成 CS9265:
ref struct Container
{
// CS9265: Field 'value' is never ref-assigned to,
// and will always have its default value (null reference)
public ref int value;
}
若要解决此警告,请在字段初始化器中或在所有构造函数代码路径上使用 ref 分配该字段。 或者,如果字段不需要引用,请从字段声明中删除 ref 修饰符。
CS9123 - 不应对异步方法中的参数或局部变量使用“>”运算符
警告波 8
& 运算符不应该用于异步方法中的参数或局部变量。
下面的代码生成 CS9123:
public static async Task LogValue()
{
int x = 1;
unsafe {
int* y = &x;
Console.WriteLine(*y);
}
await Task.Delay(1000);
}
CS8981 - 类型名称仅包含小写 ascii 字符。 这些名称可能会被该语言专用。
警告波 7
为 C# 添加的任何新关键字都是小写 ASCII 字符。 此警告可确保任何类型都不会与将来的关键字冲突。 下面的代码生成 CS8981:
public class lowercasename
{
}
可以通过重命名类型以包含至少一个非小写 ASCII 字符(例如大写字符、数字或下划线)来解决此警告。
CS8826 - 部分方法声明存在签名差异。
警告波 6
此警告修正了在报告部分方法签名之间差异时的一些不一致之处。 当分部方法签名创建不同的 CLR 签名时,编译器始终报告错误。 现在,当签名为在语法上有差异的 C# 时,编译器会报告 CS8826。 考虑以下分部类:
public partial class PartialType
{
public partial void M1(int x);
public partial T M2<T>(string s) where T : struct;
public partial void M3(string s);
public partial void M4(object o);
public partial void M5(dynamic o);
public partial void M6(string? s);
}
以下分部类实现生成多个 CS8626 示例:
public partial class PartialType
{
// Different parameter names:
public partial void M1(int y) { }
// Different type parameter names:
public partial TResult M2<TResult>(string s) where TResult : struct => default;
// Relaxed nullability
public partial void M3(string? s) { }
// Mixing object and dynamic
public partial void M4(dynamic o) { }
// Mixing object and dynamic
public partial void M5(object o) { }
// Note: This generates CS8611 (nullability mismatch) not CS8826
public partial void M6(string s) { }
}
注意
如果一个方法的实现使用不可为 null 的引用类型,而其他声明接受可为 null 的引用类型,则生成 CS8611,而不是 CS8826。
若要修复这些警告的任何实例,请确保两个签名匹配。
CS7023 - “is”或“as”运算符的第二个操作数可能不是静态类型
警告波 5
is 和 as 表达式始终返回 false 静态类型,因为你无法创建静态类型的实例。 下面的代码生成 CS7023:
static class StaticClass
{
public static void Thing() { }
}
void M(object o)
{
// warning: cannot use a static type in 'is' or 'as'
if (o is StaticClass)
{
Console.WriteLine("Can't happen");
}
else
{
Console.WriteLine("o is not an instance of a static class");
}
}
编译器报告此警告,因为类型测试永远无法成功。 若要修正此警告,请删除测试,并删除仅在测试成功时才执行的任何代码。 在前面的示例中,该else子句总是被执行。 可以将方法主体替换为一行代码:
Console.WriteLine("o is not an instance of a static class");
CS8073 - 表达式的结果始终为“value”,因为类型为“type”的值绝不等于类型“type”的“null”
警告波 5
将 == 类型的实例与 != 进行比较时,false 和 true 运算符始终返回 struct(或 null)。 下面的代码对此警告进行了演示。 假设 S 是一个 struct,它定义 operator == 和 operator !=:
class Program
{
public static void M(S s)
{
if (s == null) { } // CS8073: The result of the expression is always 'false'
if (s != null) { } // CS8073: The result of the expression is always 'true'
}
}
struct S
{
public static bool operator ==(S s1, S s2) => s1.Equals(s2);
public static bool operator !=(S s1, S s2) => !s1.Equals(s2);
public override bool Equals(object? other)
{
// Implementation elided
return false;
}
public override int GetHashCode() => 0;
// Other details elided...
}
若要修复此错误,请删除在对象为 null 时将执行的 null 检查和代码。
CS8848 - 由于优先级,无法在此处使用运算符“from”。 使用括号消除歧义。
警告波 5
下面的示例演示了这一警告。 由于运算符的优先级,表达式绑定不正确。
bool b = true;
var source = new Src();
b = true;
source = new Src();
var a = b && from c in source select c;
Console.WriteLine(a);
var indexes = new Src2();
int[] array = { 1, 2, 3, 4, 5, 6, 7 };
var range = array[0..from c in indexes select c];
若要修复此错误,请用括号将查询表达式括起来:
bool b = true;
var source = new Src();
b = true;
source = new Src();
var a = b && (from c in source select c);
Console.WriteLine(a);
var indexes = new Src2();
int[] array = { 1, 2, 3, 4, 5, 6, 7 };
var range = array[0..(from c in indexes select c)];
成员必须全部分配。 使用未赋值的变量(CS8880、CS8881、CS8882、CS8883、CS8884、CS8885、CS8886、CS8887)
警告波 5
多个警告优化了导入的汇编程序中声明的 struct 类型的确定性赋值分析。 当导入程序集中的结构包含引用类型的不可访问的字段时(通常为 private 字段),将生成所有这些新警告,如以下示例所示:
public struct Struct
{
private string data = String.Empty;
public Struct() { }
}
以下示例显示了改进的明确赋值分析中生成的警告:
- CS8880:在控制返回调用方之前,自动实现的属性“Property”必须完全赋值。 请考虑更新到语言版本“version”以自动默认该属性。
- CS8881:在控制返回调用方之前,字段“field”必须完全赋值。 请考虑将语言版本“version”更新为自动默认字段。
- CS8882:
out参数“parameter”必须在控制流离开当前方法之前被赋值。 - CS8883:使用了可能未赋值的自动实现的属性“Property”。
- CS8884:使用了可能未赋值的字段“Field”
- CS8885:在分配所有字段之前,不能使用“this”对象。 请考虑更新到语言版本“version”,以自动将未分配的字段设置为默认值。
- CS8886:使用未分配的 out 参数 “parameterName”。
- CS8887:使用了未赋值的局部变量“variableName”
public struct DefiniteAssignmentWarnings
{
// CS8880
public Struct Property { get; }
// CS8881
private Struct field;
// CS8882
public void Method(out Struct s)
{
}
public DefiniteAssignmentWarnings(int dummy)
{
// CS8883
Struct v2 = Property;
// CS8884
Struct v3 = field;
// CS8885:
DefiniteAssignmentWarnings p2 = this;
}
public static void Method2(out Struct s1)
{
// CS8886
var s2 = s1;
s1 = default;
}
public static void UseLocalStruct()
{
Struct r1;
var r2 = r1;
}
}
可以通过初始化或将导入的结构赋值为默认值来修复上述任何警告:
public struct DefiniteAssignmentNoWarnings
{
// CS8880
public Struct Property { get; } = default;
// CS8881
private Struct field = default;
// CS8882
public void Method(out Struct s)
{
s = default;
}
public DefiniteAssignmentNoWarnings(int dummy)
{
// CS8883
Struct v2 = Property;
// CS8884
Struct v3 = field;
// CS8885:
DefiniteAssignmentNoWarnings p2 = this;
}
public static void Method2(out Struct s1)
{
// CS8886
s1 = default;
var s2 = s1;
}
public static void UseLocalStruct()
{
Struct r1 = default;
var r2 = r1;
}
}
CS8892 - 方法不会用作入口点,因为找到了同步入口点“method”。
警告波 5
如果有多个有效入口点,其中包含一个或多个同步入口点,则将在所有异步入口点候选项上生成此警告。
下面的示例生成 CS8892:
public static void Main()
{
RunProgram();
}
// CS8892
public static async Task Main(string[] args)
{
await RunProgramAsync();
}
注意
编译器始终使用同步入口点。 如果有多个同步入口点,则会出现编译器错误。
若要修复此警告,请删除或重命名异步入口点。
CS8897 - 静态类型不能用作参数
警告波 5
接口的成员无法声明其类型为静态类的参数。 以下代码演示了 CS8897 和 CS8898:
public static class Utilities
{
// elided
}
public interface IUtility
{
// CS8897
public void SetUtility(Utilities u);
// CS8898
public Utilities GetUtility();
}
若要修复此警告,请更改参数类型或删除方法。
CS8898 - 静态类型不能用作返回类型
警告波 5
接口的成员无法声明静态类的返回类型。 以下代码演示了 CS8897 和 CS8898:
public static class Utilities
{
// elided
}
public interface IUtility
{
// CS8897
public void SetUtility(Utilities u);
// CS8898
public Utilities GetUtility();
}
若要修复此警告,请更改返回类型或删除方法。