C# 检查对象层次结构中的null

C# 检查对象层次结构中的null,c#,c#-3.0,C#,C# 3.0,我有一个大型C#(3.0)对象结构,它源自反序列化的XML文档。我需要知道层次结构中的一个变量是否为null。我现在这样做的方式是检查每个父对象是否为null,但这会导致if语句的长时间重复 我正在努力避免昂贵的试捕积木 有没有更聪明的方法 编辑: 例如,在将XML应用程序表单反序列化为对象层次结构之后,可能会有一个工资值 applicationForm.employeeInfo.workingConditions.salary 但是为了安全地找到答案,我必须写下 if (applicatio

我有一个大型C#(3.0)对象结构,它源自反序列化的XML文档。我需要知道层次结构中的一个变量是否为null。我现在这样做的方式是检查每个父对象是否为null,但这会导致if语句的长时间重复

我正在努力避免昂贵的试捕积木

有没有更聪明的方法

编辑: 例如,在将XML应用程序表单反序列化为对象层次结构之后,可能会有一个工资值

applicationForm.employeeInfo.workingConditions.salary
但是为了安全地找到答案,我必须写下

if (applicationForm.employeeInfo != null)
  if (applicationForm.employeeInfo.workingConditions != null)
    if (applicationForm.employeeInfo.workingConditions.salary != null)
因为如果父对象之一为null,那么简单地使用后一个if语句当然会失败


因此,我正在寻找更聪明的方法来处理这种情况。

首先,如果您在多个地方重复逻辑,请将其封装在一个方法中

其次,你不需要很多if语句,你只需要一个有很多OR条件的语句:

if(parent==null || 
   parent.Child == null || 
   parent.Child.GrandChild == null ...
第三,“避免昂贵的try/catch块”可能是过早的优化,这取决于您的场景。您是否真的尝试过这个方法并对其进行了分析,它是否真的会造成很大的开销?

您不能迭代吗

for (SomeObject obj = someInstance; obj != null; obj = obj.Parent) {
    //do something
}
使用反射


创建一个helper方法,该方法接受父对象和属性名的分层点符号字符串。然后使用PropertyInfo和递归每次检查一个属性是否为null并返回null,如果是,则返回null,否则继续沿着层次结构进行操作。

由于您没有提供太多的详细信息,我不得不填补很多空白。下面是一个PSOO代码示例,说明如何通过递归实现这一点

CheckForNull(MyType element)
{
    if(element.ChildElement != null)
    {
        CheckForNull(element.ChildElement);
    }
    else
    {
        Console.WriteLine("Null Found");
    }
}
    public bool doesHierarchyContainANull(MyObject ParentObject)
    {

        if (ParentObject.getMemberToCheckForNull() == null)
            return true;
        else if (ParentObject.isLastInHierarchy())
            return false;

        return doesHierarchyContainANull(ParentObject.getNextInHierarchy());

    }

您需要一个递归函数来迭代结构,并检查每个节点及其子节点是否为null。我正在制作一个样品,但IE崩溃了(典型!!)。稍后将发布一个

样品

您可以做如下简单的事情(假设您只想检查结构是否有效):


您遇到了一种典型情况,
A.B.C.D
中的每个步骤都可能产生
null
。尽管这是一个常见的场景,但令人惊讶的是,除了使用带有大量or(
| |
)的大型if语句之外,没有解决它的通用模式

如果每个步骤都可以返回不同的类,那么您可以应用一种很少使用的模式:使用带有方法链接的通用扩展方法

广义扩展方法不是一个固定的术语,但我在这里使用它来强调ext.方法适用于几乎所有类型的对象,因此是广义的。据我所知,这是一个糟糕的设计。但在一些远程情况下,比如你的,只要你知道你在做什么以及为什么,你就可以使用它

诀窍很简单:定义一个泛型扩展方法,并对所有具有默认构造函数的类进行泛化。如果测试失败(对象为null),该方法将返回相同类型的新对象。否则,它将返回未更改的对象本身

为什么这对您的场景来说是一个好方法?因为您不需要更改任何现有类,因为它可以理解并促进可读代码,因为它保持了类型安全性(编译时错误而不是运行时错误),并且与其他方法相比更简洁

// extension method:
public static class SomeExtentionMethods
{
    public static T SelfOrDefault<T>(this T elem)
        where T : class, new()     /* must be class, must have ctor */
    {
        return elem ?? new T();    /* return self or new instance of T if null */
    }
}

// your code now becomes very easily readable:
Obj someObj = getYourObjectFromDeserializing();

// this is it applied to your code:
var mySalary = applicationForm.SelfOrDefault().
    employeeInfo.SelfOrDefault().
    workingConditions.SelfOrDefault().
    salary;

// now test with one if-statement:
if(mySalary.IsEmpty())
   // something in the chain was empty
else
   // all's well that ends well :)
更新:扩展了一点,添加了一个更详细的示例
更新:
NotNull
重命名为
SelfOrDefault
,这意味着布尔值,它遵循LINQ的命名约定(
FirstOrDefault
等),并暗示了它的功能。

更新:重写和重新组织,使代码更适用,希望总体上更易于理解:)

