How to check for nulls in a deep lambda expression?
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.
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, 2020Comments
-
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 almost 15 yearsIn 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 almost 15 yearsI usually do, but in this domain, the properties can get set to null sometimes. This is valid behavior.
-
Lucero almost 15 yearsI edited my answer and added a code sample, which compiles fine and should do the trick.
-
Lucero almost 15 yearsInteresting, the article is almost identical to the solution I came up by thinking about it, see my post...
-
krusty.ar almost 15 yearsWow, completely missed it, I guess I was looking for the word "maybe"
-
krusty.ar almost 15 yearsModifying the implementation to save some lines in the client code should fire all kinds of alarms.
-
Josh E almost 15 yearsI 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 almost 15 yearsgood 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 almost 15 yearsThis would be great! Delphi prism does the same with its ":" operator: prismwiki.codegear.com/en/Colon_Operator
-
Nicolas Fall almost 14 yearsmaybe.codeplex.com can do it.
-
Nicolas Fall almost 14 yearsthis is far more concise one.Maybe(x=>x.Two.Three.Four.Foo); see maybe.codeplex.com
-
Simon D. about 13 yearsThat 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 almost 12 yearsI second that. This would make lots of code so much cleaner!
-
Michael Freidgeim almost 12 yearsDoes using expression tree effects performance?
-
Nicolas Fall almost 12 yearsdoes 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 almost 12 yearsDo you have any performance stats of your (or someone else) usage?
-
Nicolas Fall almost 12 yearsI do not. If you try it out perhaps you could share the results with me? =)
-
Ibrahim Quraish about 8 yearsThis feature is now in c# 6!
-
Alielson Piffer over 6 yearsI 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