为什么我可以在它可能不可为空或可能不是对象的情况下测试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语句的用法(有时是真的,有时是假的)。