为什么我不能写Nullable<Nullable<int>>?

11

Nullable<T>的定义如下:

[SerializableAttribute]
public struct Nullable<T> where T : struct, new()

约束条件where T : struct意味着T只能是值类型。因此,我非常清楚我不能写成:

Nullable<string> a; //error. makes sense to me

因为string是引用类型而不是值类型。但我真的不明白为什么不能写。

Nullable<Nullable<int>> b; //error. but why?

为什么不允许呢?毕竟,Nullable<int>是一个值类型,因此它可以作为Nullablle<T>的类型参数。当我在ideone上编译时,会出现以下错误(ideone):

error CS0453: 'int?'类型必须是非可空值类型才能在泛型类型或方法'System.Nullable'中用作类型参数 Compilation failed: 1 error(s), 0 warnings


2
抱歉,我的答案是错误的。语句new Nullable<int>();可以编译(产生一个值为null的int?),尽管默认构造函数未被记录。 - BoltClock
是的,当您尝试编译代码时,答案就在错误信息中。 - BoltClock
@rudolf_franek:不管那意味着什么,但我的重点在于类型约束,而不是代码的语义含义。 - Nawaz
正如我所说,我正在专注于约束类型,根据这个类型,即使语义上没有意义,代码也应该能够编译。 - Nawaz
@rudolf_franek:抱歉,您的评论并没有帮助我们理解这里的语言。 - Nawaz
5个回答

10

由于此规范在C#规范(第4.4.4节)中有所说明:

如果约束是值类型约束(struct),则类型A必须满足以下条件之一:

  • A是结构体类型或枚举类型,但不是可空类型。请注意,System.ValueType和System.Enum是不满足此约束的引用类型。
  • A是具有值类型约束(§10.1.5)的类型参数。

1
请详细说明规格报价。我没有完全理解。我是C#的新手。 - Nawaz
你应用于类的 struct 泛型类型约束被指定为“除了 Nullable<T> 之外的所有值类型和枚举类型”,特别是为了防止这种循环性。 - thecoop
@thecoop:这不是一个奇点。如果Nullable的类型参数没有struct约束,那么可以有一个Dictionary<TKey,TValue>包括一个方法Nullable<TValue> TryGetValue(TKey),而无需使用out参数。给定Dictionary<String, Nullable<int>> myDict,在var it=myDict.TryGetValue("Fred");之后,如果it.HasValue为false,则意味着未找到键;如果it.HasValue为true但it.Value.HasValue`为false,则意味着为键“Fred”存储了空值。真正的困难源于微软决定... - supercat
将默认值为Nullable<T>的盒子作为null而不是默认值为Nullable<T>,这使得不同类型的空可空对象无法区分。 - supercat

6
从 C# 语言规范的 4.1.10 节中得知:
非空值类型是指除了 System.Nullable 和它的简写 T?(对于任何 T)之外的所有值类型,以及被限制为非空值类型的任何类型参数(即具有 struct 约束的任何类型参数)。System.Nullable 类型指定了 T 的值类型约束(§10.1.5),这意味着可空类型的底层类型可以是任何非空值类型。可空类型的底层类型不能是可空类型或引用类型。例如,int?? 和 string? 都是无效类型。

4
从C# 4规范的§10.1.5中得知:
值类型约束指定用于类型参数的类型参数必须是非空值类型。所有非空结构类型、枚举类型和具有值类型约束的类型参数都满足此约束。请注意,虽然可分类为值类型,但可空类型(§4.1.10)不满足值类型约束。具有值类型约束的类型参数不能同时具有构造函数约束。

2

这并不是完全的答案,只是提供一些思考。

第一轮

Nullable<Nullable<int>> a;

错误 CS0453:类型“int?”必须是非可空值类型,才能将其用作泛型类型或方法“Nullable”的参数“T”

智能感知提示... 名称可以简化


第二轮

Nullable<int?> a;

错误 CS0453:类型“int?”必须是非可空值类型,才能将其用作泛型类型或方法“Nullable”的参数“T”

智能感知提示... 名称可以简化


第三轮

int?? a;

错误 CS1519:类、结构或接口成员声明中的无效标记“??”

错误 CS1525:无效的表达式项“??”


结论

int? 本质上只是 Nullable<int> 的简写,但没有类似于 int?? 这样表示 Nullable<Nullable<int>> 的方法。另外,int?? 借用了null合并运算符,所以我很高兴这是不可能的,因为它看起来很糟糕。想象一下 int????????????? a; 多么无意义。

最后,由于Nullable的参考源代码没有任何强制执行此约束的限制条件,我的猜测是当可空值类型引入C#时,该约束被嵌入到CLR中,作为一个特殊情况。


2

正如其他人所说,规范禁止这样做。

深入挖掘,值得注意的是你可以创建自己的结构体来允许这种模式:

struct Nestable<T> where T : struct { /* ... */ }

new Nestable<Nestable<int>>(); // This works just fine

禁止嵌套的可空类型无法使用我们可用的类型系统来表达。它仅由编译器中的一个特殊情况(CS0453)强制执行。 另外: 在问题中显示的new()约束实际上不存在于System.Nullable<T>上。当使用struct约束时,new()约束是被禁止的。

CS0451:“new()”约束不能与“struct”约束一起使用

所有结构体都支持默认初始化。

根据文档,new() 约束已不再存在:https://referencesource.microsoft.com/#mscorlib/system/nullable.cs - Matthew Layton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接