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

通常,在编程中我们会遇到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东西。)

接受的答案:我接受了有这个链接的答案,因为所描述的方法是创新的,而且正是我想要的。 谢谢肖恩!

看看这篇文章: 一种流畅的C#参数validation方法

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

将它们向前推到函数的开头,并将它们保留在执行工作的代码部分之外。 像这样:

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

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

 doc.Element.whatever("foo"); 

您仍将获得具有完整回溯信息的NullPointerException(或C#中的任何内容)。

您可能对Spec#感兴趣:

Spec#是API契约的forms语言,它使用非空类型 ,前置条件,后置条件,对象不变量和模型程序(将整个运行历史考虑在内的行为契约)构造扩展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 

作为旁注,您可能还想确保您没有违反得墨忒耳法则 。

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

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

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

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

如果一个典型的NullReferenceException会做,请不要打扰检查,只是让运行时抛出它。 如果您需要添加上下文错误信息,以进行日志记录或调试(或其他任何操作),请继续将validation考虑到其他方法中。 在这种情况下,我仍然鼓励你在嵌套原始exception的情况下抛出NullReferenceException

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

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

如果你因为嵌套的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() 

您可能对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"); } } 

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

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

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

  2. 如果你想使用Paige建议的静态方法,你甚至可以把它变成一个扩展方法。 你可以做类似的事情:

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

看看“ 也许莫纳德 ”。

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

还有也许是codeplex项目

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

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

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

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

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

  • 如何避免java中的“!= null”语句?
  • 如何在getter链中跟踪NullPointerException
  • 返回’null’或抛出exception
  • 如何显示方法是否可以返回null

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

我开始讨论一个主题 ,即主动抛出ArgumentNullException或让运行时抛出NullReferenceException是个好主意。 基本共识是采用主动方法而不是NullReferenceException方法是个好主意。 我并不一定说他们是正确的,而在这里提倡否则的人是错的。 我只是说社区可能不同意你的观点。

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

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

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

在应用程序/线程的顶层,应该始终有exception处理程序来处理这些未捕获的exception。

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

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

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

如果你恰好使用C#考虑可以为空 。

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

您的代码将如下所示:

 Assert.IsNotNull(doc, "document"); Assert.IsNotNull(doc.Element", "Element"); //... and so on 

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

这是一个使用LINQ表达式的解决方案,检查链中的所有成员访问,如果一切有效,则返回实际值:

 public static T CheckedGet(Expression> 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); 

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

你总是可以写:

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

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