如何管理无效检查的冲击?

时间:2009-01-09 18:18:40

标签: c# .net

很多时候,在编程中,我们会遇到null检查显示特别大的情况。我说的是:

if (doc != null)
{
  if (doc.Element != null)
  {
    ... and so on
  }
  else
    throw new Exception("Element cannot be null");
} else {
  throw new Exception("document cannot be null");
}

基本上,整个事情变成了一个难以理解的噩梦,所以我想知道:有没有更简单的方法来描述我上面要做的事情? (除了空检查之外,我还会不时得到string.IsNullOrEmpty这样的内容。)

接受的答案:我接受了this link的答案,因为所描述的方法具有创新性,正是我想要的。谢谢肖恩!

19 个答案:

答案 0 :(得分:25)

查看此文章:A fluent approach to C# parameter validation

它是由一个Paint.NET开发人员编写的。他使用扩展方法来简化和清理空检查代码。

答案 1 :(得分:17)

将它们向前推到函数的开头,并将它们从执行工作的代码部分中取出。像这样:

if (doc == null)
    throw new ArgumentNullException("doc");
if (doc.Element == null)
    throw new ArgumentException("Element cannot be null");
AndSoOn(); 

答案 2 :(得分:13)

如果你只是想抛出异常,为什么不让语言运行时为你抛出它呢?

doc.Element.whatever("foo");

您仍然会获得NullPointerException(或C#中的任何内容)以及完整的回溯信息。

答案 3 :(得分:8)

您可能对 Spec#

感兴趣
  

Spec#是API契约的形式语言,它扩展了C#的构造   非空类型,前置条件,后置条件,对象不变量,   和模型程序(记录整个历史的行为合同)   考虑到帐户)。

您可以让调用者负责确保参数不为null。 Spec#使用感叹号表示:

public static void Clear(int[]! xs) // xs is now a non-null type
{
    for (int i = 0; i < xs.Length; i++)
    {
        xs[i] = 0;
    }
}

现在是Spec#编译器,它将检测可能的空取消引用:

int[] xs = null;
Clear(xs);   // Error: null is not a valid argument

作为旁注,您可能还想确保没有违反Law of Demeter

答案 4 :(得分:3)

如果您认为由于嵌套ifs而无法读取,我的建议是重写如下:

if (doc == null)
{
  throw new Exception("document cannot be null");
}

if (doc.Element == null)
{
    throw new Exception("Element cannot be null");
}

doc.Element.someMethod()

答案 5 :(得分:3)

如果典型的NullReferenceException会这样做,请不要打扰检查,只是让运行时抛出它。如果您需要添加上下文错误信息,以进行日志记录或调试(或其他),请继续将验证分解为不同的方法。在这种情况下,我仍然鼓励你抛出一个嵌套原始异常的NullReferenceException

当我被迫手动挖掘深度XML文档时,我不得不做类似的事情。

通常,我尝试在类的接口边界强制执行null-correctness。然后我可以在我的私有方法中忽略空检查。

答案 6 :(得分:3)

您可能对Null Object Pattern感兴趣。

过去,它帮助我摆脱了许多实例中的空检查。

示例(C ++)

   class IThing
   { 
        public:
          virtual void DoThing() = 0;
   }; 

   class NullThing : public IThing
   { 
        public:
          virtual void DoThing() { /*no-op*/}
   }; 

   class RealThing : public IThing
   { 
        public:
          virtual void DoThing() { /*does something real*/}
   }; 

   int main()
   {
         NullThing theNullInstance; /* often a singleton or static*/
         IThing* thingy = &theNullInstance; /*the null value*/

         // Do stuff that may or may not set  thingy to a RealThing

         thingy->DoThing(); // If is NullThing, does nothing, otherwise does something

         // Can still check for null 
         // If NullThing is a singleton
         if (thingy == &theNullInstance)
         {
              printf("Uhoh, Null thingy!\n"); 
         }
   }

答案 7 :(得分:3)

如果属性初始化为适当的值,那么类的构造函数应该只创建对象?也就是说,如果一个实例在创建时具有最小数量的属性,那么只有创建一个基本上可以执行相同操作的Validate(Doc doc)方法,即检查对象的有效性。

答案 8 :(得分:3)

单独(静态?)函数调用:

public static void CheckForNullObject( object Obj, string Message) {
    if(Obj == null){
        throw new Exception(Message);
    }
}

虽然这不是最好的选择,但它会更具可读性。

答案 9 :(得分:2)

该代码示例几乎不可读...当变量可能为null时,您必须检查空值。但是,如果要减少此操作,请确保返回对象的方法永远不会返回null并始终返回完全有效且构造的对象。如果它返回null,它会抛出异常。返回null或-1或其他一些奇怪的约定不应该替代错误处理。

答案 10 :(得分:2)

查看“Maybe Monad”。

它解决了您希望以可读的方式在C#中进行详细的Null检查。

还有Maybe codeplex project

答案 11 :(得分:2)

有几个选项(其中一些已被其他人提及),所以我只是添加到列表中。

  1. 对于某些类型,使用null对象是有意义的。在这种情况下,您必须确保方法永远不会返回一个简单的null,但总是返回一个实例(可能是null对象)。

  2. 如果您想使用Paige建议的静态方法,您甚至可以将其转换为扩展方法。你可以做类似的事情:

     private static void ThrowIfNull(this object o, string message) {
        if (o != null) return;
        throw new ArgumentNullException(message);
     }
    

答案 12 :(得分:2)

合同是减少与空相关的问题和超级检查的关键因素。

在某些情况下,有些方法可能/应该返回null。如果你调用这样的方法,那么你只需要检查。最好尽早检查。

并且有些方法不允许返回null。不要检查他们的返回值。每种方法都有责任确保它履行自己的合同,因此作为该方法的调用者,您无需关心

有一些工具和语言功能可以帮助您记录和检查空检查和合同的正确性。抱歉,我不能再解释了,因为我不是C#程序员。

如果你想深入了解,我推荐这四个问题。它们大多以Java为中心,但C#也几乎都是如此,有时答案甚至可以自定义为.net和c#。

答案 13 :(得分:1)

除非你能够用它们做一些聪明的事情,否则不要捕捉异常。

在这种情况下,您的异常处理程序在默认值上添加的值很少 - 也就是说,让异常传播回调用链。

在你的app / thread的顶级,应该总是有异常处理程序来处理这些未捕获的异常。

编辑:投票失败,我感到被误解,也许我太敏感了;-)。原始海报抛出的例外情况没有任何价值。它们无法帮助最终用户,也无法帮助开发人员。

