当一个参数为null时,最佳实践是什么?

19

在验证方法的输入时,我过去会检查参数是否为空,如果是,则抛出ArgumentNullException异常。我对列表中的每个参数都这样做,所以最终代码看起来像这样:

 public User CreateUser(string userName, string password, 
                            string Email, string emailAlerts, 
                            string channelDescription)
    {

        if (string.IsNullOrEmpty(userName))
            throw new ArgumentNullException("Username can't be null");

        if (string.IsNullOrEmpty(Email))
            throw new ArgumentNullException("Email can't be null");
       //etc, etc, etc
    }

这样做可以吗?为什么我要这样做?如果我只是将所有检查分组并返回null值而不是抛出异常,那么可以吗?如何最好地解决这种情况?

附:我想改变这种情况,因为在长方法中这样做非常乏味。
有什么建议吗?


9
ArgumentNullException的单参数构造函数应该接收参数的名称 - 冗长的注释是不必要的。 - David M
太棒了,David M的提示!我从来没有花时间去阅读过载 :) .. 谢谢 - Ahmed
由於我最終使用了 Lou 的答案,因此我將接受它。感謝大家的幫助!非常感激! - Ahmed
3
在C# 6.0中,你可以使用内建的nameof来获取参数的名称:if (foo == null) throw new ArgumentNullException(nameof(foo));。这种模式在下面大部分答案中都很有用。 - Gordon Bean
8个回答

18
创建一个ArgChecker类,可以使用以下代码实现:
  ArgChecker.ThrowOnStringNullOrEmpty(userName, "Username");

ThrowOnStringNullOrEmpty 是什么

  public static void ThrowOnStringNullOrEmpty(string arg, string name)
  {
      if (string.IsNullOrEmpty(arg))
        throw new ArgumentNullException(name + " can't be null");
  }

您可以尝试使用params参数处理参数列表,例如:

您可以尝试使用params参数处理参数列表,例如:

  public static void ThrowOnAnyStringNullOrEmpty(params string[] argAndNames)
  {
       for (int i = 0; i < argAndName.Length; i+=2) {
          ThrowOnStringNullOrEmpty(argAndNames[i], argAndNames[i+1]);
       }
  }

并按如下方式调用
  ArgChecker.ThrowOnAnyStringNullOrEmpty(userName, "Username", Email, "email");

我喜欢它!特别是对于参数的事情。谢谢Lou +1 - Ahmed
2
我唯一的问题是,当一个字符串为空时,ArgumentNullException可能不太合适,因为它不是null。也许其他形式的无效或非法参数异常会更好。 - Thomas Owens
@Thomas,请看下面我的回答 :) - James
如果您使用一个简单的工具,如SonarLint,您会注意到这是一种不好的做法。还有一些讨论认为,在各种情况下,您不应该检查null,而应该让环境抛出NullReferenceException。 - nkalfov

18

我使用的一种方法,可能是从NHibernate源代码中学来的,就是创建一个称为Guard的静态类,用法如下:

public void Foo(object arg1, string arg2, int arg3)
{
    Guard.ArgumentNotNull(arg1, "arg1");
    Guard.ArgumentNotNullOrEmpty(arg2, "arg2");
    Guard.ArgumentGreaterThan(arg3, "arg3", 0);
    //etc.
}

public static class Guard
{
    public static void ArgumentNotNull(object argument, string parameterName)
    {
        if (parameterName == null)
            throw new ArgumentNullException("parameterName");

        if (argument == null)
            throw new ArgumentNullException(parameterName);
    }
    //etc.
}

这样做可以减少方法开头的很多无关内容,并且它的性能表现良好。


确实比我的好看。谢谢Matt! - Ahmed

9
你应该考虑方法的作用和需要使用哪些数据。如果空值代表实际失败条件,则使用异常。如果可以接受空值,则接受它们。
考虑设计契约的原则,特别是您的函数的前提条件,并标准化强制执行它们的方法(Matt和Lou在他们的答案中都建议了这一点,所以我不需要详细解释)。
另一个重要的事情是考虑方法签名的大小。如果您的方法有很多参数,这可能意味着您的抽象不好。如果将相关参数分组到集合对象中并将这些对象用作参数,则可以减少必须进行的参数检查数量。您可以将参数检查移动到这些对象中,而无需在每个使用它们的函数中进行检查。
因此,不要向每个函数传递十个相关参数,而是找出每个函数中使用的几个参数并将它们打包成一个对象,并在该对象中包括验证参数的方法。这具有易于更改的附加优势,如果需要更新一个参数的规则,则可以轻松更改。

谢谢Welbog。是的,null是失败条件。我认为我的方法签名大小相当可接受(最多7个参数)。关于将参数分组到一个对象中,我认为这将创建一个没有身份的对象,我应该如何在域模型中表示这个对象? - Ahmed
七个参数是一个不错的经验法则,但如果许多相关方法使用相同的一组参数,则将它们分组仍然是一个好主意。您可以按照自己的方式对它们进行建模。如果常见参数是面向领域的(即员工数据),请以其领域对应项(即员工)命名。如果它们是面向实现的(即特定于会话的数据),请以它们在应用程序中所代表的内容(即会话)命名。如果共同的参数除了在许多方法中使用之外没有任何相关性,则将它们保持未分组状态。 - Welbog
2
在《代码整洁之道》一书中,罗伯特·马丁认为,不是7个,而是零个参数是理想数量,其次是一个。使用Wellbog的好思路,你应该能够更接近这些理想。当你试图推动这些极限时,这非常有趣,有时也很愉悦;你会惊讶地发现它是多么容易实现,并且你会喜欢你的代码有多好。 - Carl Manaster

