How to check for nulls in a deep lambda expression?

28,389

Solution 1

You can't do that in a concise way. You can either make the lambda multiple lines, or use nested ternary operators:

var result = GetValue(one, x => x.Two == null ? null :
                                x.Two.Three == null ? null :
                                x.Two.Three.Four == null ? null :
                                x.Two.Three.Four.Foo;

Ugly, I know.

Solution 2

You could do this with a generic helper extension method, something like:

public static class Get {
    public static T IfNotNull<T, U>(this U item, Func<U, T> lambda) where U: class {
        if (item == null) {
            return default(T);
        }
        return lambda(item);
    }
}

var one = new One();
string fooIfNotNull = one.IfNotNull(x => x.Two).IfNotNull(x => x.Three).IfNotNull(x => x.Four).IfNotNull(x => x.Foo);

Solution 3

Doing this concisely requires an as-yet-unimplemented operator. We considered adding an operator ".?" to C# 4.0 which would have your desired semantics, but unfortunately it did not fit into our budget. We'll consider it for a hypothetical future version of the language.

Solution 4

You can now do using the Maybe project on codeplex.

Syntax is:

string result = One.Maybe(o => o.Two.Three.Four.Foo);

string cityName = Employee.Maybe(e => e.Person.Address.CityName);

Solution 5

I've written an extension method which enables you to do this:

blah.GetValueOrDefault(x => x.Two.Three.Four.Foo);

It uses Expression Trees to build a nested conditional checking for nulls at each node before returning the expression value; the created expression tree is compiled to a Func and cached, so subsequent uses of the same call should run at almost native speed.

You can also pass in a default value to return if you like:

blah.GetValueOrDefault(x => x.Two.Three.Four.Foo, Foo.Empty);

I've written a blog about it here.

Share:
28,389
JohnRudolfLewis
Author by

JohnRudolfLewis

John Rudolf Lewis is a software architect and developer who works primarily with the .NET Framework using C#, but is branching out into other technologies. He was born near Minneapolis, Minnesota, USA in 1974. He works for a large payment processing company based out of Mountain View, CA, but lives and works in the Seattle area. When he's not coding, he enjoys spending time with his family, reading science fiction novels, playing video games, flying small aircraft, and scuba diving.

Updated on October 10, 2020

Comments

  • JohnRudolfLewis
    JohnRudolfLewis over 3 years

    How can I check for nulls in a deep lamda expression?

    Say for example I have a class structure that was nested several layers deep, and I wanted to execute the following lambda:

    x => x.Two.Three.Four.Foo
    

    I want it to return null if Two, Three, or Four were null, rather than throwing a System.NullReferenceException.

    public class Tests
    {
        // This test will succeed
        [Fact]
        public void ReturnsValueWhenClass2NotNull()
        {
            var one = new One();
            one.Two = new Two();
            one.Two.Three = new Three();
            one.Two.Three.Four = new Four();
            one.Two.Three.Four.Foo = "blah";
    
            var result = GetValue(one, x => x.Two.Three.Four.Foo);
    
            Assert.Equal("blah", result);
        }
    
        // This test will fail
        [Fact]
        public void ReturnsNullWhenClass2IsNull()
        {
            var one = new One();
    
            var result = GetValue(one, x => x.Two.Three.Four.Foo);
    
            Assert.Equal(null, result);
        }
    
        private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
        {
            var func = expression.Compile();
            var value = func(model);
            return value;
        }
    
        public class One
        {
            public Two Two { get; set; }
        }
    
        public class Two
        {
            public Three Three { get; set; }
        }
    
        public class Three
        {
            public Four Four { get; set; }
        }
    
        public class Four
        {
            public string Foo { get; set; }
            public string Bar { get; set; }
        }
    }
    

    UPDATE:

    One solution would be to catch the NullReferenceException like this:

        private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
        {
            TResult value;
            try
            {
                var func = expression.Compile();
                value = func(model);
            }
            catch (NullReferenceException)
            {
                value = default(TResult);
            }
            return value;
        }
    

    But I hate to incur the expense of catching an exception that is not, in my mind, exceptional. I expect this to be the case quite often in my domain.

    UPDATE 2:

    Another solution would be modify the property getters like this:

        public class One
        {
            private Two two;
            public Two Two
            {
                get
                {
                    return two ?? new Two();
                }
                set
                {
                    two = value;
                }
            }
        }
    

    Which is mostly ok for my domain, but there are times when I really to expect a property to return null. I checked the answer from Josh E as helpful since it comes pretty close to what I need in some cases.

  • JohnRudolfLewis
    JohnRudolfLewis almost 15 years
    In that case, I'd want to return the default value for whatever type Foo or Bar were. What I really want to avoid is the exception if something further up in the expression tree was null.
  • JohnRudolfLewis
    JohnRudolfLewis almost 15 years
    I usually do, but in this domain, the properties can get set to null sometimes. This is valid behavior.
  • Lucero
    Lucero almost 15 years
    I edited my answer and added a code sample, which compiles fine and should do the trick.
  • Lucero
    Lucero almost 15 years
    Interesting, the article is almost identical to the solution I came up by thinking about it, see my post...
  • krusty.ar
    krusty.ar almost 15 years
    Wow, completely missed it, I guess I was looking for the word "maybe"
  • krusty.ar
    krusty.ar almost 15 years
    Modifying the implementation to save some lines in the client code should fire all kinds of alarms.
  • Josh E
    Josh E almost 15 years
    I tend to disagree that this is an issue: I would call this defensive coding. The code above ensures that the value of a property is never null without sharing that knowledge with any consumer of that property / object.
  • Josh E
    Josh E almost 15 years
    good point. In that case, you could modify it to set _two to a new Two() instance before returning it, e.g. if (null == _two) _two = new Two(); return _two;
  • Ibrahim Quraish
    Ibrahim Quraish almost 15 years
    This would be great! Delphi prism does the same with its ":" operator: prismwiki.codegear.com/en/Colon_Operator
  • Nicolas Fall
    Nicolas Fall almost 14 years
    maybe.codeplex.com can do it.
  • Nicolas Fall
    Nicolas Fall almost 14 years
    this is far more concise one.Maybe(x=>x.Two.Three.Four.Foo); see maybe.codeplex.com
  • Simon D.
    Simon D. about 13 years
    That easy? That elegant? +1 And no runtime performance hit due to reflection. Did anyone benchmark this against Gabe's solution or the 'normal' approach?
  • mdonatas
    mdonatas almost 12 years
    I second that. This would make lots of code so much cleaner!
  • Michael Freidgeim
    Michael Freidgeim almost 12 years
    Does using expression tree effects performance?
  • Nicolas Fall
    Nicolas Fall almost 12 years
    does doing something different change the performance characteristics? yes. is it actually enough that you or a user would notice? I don't know your usage, profile it.
  • Michael Freidgeim
    Michael Freidgeim almost 12 years
    Do you have any performance stats of your (or someone else) usage?
  • Nicolas Fall
    Nicolas Fall almost 12 years
    I do not. If you try it out perhaps you could share the results with me? =)
  • Ibrahim Quraish
    Ibrahim Quraish about 8 years
    This feature is now in c# 6!
  • Alielson Piffer
    Alielson Piffer over 6 years
    I tried using "?." in this question case, but it is not allowed... It's causing error Error CS8072 An expression tree lambda may not contain a null propagating operator. Some discussion about here: Null-propagating operator ?. in Expression Trees