When I get deserialized XML result into xsd-generated tree of objects and want to use some deep object inside that tree a.b.c.d.e.f, it will give me exception if any node on that query path is missing.
if(a.b.c.d.e.f != null)
Console.Write("ok");
I want to avoid checking for null for each level like this:
if(a != null)
if(a.b != null)
if(a.b.c != null)
if(a.b.c.d != null)
if(a.b.c.d.e != null)
if(a.b.c.d.e.f != null)
Console.Write("ok");
First solution is to implement Get extension method that allows this:
if(a.Get(o=>o.b).Get(o=>o.c).Get(o=>o.d).Get(o=>o.e).Get(o=>o.f) != null)
Console.Write("ok");
Second solution is to implement Get(string) extension method and use reflection to get result looking like this:
if(a.Get("b.c.d.e.f") != null)
Console.Write("ok");
Third solution, could be to implement ExpandoObject and use dynamic type to get result looking like this:
dynamic da = new SafeExpando(a);
if(da.b.c.d.e.f != null)
Console.Write("ok");
But last 2 solutions do not give benefits of strong typing and IntelliSense.
I think the best could be fourth solution that can be implemented with Expression Trees:
if(Get(a.b.c.d.e.f) != null)
Console.Write("ok");
or
if(a.Get(a=>a.b.c.d.e.f) != null)
Console.Write("ok");
I already implemented 1st and 2nd solutions.
Here is how 1st solution looks like:
[DebuggerStepThrough]
public static To Get<From,To>(this From @this, Func<From,To> get)
{
var ret = default(To);
if(@this != null && !@this.Equals(default(From)))
ret = get(@this);
if(ret == null && typeof(To).IsArray)
ret = (To)Activator.CreateInstance(typeof(To), 0);
return ret;
}
How to implement 4th solution if possible ?
Also it would be interesting to see how to implement 3rd solution if possible.
So the starting place is creating an expression visitor. This lets us find all of the member accesses within a particular expression. This leaves us with the question of what to do for each member access.
So the first thing is to recursively visit on the expression that the member is being accessed on. From there, we can use
Expression.Condition
to create a conditional block that compares that processed underlying expression tonull
, and returnsnull
if true an the original starting expression if it's not.Note that we need to provide implementations for both Members and method calls, but the process for each is basically identical.
We'll also add in a check so that of the underlying expression is
null
(which is to say, there is no instance and it's a static member) or if it's a non-nullable type, that we just use the base behavior instead.We can then create an extension method to make calling it easier:
As well as one that accepts a lambda, rather than any expression, and can return a compiled delegate:
Note that, to support cases where the accessed member resolves to a non-nullable value, we're changing the type of those expressions to lift them to be nullable, using
MakeNullable
. This is a problem with this final expression, as it needs to be aFunc<T>
, and it won't match ifT
isn't also lifted. Thus, while it's very much non-ideal (ideally you'd never call this method with a non-nullableT
, but there's no good way to support this in C#) we coalesce the final value using the default value for that type, if necessary.(You can trivially modify this to accept a lambda accepting a parameter, and pass in a value, but you can just as easily close over that parameter instead, so I see no real reason to.)
It's also worth pointing out that in C# 6.0, when it's actually released, we'll have an actual null propogation operator (
?.
), making all of this very unnecessary. You'll be able to write:and have exactly the semantics you're looking for.