为什么我可以在它可能不可为空或可能不是对象的情况下测试null的generics?

在编写包含generics变量的类时

public class ServiceInvoker : IDisposable { private TService _Service; public ServiceInvoker() { _Service = Activator.CreateInstance(); } public void Invoke(Action action) { // CAN use null if (_Service == null) throw new ObjectDisposedException("ServiceInvoker"); .... } public void Dispose() { // CAN'T use null this._Service = default(TService); } } 

我注意到编译器允许我检查generics变量的null,但是,当然,不允许我将其设置为null,因此我们必须使用default(TService)

不应该编译器警告我,我使用null? 或者它是否使用拳击转换到一个对象来进行null测试?

我读到了关于generics的正确null评估 ,但我很想知道为什么,而不是如何。

为什么我可以在它可能不可为空或可能不是对象的情况下测试null的generics?

这个问题是不合理的; generics类型的任何表达式的值在运行时始终是对象或空引用。 我想你想问:

当运行时类型既不是可空值类型也不是引用类型时,为什么我可以测试null的generics?

C#规范保证在7.6.10节中对文字null的相等比较是合法的,为方便起见,我在这里引用:


如果将类型参数类型T的操作数与null进行比较,并且T的运行时类型是值类型,则比较结果为false。 […]即使T可以表示值类型,也允许使用x == null结构,并且当T是值类型时,结果简单地定义为false。


请注意,此处的规范存在一个小错误; 最后一句应该结束“非可空值类型”。

我不确定我是否真的回答了“为什么?” 问题是否令人满意。 如果这不令人满意,请尝试提出更具体的问题。

不应该编译器警告我,我使用null?

不。为什么您认为编译器应该警告您正确使用该语言的function?

它是否使用拳击转换到一个对象来进行null测试?

嗯,这有点棘手。 一方面,第7.6.10节规定:


预定义的引用类型相等运算符永远不会导致其操作数发生装箱操作。 执行这样的装箱操作是没有意义的,因为对新分配的盒装实例的引用必然不同于所有其他引用。


但是,当我们生成IL以将genericsT与null进行比较时,我们当然实际上会生成一个T的框,一个null的加载和一个比较。 抖动可以足够智能以消除拳击的实际内存分配; 如果T是引用类型,那么它不需要装箱,如果它是可以为空的值类型,则可以将其转换为检查HasValue属性的调用,如果它是不可为空的值类型,那么装箱和检查可以是变成简单的“假”。 我不确切知道各种不同的jit编译器实现到底是做什么的; 如果您有兴趣,请查看!

CLR知道不可为空的值类型永远不能为null,因此它可能总是返回false 。 这就是你问的问题吗?

这会编译,但会发出警告:

 int l = 0; if (l == null) { //whatever } 

警告是这样的:

  warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'int?' 

所以它有效,但结果不是非常有用。 但是,通用,这可能是一个有用的测试。

为什么你认为编译器应该警告你? 当你使用这样的东西时,编译器会发出警告:

 if (1 == 2) //always false if (Dog is Cat) //never true 

但在这种情况下,条件可能为真(当TService是refernece类型且它为null时),或者为false(当它是值类型或引用类型但不为null时)。 它完美地表示if语句的用法(有时是真的,有时是假的)。