在编写包含通用变量
的类时public class ServiceInvoker<TService> : IDisposable
{
private TService _Service;
public ServiceInvoker()
{
_Service = Activator.CreateInstance<TService>();
}
public void Invoke(Action<TService> action)
{
// CAN use null
if (_Service == null)
throw new ObjectDisposedException("ServiceInvoker");
....
}
public void Dispose()
{
// CAN'T use null
this._Service = default(TService);
}
}
我注意到编译器允许我检查泛型变量的null,但是,当然,不允许我将其设置为null,因此我们必须使用default(TService)
。
编译器不应该警告我使用null吗?或者它是否使用装箱转换到一个对象来进行null测试?
我读到proper null evaluation for generic,但我很想知道原因,而不仅仅是如何。
答案 0 :(得分:12)
为什么我可以在null可以为空或可能不是对象的情况下测试null的泛型?
这个问题是不合适的;泛型类型的任何表达式的值在运行时将始终是对象或空引用。我想你想问:
为什么我可以测试null的泛型,因为它的运行时类型既不是可空值类型也不是引用类型?
C#规范保证在7.6.10节中对文字null的相等比较是合法的,为方便起见,我在这里引用:
如果将类型参数类型T的操作数与null进行比较,并且T的运行时类型是值类型,则比较结果为false。 [...]即使T可以表示值类型,也允许
x == null
构造,并且当T是值类型时,结果被简单地定义为假。
请注意,此处的规范存在一个小错误;最后一句应该以“不可为空的值类型”结束。
我不确定我是否真的回答了“为什么?”问题是否令人满意。如果这不令人满意,请尝试提出更具体的问题。
编译器不应该警告我使用null吗?
没有。为什么您认为编译器应该警告您正确使用该语言的功能?
是否使用装箱转换到一个对象来测试null?
嗯,这有点棘手。一方面,第7.6.10节规定:
预定义的引用类型相等运算符永远不会导致其操作数发生装箱操作。执行此类装箱操作将毫无意义,因为对新分配的盒装实例的引用必然会与所有其他引用不同。
然而,当我们生成IL以将泛型T与null进行比较时,我们当然实际上会生成一个T的框,一个null的加载和一个比较。抖动可以足够智能以消除拳击的实际内存分配;如果T是引用类型,那么它不需要装箱,如果它是可以为空的值类型,则可以将其转换为检查HasValue属性的调用,如果它是不可为空的值类型,那么装箱和检查可以是变成简单的“假”。我不确切知道各种不同的jit编译器实现到底是做什么的;如果你有兴趣,请查看!
答案 1 :(得分:4)
CLR知道不可为空的值类型永远不能为null,因此它可能总是返回false
。这就是你问的问题吗?
答案 2 :(得分:1)
这会编译,但会发出警告:
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?'
所以它有效,但结果不是非常有用。但是,通用,这可能是一个有用的测试。
答案 3 :(得分:1)
为什么你认为编译器应该警告你?当你使用这样的东西时,编译器会发出警告:
if (1 == 2) //always false
if (Dog is Cat) //never true
但在这种情况下,条件可能为真(当TService是refernece类型且它为null时),或者为false(当它是值类型或引用类型但不为null时)。它完美地代表了 if 语句的用法(有时是真的,有时是假的)。