在C#中,是否应该检查传递给方法的引用是否为null?

11

几个月前,我问过一个关于C和C ++的类似问题,但最近由于整个“Windows Phone”事情,我更加关注C#。

那么,在C#中,方法边界应该检查NULL吗?我认为这与C和C ++不同,因为在C#中,人们通常可以确定给定引用是否有效--编译器将防止将未初始化的引用传递到任何地方,因此唯一剩下的可能错误是它为空。此外,在.NET Framework内部定义了一个特定的异常ArgumentNullException,它似乎对程序员认为应该在传递无效null时获得的内容进行了编码。

我的个人观点再次是,调用者这样做是有问题的,而且说到底,应该向他们抛出NREs直到永远。然而,我对此比在本机代码领域要少得多--C#在某些方面与C或C ++相比具有非常不同的编程风格。

那么...在C#方法中应该检查空参数吗?


1
这个 arguments 标签在这里似乎有两个意思,哈哈。 - BoltClock
@BoltClock:哈哈——我希望不是这样。我并不想在这些事情上引发争吵,但它们似乎总是会发生。 - Billy ONeal
1
@downvoter:您对这个问题有具体的意见吗? - Billy ONeal
4个回答

8

是的,检查它们。最好使用代码合同告诉调用者您需要非空参数。

void Foo(Bar bar) {
    Contract.Requires(bar != null);
}

这特别有利,因为客户端可以清楚地看到参数所需的内容。

如果不能使用代码契约,请使用守卫条件。

void Foo(Bar bar) {
    Guard.Against<ArgumentNullException>(bar == null);
}

快速失败。


在编译时,Contract.Requires(bar != null); 会标记 Foo(null); 吗?我对此表示怀疑,那么相较于使用 if 进行简单的空值检查,使用这种方式的优势是什么呢? - Bala R
合约/守卫是从哪里来的?我对这些位不熟悉。:/ 这适用于公开暴露的方法,还是组件内部的方法也适用? - Billy ONeal
@Bill ONeal:Code Contract 在 .NET 4.0 中位于 System.Diagnostics.Contracts 命名空间中。Guard 是一个常见的实用类,人们有自己的版本,但基本上它只是在满足条件时抛出类型参数中指定的异常。 - jason
2
@Bala R:您可以进行静态检查,并将其作为文档的一部分。 - jason
1
Contracts/Guard 部分会稍微分散注意力。检查的方式取决于几个因素。我喜欢 Contracts,但它并不是到处都被接受(至少目前还没有)。 - H H

5

如果你的方法不希望某个参数为null,我认为最好抛出一个ArgumentNullException(或者像Jason建议的那样使用合同机制)。这是通过合同向客户端传达的更好指示,让客户端在调用你的方法时首先不要传递null。

然后,空值检查成为客户端的责任,这使得双方的代码更易维护(通常是双赢的局面)...至少这是我的感觉。


你认为这只是针对公开的库方法,还是内部方法也必须这样做? - Billy ONeal
@Billy:仅对公开库方法执行此操作。只有标记为“public”的方法才应对任何类型的参数进行验证。对于内部和私有方法来说,这是一项不必要的开销,这也是首先拥有不属于接口的方法的原因之一。 - Cody Gray
@Cody:如果类本身没有暴露呢?例如,这个类是“internal”。 - Billy ONeal
@Billy:这其实是一回事。类的访问级别调节了其各个成员的访问级别。如果类不能在库外使用,那么它就不是公共接口的一部分。你可能想要在库内仅为方便或彻底性而实现参数验证,但这并非绝对必要,因为内部代码可以严格审核。唯一的问题是对于将被外部暴露的类,在调用者无法得到充分审核和测试的情况下使用。 - Cody Gray

3

在公共方法中至少验证所有参数是一个好的实践。这有助于更快地定位错误并且在阅读代码时也很有帮助,因为它描述了方法的契约。

我们实现了一个静态类AssertUtilities,其中包含一堆用于参数和状态验证的方法。我相信有些人将这样的类称为Guard

例如:

    public static void ArgumentNotNull(object argument, string argumentName)
    {
        if (argument == null)
            throw new ArgumentNullException(argumentName);
    }

    public static void ArgumentInRange(decimal argument, decimal minValue, decimal maxValue, string argumentName)
    {
        if (argument < minValue || argument > maxValue)
        {
            throw new ArgumentOutOfRangeException(
                argumentName,
                FormatUtilities.Format("Argument out of range: {0}. Must be between {1} and {2}.", argument, minValue, maxValue));
        }
    }

    public static void ArgumentState(bool expression, string argumentName, string formatText, params object[] args)
    {
        if (!expression)
            throw new ArgumentException(FormatUtilities.Format(formatText, args), argumentName);
    }

    public static void ArgumentHasText(string argument, string argumentName)
    {
        ArgumentNotNull(argument, argumentName);
        ArgumentState(argument.Length > 0, argumentName, "Argument cannot be an empty string.");
    }

2
所以... 在C#方法中应该检查null参数吗? 是的,除非允许使用null。 更好的做法是:始终检查有效的参数值。有时允许引用为null,有时int必须是>= 0,字符串可能不为空或空格。因此,最好的做法是在每个方法中都开始使用参数验证块。 从那里开始,有几种选择。通常认为,在内部/私有方法中进行验证较不重要,并且出于性能原因,可以将其设置为条件性的(甚至略去)。 在边界处进行验证通常在发布版本中保留。几乎没有理由关闭它。 大多数项目都会使用一些辅助类进行验证,以最小化方法内部重复编码。一个非常好但尚未很受欢迎的工具包是内置的System.Diagnostics.Contracts类。Code-Contracts工具有许多关于参数验证的设置。

我非常不同意“检查有效参数”的说法——一般情况下这是不可能或者不切实际的,即使在像CLR这样受限制的环境中也是如此。 - Billy ONeal

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