Possible pitfalls of using this (extension method based) shorthand
Here's another solution, for chained members, including extension methods:
public static U PropagateNulls<T,U> ( this T obj
,Expression<Func<T,U>> expr)
{ if (obj==null) return default(U);
//uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2
var members = new Stack<MemberInfo>();
bool searchingForMembers = true;
Expression currentExpression = expr.Body;
while (searchingForMembers) switch (currentExpression.NodeType)
{ case ExpressionType.Parameter: searchingForMembers = false; break;
case ExpressionType.MemberAccess:
{ var ma= (MemberExpression) currentExpression;
members.Push(ma.Member);
currentExpression = ma.Expression;
} break;
case ExpressionType.Call:
{ var mc = (MethodCallExpression) currentExpression;
members.Push(mc.Method);
//only supports 1-arg static methods and 0-arg instance methods
if ( (mc.Method.IsStatic && mc.Arguments.Count == 1)
|| (mc.Arguments.Count == 0))
{ currentExpression = mc.Method.IsStatic ? mc.Arguments[0]
: mc.Object;
break;
}
throw new NotSupportedException(mc.Method+" is not supported");
}
default: throw new NotSupportedException
(currentExpression.GetType()+" not supported");
}
object currValue = obj;
while(members.Count > 0)
{ var m = members.Pop();
switch(m.MemberType)
{ case MemberTypes.Field:
currValue = ((FieldInfo) m).GetValue(currValue);
break;
case MemberTypes.Method:
var method = (MethodBase) m;
currValue = method.IsStatic
? method.Invoke(null,new[]{currValue})
: method.Invoke(currValue,null);
break;
case MemberTypes.Property:
var method = ((PropertyInfo) m).GetGetMethod(true);
currValue = method.Invoke(currValue,null);
break;
}
if (currValue==null) return default(U);
}
return (U) currValue;
}
Then you can do this where any can be null, or none:
foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method());
We independently came up with the exact same extension method name and implementation: Null-propagating extension method. So we don't think it's confusing or an abuse of extension methods.
I would write your "multiple levels" example with chaining as follows:
propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty);
There's a now-closed bug on Microsoft Connect that proposed "?." as a new C# operator that would perform this null propagation. Mads Torgersen (from the C# language team) briefly explained why they won't implement it.