是否存在链式空值检查?

7

我有以下糟糕的代码:

if (msg == null || 
    msg.Content == null || 
    msg.Content.AccountMarketMessage == null || 
    msg.Content.AccountMarketMessage.Account == null ||
    msg.Content.AccountMarketMessage.Account.sObject == null) return;

有没有一种方法可以在C#中链接检查空值,以便我不必检查每个单独的级别?

很遗憾,虽然这会很好,但事实并非如此。 - Gusman
1
可能是Null coalescing operator override的重复问题。请参见该问题中的我的回答 - Federico Berasategui
1
我相信C#6会让你这样做:if(msg.?Content.?AccountMarketMessage.?Account.?sObject == null) return; - Lee
7个回答

9

在C# 6中,提案之一是添加一个新的Null Propogation运算符

这将(希望能够)允许您编写:

var obj = msg?.Content?.AccountMarketMessage?.Account?.sObject;
if (obj == null) return;

很遗憾,目前该语言中没有处理此问题的功能。


我思考了一段时间...我不确定我喜欢这个语法。也许用双点符号表示?msg..Content..AccountMarketMessage..Account..sObject。嗯..即使是这样看起来也很奇怪。但我一定会期待它的到来。 - Simon Whitehead
@SimonWhitehead 我刚刚粘贴的第二个链接是提出替代方案的好地方。 - Reed Copsey
问号似乎没问题,因为它具有相当成熟的“可空”的含义。 - BartoszKP
@BartoszKP 这无疑是选择它的原因。只是在一起输入 ?. 感觉不自然(也许以后会习惯吧)。 - Simon Whitehead

4
目前还没有这样的东西,但它可能很快就会出现在.NET上。关于这个问题,有一个众所周知的用户声音线程。正如这篇文章中所述,Visual Studio团队最近宣布:

我们正在认真考虑为C#和VB添加此功能,并将在未来几个月内进行原型设计。

编辑:正如Reed Copsey在上面的回答中所提到的,它现在是C# 6的计划添加功能。他链接的Codeplex页面上有更好的细节说明。


2

虽然没有内置支持,但是您可以使用扩展方法来实现:

public static bool IsNull<T>(this T source, string path)
{
     var props = path.Split('.');
     var type = source.GetType();

     var currentObject = type.GetProperty(props[0]).GetValue(source);

     if (currentObject == null) return true;
     foreach (var prop in props.Skip(1))
     {
          currentObject = currentObject.GetType()
                .GetProperty(prop)
                .GetValue(currentObject);

         if (currentObject == null) return true;
     }

     return false;
}

然后调用它:

if ( !msg.IsNull("Content.AccountMarketMessage.Account.sObject") )  return;

2
这个方法可以工作,但是非常容易出错(魔术字符串)并且性能很差... - Reed Copsey

2

您可以使用lambda表达式来懒惰地评估值。这对于简单的空值检查来说有些过度,但对于以“流畅”的方式链接更复杂的表达式可能非常有用。

示例

// a type that has many descendents
var nested = new Nested();

// setup an evaluation chain
var isNull =
    NullCheck.Check( () => nested )
        .ThenCheck( () => nested.Child )
        .ThenCheck( () => nested.Child.Child )
        .ThenCheck( () => nested.Child.Child.Child )
        .ThenCheck( () => nested.Child.Child.Child.Child );

// handle the results
Console.WriteLine( isNull.IsNull ? "null" : "not null" );

代码

这是一个完整的示例(虽然是草稿质量的代码),可以粘贴到控制台应用程序或LINQPad中。

public class Nested
{
  public Nested Child
  {
      get;
      set;
  }
}

public class NullCheck
{
   public bool IsNull { get; private set; }

   // continues the chain
   public NullCheck ThenCheck( Func<object> test )
   {
       if( !IsNull )
       {
           // only evaluate if the last state was "not null"
           this.IsNull = test() == null;
       }

       return this;
   }

   // starts the chain (convenience method to avoid explicit instantiation)
   public static NullCheck Check( Func<object> test )
   {
       return new NullCheck { IsNull = test() == null };
   }
}

