如何检查一个对象是否为空?

20

如何检查传递给方法的对象是否为null?

在调用方法之前需要对对象进行测试吗?还是在使用参数的方法内部进行检查?

public class Program
{
    public static void Main(string[] args)
    {
        // Check if person is null here? or within PrintAge?

        PrintAge(new Person { Age = 1 });
    }

    private static void PrintAge(Person person)
    {
        // check if person is null here?

        Console.WriteLine("Age = {0}", person.Age);
    }
}

public class Person
{
    public int Age { get; set; }
}

在两个类中都进行"null"检查似乎会有太多的冗余代码。

[编辑]: 在调用者或被调用者中检查null的优缺点是什么?

[编辑2]: 我刚刚了解到防御式编程,它似乎主张在被调用者内检查null。我想知道这是否是一种广为接受的做法。


你可以查看这个链接,了解一个直接的分析如何检查对象是否已定义? - CeganB
13个回答

11

如果你设计一个库,那么将有方法暴露给外部世界。在这些方法中,你应该检查传入的数据。对于不暴露的方法,不需要进行检查,因为只有你的代码会调用它们,而它们的逻辑应该处理你在调用暴露的方法时接受的所有情况。

                    --------------------------
                   |                          |
                   |         Library          |
                   |                          |
 -------        ---------        ----------   |
|       |      |         |      |          |  |
| Outer |      | Library |      | Library  |  |
|       | ===> | Entry   | ===> | Backend/ |  |
| World |      | Method  |      | Helpers  |  |
|       |      |         |      |          |  |
 -------        ---------        ----------   |
                   |                          |
                   |                          |
                    --------------------------
如果你已经接受了entry方法中提供的数据,那么你应该执行所请求的操作并返回预期的结果,即处理所有剩余情况。

更新

为了澄清库内部的情况。可能会有null检查,但仅因为逻辑需要,并非参数验证。在库内部,空值检查有两种可能的位置。第一种是被调用的方法知道如何处理空值。

private CallingMethod()
{
   CalledMethod(someData);
}

private CalledMethod(Object parameter)
{
   if (parameter == null)
   {
      // Do something
   }
   else
   {
      // Do something else
   }
}

而第二种情况是,如果您调用一个不能处理空值的方法。

private CallingMethod()
{
   if (someData == null)
   {
      // Do the work myself or call another method
   }
   else
   {
      CalledMethod(someData);
   }
}

private CalledMethod(Object parameter)
{
   // Do something
}

整个思想是拒绝您无法立即处理的案例,并正确处理所有其余案例。如果输入无效,则会抛出异常。这迫使库调用者仅提供有效值,并且不允许调用者继续使用无意义的返回值执行(此外,调用方将吞噬异常并继续执行)。


那么在“库后端/助手”中,无论是调用方还是被调用方都没有进行“null”检查?我的理解是否正确? - dance2die
可能会有空值检查,但只有在实际逻辑需要时才进行。没有仅仅验证参数的空值检查。 - Daniel Brückner

6
你在Main中没有任何需要检查的内容 - 你正在使用new运算符,它从不返回null(除了Nullable<T>)。
如果PrintAge被公开使用,检查是完全合理的。(对于私有API来说,进行参数检查的重要性较小,但仍然非常有用。)
if (person == null)
{
    throw new ArgumentNullException("person");
}

现在在C# 3.0中,我通常会使用扩展方法来处理这个


4

我认为检查PrintAge更有意义,因为这符合例程的要求。当然,在测试时可以用Debug.Assert()代码替换空值检查,但在发布时则不需要。


4
你可以设计一个方法只能处理有效对象。
这意味着你期望接收有效对象(在你的情况下不是空对象)。
这也意味着你不知道如何处理无效对象:
  • 从函数中静默返回不是解决方案;
  • 抛出异常将意味着你将责任转移到上层方法,在那里他们可以在传递给你之前检查值。
因此,如果你的方法不知道如何处理无效对象,并且在无效情况下该方法不遵循其他逻辑,则应该放置。
Debug.Assert( Person );

