While we cannot add custom operators to C#, we can use extension methods and expressions to simulate p -> p.Address?.ZipCode. To follow the naming convention in LINQ, I am going to create an extension method on object called ValueOrDefault. Since operators are not an option, expression trees are the primary option. Assuming we have a an object, our goal is to support target.ValueOrDefault( item => item.Foo.Bar().Baz ) evaluating the expression layer by layer until we finish or encounter a default(T) value. To evaluate the expression, we have to parse from the outside in, but evaluate from the inside out, and wrap every subexpression evaluation in the same safe call to ValueOrDefault. The recursion is rather fun:

In order to evaluate target.ValueOrDefault( item => item.Foo.Bar().Baz ) we need to

Determine if target is null, if so, return null

Pass target as the instance and item => item.Foo.Bar().Baz

Pull off the .Baz expression and evaluate item.Foo.Bar() with target as the instance

Pull off the .Bar() and evaluate item.Foo with target as the instance

Pull off the .Foo and evaluate item with target as the instance

There are no more member or method expressions, so target is passed back up the stack

.Foo is evaluated on the target instance. If Foo evaluates to default(T) we cascade back up the recursion tree and stop evaluating. If not, we proceed to #8 passing the value returned by .Foo as the target instance.

.Bar() is executed on the target instance. Same behavior as #7, pass the return value back up the stack

usingSystem;usingSystem.Linq.Expressions;usingSystem.Reflection;publicstaticclassObjectExtensions{publicstaticTValueValueOrDefault<TSource,TValue>(thisTSourceinstance,Expression<Func<TSource,TValue>>expression){returnValueOrDefault(instance,expression,true);}privatestaticTValueValueOrDefault<TSource,TValue>(thisTSourceinstance,Expression<Func<TSource,TValue>>expression,boolnested){returnReferenceEquals(instance,default(TSource))?default(TValue):nested?EvaluateExpression(instance,expression):expression.Compile()(instance);}internalstaticTPropertyEvaluateExpression<TSource,TProperty>(TSourcesource,Expression<Func<TSource,TProperty>>expression){varmethod=expression.BodyasMethodCallExpression;if(method!=null){returnValueOrDefault(source,expression,false);}varbody=expression.BodyasMemberExpression;if(body==null){conststringformat="Expression '{0}' must refer to a property.";stringmessage=string.Format(format,expression);thrownewArgumentException(message);}objectvalue=EvaluateMemberExpression(source,body);if(ReferenceEquals(value,null)){returndefault(TProperty);}return(TProperty)value;}privatestaticobjectEvaluateMemberExpression(objectinstance,MemberExpressionmemberExpression){if(memberExpression==null){returninstance;}instance=EvaluateMemberExpression(instance,memberExpression.ExpressionasMemberExpression);varpropertyInfo=memberExpression.MemberasPropertyInfo;instance=ValueOrDefault(instance,item=>propertyInfo.GetValue(item,null),false);returninstance;}}

The key thing to grok is the inside out evaluation. If you want to play around with this code, I have included a number of tests below to make sure that everything is in working order. Having ?. added to the C# language is high on my list of desired vNext features. Hopefully the C# team agrees.