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

Pie*_*ant 7 c# generics

在编写包含泛型变量的类时

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Eri*_*ert 12

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

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

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

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


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


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

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

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

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

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

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


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


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

  • @Joan:我们有很多笔记,其中很多都说"我们考虑了成本和收益并决定......"但从未记录成本和收益.因此,它归结为记忆和受过教育的猜测. (2认同)