【问题标题】:Comparing a generic against null that could be a value or reference type?将泛型与可能是值或引用类型的 null 进行比较?
【发布时间】:2012-02-08 01:02:07
【问题描述】:
public void DoFoo<T>(T foo) where T : ISomeInterface<T>
{
    //possible compare of value type with 'null'.
    if (foo == null) throw new ArgumentNullException("foo");
}

我故意只检查null,因为我不想限制ValueType 等于它的default(T)。我的代码以这种方式编译和工作得很好(ReSharper 抱怨,但不是 CodeAnalysis)。虽然我确实想知道:

  • 有没有更标准的方法来处理这种情况?
  • 是否有可能由此引发问题?
  • 当我进行调用并传入值类型时,真正发生了什么?

【问题讨论】:

    标签: c# generics null value-type null-check


    【解决方案1】:

    不,不会有任何问题,但是如果您希望警告消失,您可以使用以下方法:

    public void DoFoo<T>(T foo) where T : ISomeInterface<T>
    {
        if (ReferenceEquals(foo, null)) throw new ArgumentNullException("foo");
    }
    

    您也可以这样做:

    // when calling this with an actual T parameter, you have to either specify the type
    // explicitly or cast the parameter to T?.
    public void DoFoo<T>(T? foo) where T : struct, ISomeInterface<T>
    {
        if (foo == null)
        {
            // throw...
        }
    
        DoFooInternal(foo.Value);
    }
    
    public void DoFoo<T>(T foo) where T : class, ISomeInterface<T>
    {
        if (foo == null)
        {
            // throw...
        }
    
        DoFooInternal(foo); 
    }
    
    private void DoFooInternal<T>(T foo) where T : ISomeInterface<T>
    {
        // actual implementation
    }
    

    【讨论】:

    • 如果T 是值类型,则使用ReferenceEquals 将导致装箱操作,这可能是不可取的。
    【解决方案2】:

    我故意只检查 null,因为我不想限制 ValueType 等于它的 default(T)

    这是一个很好的见解,但别担心,你已经被覆盖了。首先使用== 将T 与default(T) 进行比较是不合法的;重载决议将找不到唯一的最佳 == 运算符。

    当然,您可以与.Equals 进行比较,但如果接收器为空,您将面临崩溃的风险,而这正是您试图避免的。

    有没有更标准的方法来处理这种情况?

    没有。与 null 进行比较是正确的做法。

    正如 C# 规范在第 7.10.6 节中所说:“x == null 构造是允许的,即使 T 可以表示值类型,并且当 T 是值类型时,结果被简单地定义为 false。 "

    有没有可能由此引发问题?

    当然。仅仅因为代码编译并不意味着它具有您想要的语义。写一些测试。

    当我进行调用并传入值类型时,真正发生了什么?

    这个问题模棱两可。让我把它改写成两个问题:

    当我使用非可空值类型的类型参数调用泛型方法时,真正发生了什么?

    jitter 在第一次调用时使用该构造编译方法。当 jitter 检测到 null 检查时,它会将其替换为“false”,因为它知道任何不可为 null 的值类型永远不会等于 null。

    当我使用一个类型参数是引用类型但参数是结构类型的泛型方法进行调用时,真正发生了什么?例如:

    interface IFoo : ISomeInterface<IFoo> {}
    struct SFoo : IFoo { whatever }
    ...
    DoFooInternal<IFoo>(new SFoo());
    

    在这种情况下,抖动无法忽略空值检查,并且呼叫站点无法避免装箱。 SFoo 实例将被装箱,并检查对装箱 SFoo 的引用是否为空。

    【讨论】:

    • object.Equals(foo, default(T))) 其中foo 是一个装箱的值类型返回false 时,我仍然感到困惑(例如fooDateTime,但通过PropertyInfoGetValue 获得)。
    • 空值检查是否针对可空值类型进行了优化,或者在这种情况下是否会发生装箱?理想情况下,它会转换为 HasValue 调用,但根据我的经验,== null 检查对于可空值类型的运行速度非常慢。
    • @ChaseMedallion:好问题——我应该在原始答案中涵盖。我相信抖动会为可空值类型重新生成代码,因此可以将空检查转换为对 HasValue 的调用,而不是一个框后跟一个空检查。是否真的如此,我不记得了。不过应该很容易检查;只需编写一个小测试程序,然后在调试器中查看生成的 x86 代码。
    猜你喜欢
    • 2010-12-30
    • 2015-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2016-06-14
    • 2023-03-16
    • 2021-09-06
    相关资源
    最近更新 更多