private void Main()
{
   // test 1
   var nested = new Nested();
   var isNull =
       NullCheck.Check( () => nested )
           .ThenCheck( () => nested.Child )
           .ThenCheck( () => nested.Child.Child )
           .ThenCheck( () => nested.Child.Child.Child )
           .ThenCheck( () => nested.Child.Child.Child.Child );

   Console.WriteLine( isNull.IsNull ? "null" : "not null" );

   // test 2
   nested = new Nested { Child = new Nested() };
   isNull = NullCheck.Check( () => nested ).ThenCheck( () => nested.Child );

   Console.WriteLine( isNull.IsNull ? "null" : "not null" );

   // test 3
   nested = new Nested { Child = new Nested() };
   isNull = NullCheck.Check( () => nested ).ThenCheck( () => nested.Child ).ThenCheck( () => nested.Child.Child );

   Console.WriteLine( isNull.IsNull ? "null" : "not null" );
}

再次强调: 由于引入的复杂性,在简单的null检查中不应使用此方法,但它是一种有趣的模式。

2
您需要使用单子和单子空值检查。可以参考Monads.Net包。它可以帮助简化空值测试以及获取深层导航属性的值。
类似于下面这样的内容:
var sObject = person.With(p=>p.Content).With(w=>w.AccountMarketMessage ).With(p=>p.Account).With(p=>p.Object);

如果你需要一个默认值,那么
var sObject = person.With(p=>p.Content).With(w=>w.AccountMarketMessage).With(p=>p.Account).Return(p=>p.Object, "default value");

0

.NET Fiddle

正如所述,计划在c# 6.0中实现?运算符以在某种程度上方便这个过程。如果您不能等待,我建议使用lambda表达式和一个简单的帮助函数来解决这个问题。

public E NestedProperty<T,E>(T Parent, Func<T,E> Path, E IfNullOrEmpty = default(E))
{
    try
    {
        return Path(Parent);
    }
    catch
    {
        return IfNullOrEmpty;
    }
}

这可以在下面的演示中使用:int value = NestedProperty<First,int>(blank,f => f.Second.Third.id);

程序

public class Program
{
    public void Main()
    {
        First blank = new First();
        First populated = new First(true);

        //where a value exists
        int value = NestedProperty<First,int>(blank,f => f.Second.Third.id);
        Console.WriteLine(value);//0

        //where no value exists
        value = NestedProperty<First,int>(populated,f => f.Second.Third.id);
        Console.WriteLine(value);//1

        //where no value exists and a default was used
        value = NestedProperty<First,int>(blank,f => f.Second.Third.id,-1);
        Console.WriteLine(value);//-1
    }

    public E NestedProperty<T,E>(T Parent, Func<T,E> Path, E IfNullOrEmpty = default(E))
    {
        try
        {
            return Path(Parent);
        }
        catch
        {
            return IfNullOrEmpty;
        }
    }
}

简单的演示结构

public class First
{
    public Second Second { get; set; }
    public int id { get; set; }
    public First(){}
    public First(bool init)
    {
        this.id = 1;
        this.Second = new Second();
    }
}

public class Second
{
    public Third Third { get; set; }
    public int id { get; set; }
    public Second()
    {
        this.id = 1;
        this.Third = new Third();
    }
}

public class Third
{
    public int id { get; set; }
    public Third()
    {
        this.id = 1;
    }
}

0

从3.5版本开始(或更早),您可以编写非常简单的扩展方法

  public static TResult DefaultOrValue<T, TResult> (this T source, 
                                                Func<T, TResult> property) where T : class
    {
        return source == null ? default(TResult) : property(source);
    }

你可以将这个方法命名得更短,并像这样使用

 var instance = new First {SecondInstance = new Second 
                          {ThirdInstance = new Third {Value = 5}}};
        var val =
            instance .DefaultOrValue(x => x.SecondInstance)
                .DefaultOrValue(x => x.ThirdInstance)
                .DefaultOrValue(x => x.Value);
        Console.WriteLine(val);
        Console.ReadLine();

所以源代码类是:

public class Third
{
    public int Value;
}

public class First
{
    public Second SecondInstance;
}

public class Second
{
    public Third ThirdInstance;
}

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