这里的问题似乎是在复杂数组类型声明中存在“规范不一致性”,即无法确定是从左到右作为外部到内部读取,还是从内到外读取。然而,鉴于这些不一致性,编译器和运行时似乎正在按照规定的方式运行。
让我们来审查一下规范:
数组类型声明如果没有可空注释,应该从左边的元素类型开始读取,然后按外到内的顺序读取等级声明。
参见
C# 7.0 Specification 17.2.1 Arrays:
“数组类型写作非数组类型后跟一个或多个等级说明符......在最终的非数组元素类型之前读取等级说明符。”
例如:
T[][,,][,]
中的类型是一个一维数组、三维数组、二维数组和 int 的单一维度数组。
以及
8.2.1 General 中的语法规范
值得注意的是,等级说明符语法
不包括可空注释。
可空注释打断了数组等级声明的外部到内部读取,并引入了内向外的读取。
可以在
Nullable Reference Types Specification 中找到可空注释的语法:
type
: value_type
| reference_type
| nullable_type_parameter
| type_parameter
| type_unsafe
;
reference_type
: ...
| nullable_reference_type
;
nullable_reference_type
: non_nullable_reference_type '?'
;
non_nullable_reference_type
: reference_type
;
nullable_type_parameter
: non_nullable_non_value_type_parameter '?'
;
non_nullable_non_value_type_parameter
: type_parameter
;
在 nullable_reference_type 中的 non_nullable_reference_type 必须是一个非空引用类型(类、接口、委托或
数组)。
需要注意的是,
rank_specifier
语法未被修改。相反,可空注释
?
只允许在数组类型的
结尾。
因此,
string?[,]?[]
实际上按内到外读取,作为字符串的多个一维数组的二维数组,而
string? [,][]
按外到内读取,作为多个一维数组的二维数组的交错二维数组。
要确认,请注意以下 .NET 7 中的编译成功代码:
string? [,][] d = new string[1,2][]; // 编译(毫无疑问)
string? []?[,] f = new string[1,2][]; // 编译(惊喜!)
Assert.That(d.GetType() == f.GetType()); // 不会失败
演示实例
here。
对于复杂嵌套的交错数组类型,
GetType().Name
显然应该按内到外读取。
我找不到任何地方有 MSFT 记录这一点,但
mono source 明确地展示了数组类型名称通过递归构建获取元素类型的类型名称,并将等级规范添加到末尾。
要确认,请看下面的代码行
Console.WriteLine($"typeof(string? [,][]) = {typeof(string? [,][])}");
Console.WriteLine($"typeof(string? [,][]).GetElementType().Name = {typeof(string? [,][]).GetElementType()!.Name}");
输出:
typeof(string? [,][]) = System.String[][,]
typeof(string? [,][]).GetElementType().Name = String[]
现在,如果你觉得需要写类似下面这样的代码很不可接受或者难以理解:
string? []?[,] f = new string[1,2][];
您可以考虑引入扩展方法来隐藏不一致性,例如:
public static partial class ArrayExtensions
{
public static T[]?[,] ToArrayOfNullable<T>(this T [,][] a) => a;
public static T[,]?[] ToArrayOfNullable<T>(this T [][,] a) => a;
}
方法应该是通用的,但是对于你在代码中使用的不同的锯齿形和多维数组等级模式,你需要多个方法来注入可空注释。完成后,您将能够编写以下内容:
var nullableArray = (new string?[1,2][]).ToArrayOfNullable();
nullableArray[0, 0] = null; // No warning
nullableArray[0, 0] = new string? [] { null }; // No warning
而且你的代码看起来会很干净(在扩展方法之外)。
注:
演示#2 在这里。