将泛型与可能是值或引用类型的 null 进行比较

本文关键字:引用类型 null 比较 泛型 | 更新日期: 2023-09-27 18:19:57

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(。虽然我确实想知道:

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

将泛型与可能是值或引用类型的 null 进行比较

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

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

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

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

不。在这里,与 null 进行比较是正确的做法。

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

没有可能由此引起问题?

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

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

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

当我使用不可为空的值类型的类型参数调用泛型方法时,到底发生了什么?

抖动在使用该构造的第一次调用时编译方法。当抖动检测到空检查时,它会将其替换为"false",因为它知道任何不可为空的值类型都不会等于空。

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

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

在这种情况下,抖动无法消除空检查,呼叫站点无法避免装箱。SFoo 实例将被装箱,并且将检查对带盒的 SFoo 的引用以查看它是否为 null。

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

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
}