5

对于我们中的C# 3.0开发人员来说,封装这种空值检查的好方法是使用扩展方法。

public void Foo(string arg1, int? arg2)
{
  arg1.ThrowOnNull();
  arg2.ThrowOnNull();
}

public static class extensions
{
    public static void ThrowOnNull<T>(this T argument) where T : class
    {
        if(argument == null) throw new ArgumentNullException();
    } 
}

如果您想的话,您总是可以重载它来接受一个参数名称。


4
我不确定我喜欢似乎是在一个空对象上调用的方法,尽管我知道这对于扩展方法是合法的。这可以归类为“句法不协调”。 - Matt Howells

3
使用哈希表会是 Lou 的答案的一个小改进,这意味着它可以检查对象以及字符串。在方法中填充和处理哈希表也更加方便。
public static class ParameterChecker
{
    public static void CheckForNull(Hashtable parameters)
    {
        foreach (DictionaryEntry param in parameters)
        {
            if (param.Value == null || string.IsNullOrEmpty(param.Value as string))
            {
                throw new ArgumentNullException(param.Key.ToString());
            }
        }
    }
}

您想要使用的方式:

public User CreateUser(string userName, string password, string Email, string emailAlerts, string channelDescription)    
{
    var parameters = new Hashtable();
    parameters.Add("Username", userName);
    parameters.Add("Password", password);
    parameters.Add("EmailAlerts", emailAlerts);
    parameters.Add("ChannelDescription", channelDescription);
    ParameterChecker.CheckForNull(parameters);

    // etc etc
}

1
如果你要将东西封装到哈希表中,最好创建自己的静态类型结构或类来抽象这种事情。使用类可以更好地控制属性的类型以及它们有效性的含义。 - Welbog
很容易对param.Value执行另一个检查,即if(param.Value is Type),并相应地处理。 - James

2

我建议您仍然采用原始方法,除了传递参数名称之外。原因是一旦您开始编写这些帮助程序,当每个人开始使用不同的约定编写帮助程序时,这就成为一个问题。当有人查看您的代码时,他们现在必须检查以确保您正确编写了帮助程序,以便调试您的代码。

请继续单独检查每个参数,即使您的手指因打字而疲倦。当您的关注者得到意外的ArgumentException并且被免于进行调试运行以确定哪个参数失败时,他们将会感激您。


伙计!我真的很关心我的同事,我爱他们,实际上我们一直在奉承(开玩笑):)。 我认为调用这样一个检查 null 或空的微小函数不会让任何人感到困惑。 - Ahmed
你会惊讶地发现,对你来说更简单、更清晰的东西可能对别人来说并非如此。我曾经读过的最糟糕的代码是由一个人编写的,他认为所有的C语言结构都应该被宏定义所取代,以定义新的循环结构等等。:) 我想他在完成之前已经走了一半的C#(或者F#)的路程。 - Larry Watanabe

1
使用一个AggregateException(用于包含多个异常)和多个ArgumentNullException实例的列表。不要忘记还要利用ArgumentNullExceptionparameterName参数,它可以很好地与nameof()配合使用:
var exceptions = new List<Exceptions>();

if (firstArgument == null)
    exceptions.Add(new ArgumentNullException(nameof(firstArgument), "Some optional message"));

if (secondArgument == null)
    exceptions.Add(new ArgumentNullException(nameof(secondArgument), "Another optional message"));

if (exceptions.Count > 0)
    throw new AggregateException(exceptions);

0

我给你的第一个建议是使用ReSharper。它会告诉你可能存在空值的问题,以及不需要检查它们的情况,并且只需点击鼠标即可添加检查。话虽如此...

您不必检查int或bool,因为它们不能为null。

可以使用string.IsNullOrEmpty()检查字符串...

如果您仍然决定要检查每个参数,可以使用Command设计模式和反射,但您的代码将变得不必要地笨重,或者对于每个方法使用以下内容: private myType myMethod(string param1, int param2, byte[] param3) { CheckParameters("myMethod", {param1, param2, param3}); // 其余的代码...

在您的实用程序类中放置以下内容:

///<summary>Validates method parameters</summary>
///... rest of documentation
public void CheckParameters(string methodName, List<Object> parameterValues) 
{
    if ( string.IsNullOrEmpty(methodName) )
       throw new ArgumentException("Fire the programmer! Missing method name", "methodName"));

    Type t = typeof(MyClass);
    MethodInfo method = t.GetMethod(methodName);
    if ( method == null )
       throw new ArgumentException("Fire the programmer! Wrong method name", "methodName"));
    List<ParameterInfo> params = method.GetParameters();
    if ( params == null || params.Count != parameterValues.Count )
       throw new ArgumentException("Fire the programmer! Wrong list of parameters. Should have " + params.Count + " parameters", "parameterValues"));

    for (int i = 0; i < params.Count; i++ )
    {
            ParamInfo param = params[i];
            if ( param.Type != typeof(parameterValues[i]) )
                throw new ArgumentException("Fire the programmer! Wrong order of parameters. Error in param " + param.Name, "parameterValues"));
            if ( parameterValues[i] == null )
                throw new ArgumentException(param.Name + " cannot be null");
    }
} // enjoy

int 不能为 null;它可以是 0int? 可以为 null - nkalfov

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