关于“可能将值类型与'null'进行比较”,我该怎么办?

73

在编写自定义 NUnit 约束条件的方法时。

    private void AddMatchFailure<TExpected, TActual>(string failureName, TExpected expected, TActual actual)
    {
        _matchFailures.Add(
            String.Format(MatchFailureFormat, failureName,
            (expected == null) ? "null" : expected.ToString(),
            (actual == null) ? "null" : actual.ToString()));
    }

Resharper警告expectedactual可能是ValueType对象。

例如,TExpected是DateTime
expected == null;// 但DateTime是一个结构体。

在比较值类型和null时有什么规则,我应该如何编写方法来考虑到这一点,而不限制泛型参数并添加类约束?

4个回答

82

不要修改代码 - 只需忽略该警告。如果类型参数是非空值类型,则比较将始终失败,并且它将始终调用ToString()。我不知道它是否实际上被即时编译(JIT)忽略,但我不会感到惊讶...而且这似乎也不是性能关键代码 :)

就我个人而言,在这种情况下,我会保留该警告,但忽略它 - 可能会加上注释。

我在重新实现LINQ to Objects时几次遇到相同的警告。


65
我已经添加了 "//我得到了Skeets先生的明确许可,忽略此警告",似乎解决了问题 :) - Grokodile
19
@panamack:我可以请求在“Skeet's”中加上一个撇号吗?谢谢。 - Jon Skeet
66
当然,"//我已经得到Skeet先生的明确许可,可以忽略这个警告。" - Grokodile
15
值得注意的是,如果有人真的因为错误消息而困扰,他们可以始终执行Object.ReferenceEquals(objA, null),这实际上只是在.NET源代码中调用objA == objB。而且,我相当确定这将以某种方式被即时编译或内联。美妙的是,错误消息消失了,调用仍然有效 :) - myermian
5
为了完整起见,Eric Lippert 表示,当编译类型参数为非可空值类型的方法时,JIT实际上会用“false”替换“val == null”的检查。 - Joe Amenta
显示剩余8条评论

6
如果您不知道它们将是引用类型,那么可以这样说:当比较ValueType和null时的规则是什么?在不通过添加类约束来限制泛型参数的情况下,我应该如何编写方法以考虑到这一点?
private void AddMatchFailure<TExpected, TActual>(
    string failureName,
    TExpected expected,
    TActual actual
) {
    _matchFailures.Add(
        String.Format(MatchFailureFormat, failureName,
        IsDefault<TExpected>(expected) ? DefaultStringForType<TExpected>() : expected.ToString(),
        IsDefault<TActual>(actual) ? DefaultStringForType<TActual>() : actual.ToString()
    );
}

private bool IsDefault<T>(T value) {
    if(typeof(T).IsValueType) {
        return default(T).Equals(value);
    }
    else {
        return Object.Equals(null, value);
    }
}

private string DefaultStringForType<T>() {
    if(typeof(T).IsValueType) {
        return default(T).ToString();
    }
    else {
        return "null";
    }
}

1
@Jason 但对于非空值类型,它不是 null。对于没有 null 的类型,将比较变为常量 false 是有意义的。这可能是 OP 代码所期望的行为。 - CodesInChaos
@CodeInChaos:default(Nullable<T>)中的T是非可空值类型,它是Nullable<T>的实例,其中Nullable<T>.HasValuefalse(即为null)。 - jason
在我的代码中,我只想永远不要调用null.ToString()。然而,当Matches()失败时,我也希望我的NUnit约束表达性强,所以感谢您提醒我如何使用结构体的默认关键字,我已经忘记了。 - Grokodile
1
@Jason 我知道这一点,但这与我对你的代码的反对无关。你的 DefaultStringForType 没有正确处理一个为 nullNullable<T>。即使不考虑这一点,OP 的代码也比你的干净多了。 - CodesInChaos
虽然有时可能需要这种行为,但显然这不是 OP 想要的。很明显他只想防止 NullReferenceExceptions 并使用一个 "null" 字符串代替。然而,这段代码将打印 0false 或其他默认值,更加复杂,不直观,而且相对于与 null 的简单比较来说也更慢。 - enzi
显示剩余2条评论

2

我正在使用以下类似方法来检查泛型类型是否为空:

if (Equals(result, Default(T)))

9
注意!default(int)的值为0,这可能是一个有效且明确设置的值。检查default(T)与检查null有很大不同。 - ANeves
1
+1 给 @ANeves,resharper 在他们的帮助页面中建议这样做,但我认为它是代码异味。http://confluence.jetbrains.com/display/ReSharper/Possible+compare+of+value+type+with+null - Choco Smith
我也讨厌这个例子,因为这显然是大多数人希望调用 0.ToString() 的情况... - NobodysNightmare

-3
private void AddMatchFailure<TExpected, TActual>(string failureName, TExpected expected, TActual actual)
{
    _matchFailures.Add(
        String.Format(MatchFailureFormat, failureName,
        (expected == default(TExpected)) ? "null" : expected.ToString(),
        (actual == default(TActual)) ? "null" : actual.ToString()));
}

应该这样做。

default(T)会给出该类型的默认值,对于引用类型来说是null - 对于其他类型则取决于具体情况。(例如,对于枚举类型,它相当于(enumType)0)。


2
如果你正在比较整数,那么它将打印“null”而不是0。这样会让测试失败变得难以理解 ;) - Jon Skeet
这给了我一个编译器警告:“无法将' TExpected '和' TExpected '类型的操作数应用于运算符=='” - Grokodile
@Jon 这是真的,但这是唯一摆脱符合参数警告的方法。 :p - Massif
2
问题并不是问如何消除警告,而是询问该怎么处理它 :) - Jon Skeet
@Jon - 好的,没问题...(现在,哪里有“优雅认输”的表情符号) - Massif

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接