C# 我如何管理空检查的冲击?

C# 我如何管理空检查的冲击?,c#,.net,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"); } 基本上,整件事都变成了一场无法读懂的噩梦,

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


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

那个代码示例几乎是不可读的。。。当变量可能为null时,必须检查null。但是,如果您想减少这种情况,请确保返回对象的方法从不返回null,而是始终返回完全有效且构造的对象。让它在返回null的情况下抛出异常。返回null或-1或其他一些奇怪的约定不应该代替错误处理。

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

doc.Element.whatever("foo");

您仍然会得到一个NullPointerException(或C#中的任何内容),其中包含完整的回溯信息。

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

if (doc == null)
    throw new ArgumentNullException("doc");
if (doc.Element == null)
    throw new ArgumentException("Element cannot be null");
AndSoOn(); 
单独(静态?)函数调用:

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

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

如果属性被初始化为适当的值,那么类的构造函数应该只创建对象吗?也就是说,只有当实例在创建时具有最少数量的属性时,才会创建实例,而不是创建一个验证(Doc-Doc)方法来执行基本相同的操作,即检查对象的有效性。

您可能对该实例感兴趣

在过去,它帮助我在很多情况下消除了空检查

示例(C++)


除非您能够对异常进行智能处理,否则不要捕获异常

在这种情况下,您的异常处理程序与默认值相比几乎没有什么价值——也就是说,让异常传播回调用链

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

编辑:被否决了,我觉得被误解了,也许我太敏感了;-)。原始海报抛出的异常没有任何价值。它们既不能帮助最终用户,也不能帮助开发人员

应用程序中的顶级异常处理程序应该捕获这样的异常并记录它。日志应该包括堆栈跟踪。这将告诉开发人员错误来自何处,并消除许多实际上没有任何用途的代码行


如果异常增加了一些值,那么我同意抛出它是合理的。但事实并非如此。更糟糕的是,一旦你声明这是一个好的原则,你会看到更多的代码检查空引用,以至于代码会被它们弄得乱七八糟。

如果一个典型的
NullReferenceException
就可以了,不要麻烦检查,让运行时抛出它。如果您需要添加上下文错误信息,出于日志记录或调试目的(或其他任何目的),请继续并将您的验证考虑到不同的方法中。在这种情况下,我仍然鼓励您抛出一个嵌套了原始异常的
NullReferenceException

当我被迫手动浏览深层XML文档时,我也不得不做类似的事情


通常,我试图在类中的接口边界强制执行空正确性。然后,我可以忽略我的私有方法中的空检查。

如果您觉得它由于嵌套的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()

你可以写:

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

}
但是您在解决方案的粒度上比我的帖子高一个帖子(例如,在异常情况下会出现单独的消息)。

请查看“”

它满足了您对在C#中执行详细空检查的可读方式的需求


还有

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

我讨论了主动抛出ArgumentNullException还是让运行时抛出NullReferenceException的问题。基本的共识是,采取主动的方法而不是NullReferenceException方法是个好主意。我并不是说他们是对的,而在这里鼓吹其他观点的人是错的。我只是说社区可能不同意你


我想指出的是,如果你做了大量这类检查,很有可能你做错了什么。要么你的方法做得太多,要么你传递了太多的“tramp”参数(这些参数除了传递给另一个函数之外没有其他用途)。也许你应该考虑是否可以将代码分解成更多的方法,或者将一些参数封装到一个对象中。

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

  • 对于某些类型,使用null对象是有意义的。在这种情况下,您必须确保方法从不返回简单的null,而是始终返回实例(可能是null对象)

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

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

  • 您可能对感兴趣:

    Spec#是一种用于API契约的正式语言,它使用用于 非空类型、前置条件、后置条件、对象不变性
     private static void ThrowIfNull(this object o, string message) {
        if (o != null) return;
        throw new ArgumentNullException(message);
     }
    
    public static void Clear(int[]! xs) // xs is now a non-null type
    {
        for (int i = 0; i < xs.Length; i++)
        {
            xs[i] = 0;
        }
    }
    
    int[] xs = null;
    Clear(xs);   // Error: null is not a valid argument
    
    Assert.IsNotNull(doc, "document");
    Assert.IsNotNull(doc.Element", "Element");
    
    //... and so on
    
    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);