C#6 Update
In C#6 ?. is now a language feature:
// C#1-5
propertyValue1 = myObject != null ? myObject.StringProperty : null;
// C#
Not an answer to the exact question asked, but there is Null-Conditional Operator in C# 6.0. I can argue it will be a poor choice to use the option in OP since C# 6.0 :)
So your expression is simpler,
string propertyValue = myObject?.StringProperty;
In case myObject
is null it returns null. In case the property is a value type you have to use equivalent nullable type, like,
int? propertyValue = myObject?.IntProperty;
Or otherwise you can coalesce with null coalescing operator to give a default value in case of null. For eg,
int propertyValue = myObject?.IntProperty ?? 0;
?.
is not the only syntax available. For indexed properties you can use ?[..]
. For eg,
string propertyValue = myObject?[index]; //returns null in case myObject is null
One surprising behaviour of the ?.
operator is that it can intelligently bypass subsequent .Member
calls if object happens to be null. One such example is given in the link:
var result = value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);
In this case result
is null if value
is null and value.Length
expression wouldn't result in NullReferenceException
.
To reader not in the know it looks like you're calling a method on a null reference. If you want this, I'd suggest putting it in a utility class rather than using an extension method:
propertyValue1 = Util.IfNotNull(myObject1, x => x.StringProperty );
propertyValue2 = Util.IfNotNull(myObject2, x => x.IntProperty, 0);
The "Util." grates, but is IMO the lesser syntactic evil.
Also, if you developing this as part of a team, then gently ask what others think and do. Consistency across a codebase for frequently used patterns is important.
If you find yourself having to check very often if a reference to an object is null, may be you should be using the Null Object Pattern. In this pattern, instead of using null to deal with the case where you don't have an object, you implement a new class with the same interface but with methods and properties that return adequate default values.
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.
While extension methods generally cause misunderstandings when called from null instances, I think the intent is pretty straightforward in this case.
string x = null;
int len = x.IfNotNull(y => y.Length, 0);
I would want to be sure this static method works on Value Types that can be null, such as int?
Edit: compiler says that neither of these are valid:
public void Test()
{
int? x = null;
int a = x.IfNotNull(z => z.Value + 1, 3);
int b = x.IfNotNull(z => z.Value + 1);
}
Other than that, go for it.
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());