什么时候应该防范null?

时间:2012-05-20 22:12:55

标签: c#

我什么时候应该防范null论点?理想情况下,我会在任何地方防范null,但这会变得非常臃肿和乏味。我还注意到,人们并没有像AsyncCallback s那样放置警卫。

为了避免让那些带有大量单一代码的人烦恼,有什么可以接受的标准,我应该防范null吗?

感谢。

5 个答案:

答案 0 :(得分:5)

我经常使用的一种方法是null object pattern. 例如,如果a有一个工厂类,它基于一个参数返回一个接口的不同实现,并且提供的参数没有映射到任何实现,我会返回一个NullObject, e.g。

   public interface IFoo{
         void Bar();
   }
   public class NullFoo{
       public void Bar(){
          //null behaviour 
       }
   }
   public class FooFactory{
        public IFoo CreateFoo(int i){
              switch(i){
                  case 1:
                  return new OneFoo();
                  break;
                  case 2:
                  return new TwoFoo();
                  break;
                  default:
                  return new NullFoo();
                  break;
              }
        } 
   }

当我想从IFoo获取CreateFoo时,我不必检查返回的对象是否为空。

显然,这只是众多方法中的一种。没有“一刀切”,因为null可能意味着不同的东西。

防止空参数的另一种方法是使用CodeContract preconditions。 e.g。

  public void Foo(Bar x){
      Contract.Requires<ArgumentNullException>( x != null, "x" );
      //access x
  }

使用代码约定可以对代码运行静态代码分析并捕获Foo(null)等错误。 (more here

另外一个原因是使用一个非常简单的通用扩展方法:

public static class Ex
{
    public static void EnsureNotNull<T>(this T t, string argName) where T:class
    {
        if(t == null)
        {
            throw new ArgumentNullException(argName);
        }
    }
}

然后你可以检查你的论点:

 public void Foo(Bar x, Bar y){
     x.EnsureNotNull("x");
     y.EnsureNotNull("y");
 }

答案 1 :(得分:4)

  

我什么时候应该防范空参数?

我假设您正在讨论传递给公共方法或您编写的代码构造函数的null参数。请注意,每当调用任何可能返回null的外部依赖项时,您也可能必须“防范”null,除非您的代码可以正常处理这些情况。

您应该在任何公共方法(包括构造函数和属性设置器)中防止null,这些方法是在null值没有有用和显式含义的用户处公开的。如果一个空值对你的代码没有什么特别的意义(例如数组结尾,“未知”等)那么你就不应该接受那个值,而应该抛出一个ArgumentNullException

此规则也不是null唯一的。您应该始终检查传递给公共方法的参数。

例如,假设您正在编写某种带有用户ID的Web服务方法,并对用户执行某些操作(例如删除它们)。在方法对该用户执行任何其他操作之前,您应该验证它是否是有效的用户ID。另一个例子是,如果您正在编写一个将索引作为集合或数组的公共方法。您应该检查索引是否在允许的范围内 - 索引不大于集合,或者小于零。

  

我还注意到人们并没有像AsyncCallbacks那样放置警卫。

如果您知道传递给您的方法的参数会保持您的方法前置条件(因为如果不是则抛出异常),那么您可以在私有和内部方法中跳过这些检查,或者私人和内部课程。

但是正如您所指出的那样,您仍然必须小心不要相信您未编写的API的任何返回值,或者传递给回调方法的任何值。将它们视为“脏”,并假设它们可以为空或无效值。

  

变得非常臃肿和乏味

指定和跟踪公共方法的前提条件并不是膨胀 - 它是编译文档。这是您确保代码正确的方式。这是你的代码的用户如何被告知他们做错了什么。让这个在你的方法中间失败(或者在一些模糊相关的类中的另一个方法中)会使调试你的问题变得更加困难。

现在这似乎不是什么大问题。但是,一旦你开始收到堆栈中NullReferenceException 5级的客户的投诉,之后会有20个方法调用,那么我认为你会开始看到这些好处:)

  

为了避免让那些有大量单一代码的人烦恼,有什么可以接受的标准,我应该防范null吗?

通常,人们只需在其方法的顶部编写if ... throw代码。这是最习惯的语法,即使对于初学者也很容易理解。有点像Lisp中的parens,一旦你使用了这种模式,你就可以很快地浏览它,而不用考虑它。

使用Visual Studio Code Snippets编写这些检查可以更快。

您可以通过使用或构建一些支持断言语法的共享代码来缩短此代码。您可以编写类似if ... throw的行,而不是Assert.NotNull(arg1, "arg1");语法。如果您需要一些灵感,可以查看NUnit框架的assertionsconstraints

您可能还想查看Code Contracts API。它设计用于检查前置条件,后置条件和不变量(这些是“保护条件”的正式名称)。它还可以将一些验证从运行时间转移到编译时,这样您就可以在运行程序之前发现自己犯了错误。我没有真正看过它,但它也可能会给你更简洁的语法。 修改:另请参阅Pencho Ilchev's answer,了解使用此API的一部分的简短示例

答案 2 :(得分:1)

必须检查公共API。对于开发人员来说,如果他们知道他们因为null参数而导致错误而不是尝试通过该null参数调试一些模糊的其他异常,那么开发人员会更容易(也更安全)。

在内部,构造函数是一个检查空值的好地方,原因相同 - 在构造函数中捕获它比在类上调用方法时更容易。

有些人认为在任何地方都可以检查,但我倾向于认为在实际阅读代码时会有更多代码需要筛选。

答案 3 :(得分:1)

有时我创建一个Preconditions类,其中包含用于检查一些常见方法前置条件的静态方法,即null参数 - 例如:

public static <T> T checkNull(T arg) {
    if (arg == null)
        throw new IllegalArgumentException();
    return arg;
}

这可以让你的代码更清洁。

至于 ,你应该检查null,它应该在不是有效值的地方完成。

修改

注意到这个问题被标记为C#,但你明白了......

答案 4 :(得分:1)

如果您正在讨论方法参数,那么您可以选择。您可以检查参数并抛出ArgumentNullException,或者您可以忽略该检查并让其他内容进一步抛出NullReferenceException。在不检查参数的情况下运行的风险是您的代码可能会在抛出NullReferenceException之前更改某些全局状态,从而使程序处于未知状态。

通常,检查参数并记录您的方法抛出ArgumentNullException可能更好。是的,这是一个更多的工作,在一个方法的开头趟过这些检查可能会很烦人。你必须问自己,你得到的更强大的代码是否是一个很好的权衡。

有关此问题的详细讨论,请参阅This link