您可以嵌套三元运算符。仍然很痛苦,但没有嵌套ifs那么糟糕

string salary = (applicationForm.employeeInfo == null) ? null :
                (applicationForm.employeeInfo.workingConditions == null) ? null :
                applicationForm.employeeInfo.workingConditions.salary;
如果您只想知道它是否为空:

bool hasSalary = (applicationForm.employeeInfo == null) ? false :
                 (applicationForm.employeeInfo.workingConditions == null) ? false :
                 (applicationForm.employeeInfo.workingConditions.salary != null);

我的解决方案是:

public static TResult SafeGet<TSource, TResult>(this TSource source, Func<TSource, TResult> getResult) {
    if (source == null)
        return default(TResult);
    try {
        return getResult(source);
    }
    catch {
        return default(TResult);
    }
}

我喜欢Pontus Bremdahl的答案,但为我的使用添加了更多细节。 代码:

更新 CSharp版本6现在内置了此功能。

空条件运算符 有时,代码往往会在空检查中淹没一些内容。空条件运算符仅允许在接收方不为空时访问成员和元素,否则将提供空结果:

int? length = customers?.Length; // null if customers is null
Customer first = customers?[0];  // null if customers is null
空条件运算符可以方便地与空合并运算符一起使用:

int length = customers?.Length ?? 0; // 0 if customers is null
null条件运算符表现出短路行为,其中仅当原始接收方不为null时,才会执行紧跟其后的成员访问、元素访问和调用链:

int? first = customers?[0].Orders.Count();
该示例实质上等同于:

int? first = (customers != null) ? customers[0].Orders.Count() : null;
除了
客户
仅评估一次。除非
客户
具有非空值,否则不会执行紧跟
之后的任何成员访问、元素访问和调用

当然,null条件运算符本身也可以链接,以防需要在一个链中多次检查null:

int? first = customers?[0].Orders?.Count();
请注意,调用(带括号的参数列表)不能立即跟随
运算符–这将导致太多语法歧义。因此,仅在委托存在时调用委托的简单方法不起作用。但是,您可以通过委托上的
Invoke
方法执行此操作:

if (predicate?.Invoke(e) ?? false) { … }
我们预计该模式的一个非常常见的用途是触发事件:

PropertyChanged?.Invoke(this, args);

这是一种在触发事件之前检查null的简单且线程安全的方法。它之所以是线程安全的,是因为该功能只对左侧求值一次,并将其保存在一个临时变量中。

使用Null monad。它可以在同一个文件中,也可以在不同的文件中,只要您
使用它

public static class NullMonad {
    public static TResult SelectMany<TIn, TOut, TResult>(this TIn @in, Func<TIn, TOut> remainder, Func<TIn, TOut, TResult> resultSelector)
        where TIn : class
        where TOut : class
        where TResult : class {
        var @out = @in != null ? remainder(@in) : null;
        return @out != null ? resultSelector(@in, @out) : null;
    }
}
如果存在,则返回工资;如果存在,则返回null
int? first = (customers != null) ? customers[0].Orders.Count() : null;
int? first = customers?[0].Orders?.Count();
if (predicate?.Invoke(e) ?? false) { … }
PropertyChanged?.Invoke(this, args);
public static class NullMonad {
    public static TResult SelectMany<TIn, TOut, TResult>(this TIn @in, Func<TIn, TOut> remainder, Func<TIn, TOut, TResult> resultSelector)
        where TIn : class
        where TOut : class
        where TResult : class {
        var @out = @in != null ? remainder(@in) : null;
        return @out != null ? resultSelector(@in, @out) : null;
    }
}
var salary = from form in applicationForm
             from info in form.employeeInfo
             from cond in info.workingConditions
             select cond.salary
if (applicationForm?.employeeInfo?.workingConditions?.salary != null)