How to check for nulls in a deep lambda expression? [duplicate]
Asked Answered
A

10

16

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.

Aindrea answered 12/5, 2009 at 20:1 Comment(0)
A
16

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.

Ataraxia answered 12/5, 2009 at 20:17 Comment(1)
this is far more concise one.Maybe(x=>x.Two.Three.Four.Foo); see maybe.codeplex.comUnlikely
A
19

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);
Abstriction answered 12/5, 2009 at 20:7 Comment(3)
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.Aindrea
I edited my answer and added a code sample, which compiles fine and should do the trick.Abstriction
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?Ephah
A
16

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.

Ataraxia answered 12/5, 2009 at 20:17 Comment(1)
this is far more concise one.Maybe(x=>x.Two.Three.Four.Foo); see maybe.codeplex.comUnlikely
B
7

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.

Backbencher answered 12/5, 2009 at 21:57 Comment(4)
This would be great! Delphi prism does the same with its ":" operator: prismwiki.codegear.com/en/Colon_OperatorAtaraxia
I second that. This would make lots of code so much cleaner!Protohistory
This feature is now in c# 6!Ataraxia
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 TreesMutton
U
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);
Unlikely answered 19/5, 2010 at 14:52 Comment(4)
Does using expression tree effects performance?Fraunhofer
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.Unlikely
Do you have any performance stats of your (or someone else) usage?Fraunhofer
I do not. If you try it out perhaps you could share the results with me? =)Unlikely
M
3

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.

Monarda answered 10/1, 2013 at 11:39 Comment(0)
W
2

I'm not skilled in c#, but maybe there's some way to implement the "andand" pattern from ruby that solves exactly this problem without polluting the implementation.

The concept is also known as the Maybe Monad in Haskell.

The title of this article seems promising.

Woodwind answered 12/5, 2009 at 21:7 Comment(3)
Interesting, the article is almost identical to the solution I came up by thinking about it, see my post...Abstriction
Wow, completely missed it, I guess I was looking for the word "maybe"Woodwind
maybe.codeplex.com can do it.Unlikely
R
0

Always initialize your properties before using them. Add a constructor to class One, Two, Three and Four. In the constructor initialize your properties so they are not null.

Relent answered 12/5, 2009 at 20:17 Comment(1)
I usually do, but in this domain, the properties can get set to null sometimes. This is valid behavior.Aindrea
R
0

You could modify your getters to read something like:

private Two _two;
public Two Two
{
     get 
     {
       if (null == _two)
         return new Two();
       else
         return _two;
      }
}
Reich answered 12/5, 2009 at 20:44 Comment(4)
Modifying the implementation to save some lines in the client code should fire all kinds of alarms.Woodwind
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.Reich
If I keep calling Two while _two is null, I keep getting new instances of Two... ewRodina
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;Reich
G
0

I find the coalesce operator useful for this at times. This only helps though if there is a default/null equivalent version of the object you can drop in.

For instance, sometimes when I'm cracking open XML...

IEnumeratable<XElement> sample;
sample.Where(S => (S.Attribute["name"] ?? new XAttribute("name","")).Value.StartsWith("Hello"))...

Depending on how the default objects are retrieved this can be verbose, and the above example is not a great use but you get the idea. For the particular case of reading XML attributes I have an extension method that returns the attribute value or an empty string.

Guibert answered 12/5, 2009 at 21:22 Comment(0)
L
0

I converted a function that used a lot of if statements to avoid the nulls to the .IFNotNull method for classes that I converted from an XSD that are 4 and 5 levels deep.

Here are a few lines of the converted code:

ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_VALUE = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.VALUE).ToDouble();  
ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_UOM = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.UOM);  

Here are some interesting stats about it:

1) This new method took 3.7409 times longer to run that the variation with the If Statements.
2) I decreased my function line count from 157 to 59.
3) CodeRush from DevExpress has a "Maintenance Complexity" score. When I converted to the Lambda statements, it increased from 984 to 2076, which is theoretically much harder to maintain.

Lyonnais answered 16/8, 2012 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.