应用程序中的顶级异常处理程序应该捕获这样的异常并记录它。日志应包括堆栈跟踪。这告诉开发人员错误的来源,并消除了许多真正无用的代码。

如果异常增加了一些价值,那么我同意抛出它是合理的。但这不是这种情况。更糟糕的是,一旦你声明这是一个很好的原则,你会看到更多的代码行检查空引用,以至于代码会被它们弄乱。

答案 14 :(得分:1)

解决那些主张允许运行时抛出NullReferenceException的人:

I started a topic主题是主动抛出ArgumentNullException或让运行时抛出NullReferenceException是个好主意。基本的共识是采用主动方法而不是NullReferenceException方法是个好主意。我并不一定说他们是对的,而在这里提倡否则的人是错的。我只是说社区可能不同意你的意见。

想要指出的是,如果你正在做这些检查的很多,那么你做错事的可能性很大。要么你的方法做得太多,要么你传递了太多的“tramp”参数(除了被传递给另一个函数之外没有其他目的的参数)。也许您应该考虑是否可以将代码更多地分解为单独的方法,或者将一些参数封装到对象中。

答案 15 :(得分:0)

这是一个使用LINQ表达式的解决方案,检查链中的所有成员访问,并在所有内容都有效时返回实际值:

public static T CheckedGet<T>(Expression<Func<T>> expr) where T : class
{
    CheckAccess(expr);
    return expr.Compile().Invoke();
}

public static void CheckAccess(Expression expr)
{
    switch (expr.NodeType)
    {
        case ExpressionType.Lambda:
            CheckAccess((expr as LambdaExpression).Body);
            break;
        case ExpressionType.MemberAccess:
            {
                CheckAccess((expr as MemberExpression).Expression);
                var value = Expression.Lambda(expr).Compile().DynamicInvoke();
                if (value == null)
                {
                    throw new NullReferenceException(expr.ToString());
                }
            }
            break;
        case ExpressionType.ArrayIndex:
            {
                var binaryExpr = expr as BinaryExpression;
                CheckAccess(binaryExpr.Left);
                var arrayLength = (int)Expression.Lambda(Expression.ArrayLength(binaryExpr.Left)).Compile().DynamicInvoke();
                var arrayIndex = (int)Expression.Lambda(binaryExpr.Right).Compile().DynamicInvoke();
                if (arrayIndex >= arrayLength)
                {
                    throw new IndexOutOfRangeException(expr.ToString());
                }
                var value = Expression.Lambda(expr).Compile().DynamicInvoke();
                if (value == null)
                {
                    throw new NullReferenceException(expr.ToString());
                }
            }
            break;
        case ExpressionType.Constant:
            return;
    }
}

有用,例如:

var val = CheckedGet(() => classA.PropertyA.ArrayB[0].FieldC);

它将通过适当的异常检查null的所有成员访问权限和有效的数组长度。

答案 16 :(得分:0)

如果您要检查传递给公共API的部分参数,那么您应该在方法的最开始使用“保护条款”,例如Dan Monego写的。如果您使用IsNotNull等方法创建或重用一些辅助类(如Assert)(或许您真的只需要其中一个),那么代码将很容易阅读。

您的代码如下:

Assert.IsNotNull(doc, "document");
Assert.IsNotNull(doc.Element", "Element");

//... and so on

如果您要检查传递给您的某个私有方法的参数,那么您不应该进行此类检查,而应该添加合同(在方法文档中或如果您使用某些工具时使用某些特殊声明)为此而且总是希望用正确的参数调用这个方法。

答案 17 :(得分:0)

如果您恰好使用C#,请考虑nullable

答案 18 :(得分:-3)

你总是可以写:

if (doc == null && doc.Element == null
{

}

但是你在我上面的一个帖子中丢失了粒度(例如Exception中的单独消息)。