Is there such a thing as a chained NULL check?

前端 未结 7 449
灰色年华
灰色年华 2021-01-18 05:30

I have the following ugly code:

if (msg == null || 
    msg.Content == null || 
    msg.Content.AccountMarketMessage == null || 
    msg.Content.AccountMarke         


        
相关标签:
7条回答
  • 2021-01-18 06:07

    .NET Fiddle

    As stated there is a plan to have c# 6.0 implement the ? operator to facilitate this process somewhat. If you cannot wait, I would suggest using a lambda expression and a simple helper function to solve this.

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

    This could be used int value = NestedProperty<First,int>(blank,f => f.Second.Third.id); as shown in the demo below

    program

    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;
            }
        }
    }
    

    simple demo structure

    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 讨论(0)
  • 2021-01-18 06:12

    You can lazily evaluate the values using lambda expressions. This is overkill for a simple null check, but can be useful for chaining more complex expressions in a "fluent" manner.

    Example

    // 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" );
    

    Code

    This is a full example (albeit draft-quality code) that can be pasted into a console app or 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" );
    }
    

    Again: you probably shouldn't use this in lieu of simple null checks due to the complexity it introduces, but it's an interesting pattern.

    0 讨论(0)
  • 2021-01-18 06:20

    There is no built-in support for this, but you can use an extension method for that:

    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;
    }
    

    Then call it:

    if ( !msg.IsNull("Content.AccountMarketMessage.Account.sObject") )  return;
    
    0 讨论(0)
  • 2021-01-18 06:21

    One of the proposals in C# 6 would be to add a new Null Propogation operator.

    This will (hopefully) allow you to write:

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

    Unfortunately, there is nothing in the language at this point that handles this.

    0 讨论(0)
  • 2021-01-18 06:24

    You need monads and Monadic null checking. Could have a look at Monads.Net package. It can help with simplifying null tests and getting values from deep navigation properties

    Something like

    var sObject = person.With(p=>p.Content).With(w=>w.AccountMarketMessage ).With(p=>p.Account).With(p=>p.Object);
    

    If you wanted a default value then

    var sObject = person.With(p=>p.Content).With(w=>w.AccountMarketMessage).With(p=>p.Account).Return(p=>p.Object, "default value");
    
    0 讨论(0)
  • 2021-01-18 06:28

    Since 3.5 (maybe earlier), You could write very simple extension method

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

    You may name this method even shortier and then use like this

     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();
    

    so source classes are:

    public class Third
    {
        public int Value;
    }
    
    public class First
    {
        public Second SecondInstance;
    }
    
    public class Second
    {
        public Third ThirdInstance;
    }
    
    0 讨论(0)
提交回复
热议问题