PrintAge开始处进行检查,这将通过调用堆栈强制您进行检查。
层次结构中较低的函数应该做更少的检查。以下是在执行工作的函数中进行检查的缺点
  • 正在执行实际工作的函数必须尽可能清晰,而不是有大量的if
  • 该函数将被多次调用
  • 这样的函数可以调用这样的函数,它们可以再次调用这样的函数。每个函数都将执行相同的验证。

3
Debug.Assert的问题在于,除非你明确地在发布版中包含DEBUG符号,否则你的代码在开发环境和生产环境下的行为会有所不同-这不好。生产环境是您真正想要确保不使用错误数据的时间 :) - Jon Skeet
在调试中使用Assert会强制你在调用PrintAge之前进行检查。 - Mykola Golubyev
+标记为答案:“他们每个人都会执行相同的验证” - dance2die
如果这段代码不受单元测试支持,并且它位于某些不常见的流程中,您可能无法发现它,而Assert提供的错误安全机制可能会对您产生不良影响! - dr. evil
@Slough:Assert 的虚假安全性?这是什么意思?你不控制项目构建的方式吗?单元测试。在代码周围放置 if 语句是否会取消单元测试?我对所有这些“以防万一”的 if 语句感到厌烦,而这是因为低级函数中有一个 if 语句引起的。 - Mykola Golubyev

2

你的意思是两种方法都要检查吗?我肯定会在PrintAge中进行检查,如果在Main中也有意义的话,也可以在Main中进行检查。总的来说,我认为这个问题并没有一个确定的答案。这取决于具体情况 :-)


2

通常我会让我的空值检查由预期来控制,如果我预期某些东西为空或不确定它是否为空,我会添加一个检查。否则我就不会添加。NullPointerException 是最容易追踪的问题之一,因此过多地添加检查会使代码变得臃肿。在这个具体的例子中,我不会进行任何检查,因为很容易理解它不是空的。


所以基本上,你的做法是根据当前情况而变化的? - dance2die
是的。我试图通过这样的检查来表达我的期望。 - krosenvold

1

如果实例为空,你想要做什么?

我认为这取决于您提供的API和定义的契约(就像.NET框架类所做的那样)。话虽如此,如果方法定义了在传递空引用时预期的结果,则无需在主函数中检查null。


实际上,当一个对象为空时,我应该做什么取决于很多情况。如果我在被调用的方法中检查空值,那么我会抛出异常或者什么都不做。在被调用的函数内部,我也会这样做。 - dance2die
我的意思是假设这是一个你让外部用户调用的方法,如果传递的值为null,你想在被调用的方法中做什么? - shahkalpesh
@shahkalpesh:有时候我想根据方法的操作(是Get/Set方法)抛出异常或者返回一个空列表。 - dance2die

1

只有一种情况下构造函数才能返回null [Nullable<T>上的new()] - 因此调用代码不需要检查。

被调用方可能应该检查; 如果为null,则抛出ArgumentNullException。在.NET 4.0中,这将通过代码合同更好地实现。但还没有;-p


我仍然不太明白的是,为什么"调用代码不需要检查"并放弃对被调用者进行空指针检查。 - dance2die
因为它(根据最初的代码)不可能为空。 - Marc Gravell

1

根据我的理解,您的问题比您的示例更为普遍。 我的偏好如下:

  • 所有公开可访问的方法必须检查 NULL 输入并适当地抛出异常。 因此,如果您正在构建供他人使用的框架,请进行代码防御。
  • 如果您知道在其他地方已经执行了此操作或者参数永远不会为 NULL,则私有方法可以省略 NULL 检查,但总体而言,我更喜欢显式的 ArgumentNullException 而不是 NullRefereceException。

Brad Abrams 在这里提供了更多信息:http://blogs.msdn.com/brada/archive/2004/07/11/180315.aspx


@Brian:是的,我之前的问题过于笼统了。 - dance2die

0

我喜欢在方法内部进行空值检查,有两个原因:

  1. 我认为函数应该是“完整的”,即处理空值/“边缘情况”,而不依赖于调用者。理由如下:

    • 后续调用该方法的人可能会忘记添加空值检查
    • 使用单元测试可以更容易地测试具有边缘情况的方法。
  2. 在方法内部进行空值检查可以减少代码中总体的空值检查次数,通常意味着代码更易读。


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