C# elegant way to check if a property's property is null
In C#, s开发者_Python百科ay that you want to pull a value off of PropertyC in this example and ObjectA, PropertyA and PropertyB can all be null.
ObjectA.PropertyA.PropertyB.PropertyC
How can I get PropertyC safely with the least amount of code?
Right now I would check:
if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null)
{
// safely pull off the value
int value = objectA.PropertyA.PropertyB.PropertyC;
}
It would be nice to do something more like this (pseudo-code).
int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal;
Possibly even further collapsed with a null-coalescing operator.
EDIT Originally I said my second example was like js, but I changed it to psuedo-code since it was correctly pointed out that it would not work in js.
In C# 6 you can use the Null Conditional Operator. So the original test will be:
int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
Short Extension Method:
public static TResult IfNotNull<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
where TResult : class where TInput : class
{
if (o == null) return null;
return evaluator(o);
}
Using
PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC);
This simple extension method and much more you can find on http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/
EDIT:
After using it for moment I think the proper name for this method should be IfNotNull() instead of original With().
Can you add a method to your class? If not, have you thought about using extension methods? You could create an extension method for your object type called GetPropC()
.
Example:
public static class MyExtensions
{
public static int GetPropC(this MyObjectType obj, int defaltValue)
{
if (obj != null && obj.PropertyA != null & obj.PropertyA.PropertyB != null)
return obj.PropertyA.PropertyB.PropertyC;
return defaltValue;
}
}
Usage:
int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue)
By the way, this assumes you are using .NET 3 or higher.
The way you're doing it is correct.
You could use a trick like the one described here, using Linq expressions :
int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0);
But it's much slower that manually checking each property...
Refactor to observe the Law of Demeter
Update 2014: C# 6 has a new operator ?.
various called 'safe navigation' or 'null propagating'
parent?.child
Read http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx for details
This has long been a hugely popular request https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d
You're obviously looking for the Nullable Monad:
string result = new A().PropertyB.PropertyC.Value;
becomes
string result = from a in new A()
from b in a.PropertyB
from c in b.PropertyC
select c.Value;
This returns null
, if any of the nullable properties are null; otherwise, the value of Value
.
class A { public B PropertyB { get; set; } }
class B { public C PropertyC { get; set; } }
class C { public string Value { get; set; } }
LINQ extension methods:
public static class NullableExtensions
{
public static TResult SelectMany<TOuter, TInner, TResult>(
this TOuter source,
Func<TOuter, TInner> innerSelector,
Func<TOuter, TInner, TResult> resultSelector)
where TOuter : class
where TInner : class
where TResult : class
{
if (source == null) return null;
TInner inner = innerSelector(source);
if (inner == null) return null;
return resultSelector(source, inner);
}
}
Assuming you have empty values of types one approach would be this:
var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;
I'm a big fan of C# but a very nice thing in new Java (1.7?) is the .? operator:
var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;
I saw something in the new C# 6.0, this is by using '?' instead of checking
for example instead of using
if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{
var city = person.contact.address.city;
}
you simply use
var city = person?.contact?.address?.city;
I hope it helped somebody.
UPDATE:
You could do like this now
var city = (Person != null)?
((Person.Contact!=null)?
((Person.Contact.Address!= null)?
((Person.Contact.Address.City!=null)?
Person.Contact.Address.City : null )
:null)
:null)
: null;
This code is "the least amount of code", but not the best practice:
try
{
return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
return null;
}
When I need to chain calls like that, I rely on a helper method I created, TryGet():
public static U TryGet<T, U>(this T obj, Func<T, U> func)
{
return obj.TryGet(func, default(U));
}
public static U TryGet<T, U>(this T obj, Func<T, U> func, U whenNull)
{
return obj == null ? whenNull : func(obj);
}
In your case, you would use it like so:
int value = ObjectA
.TryGet(p => p.PropertyA)
.TryGet(p => p.PropertyB)
.TryGet(p => p.PropertyC, defaultVal);
You could do this:
class ObjectAType
{
public int PropertyC
{
get
{
if (PropertyA == null)
return 0;
if (PropertyA.PropertyB == null)
return 0;
return PropertyA.PropertyB.PropertyC;
}
}
}
if (ObjectA != null)
{
int value = ObjectA.PropertyC;
...
}
Or even better might be this:
private static int GetPropertyC(ObjectAType objectA)
{
if (objectA == null)
return 0;
if (objectA.PropertyA == null)
return 0;
if (objectA.PropertyA.PropertyB == null)
return 0;
return objectA.PropertyA.PropertyB.PropertyC;
}
int value = GetPropertyC(ObjectA);
This approach is fairly straight-forward once you get over the lambda gobbly-gook:
public static TProperty GetPropertyOrDefault<TObject, TProperty>(this TObject model, Func<TObject, TProperty> valueFunc)
where TObject : class
{
try
{
return valueFunc.Invoke(model);
}
catch (NullReferenceException nex)
{
return default(TProperty);
}
}
With usage that might look like:
ObjectA objectA = null;
Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID));
Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));
I would write your own method in the type of PropertyA (or an extension method if it's not your type) using the similar pattern to the Nullable type.
class PropertyAType
{
public PropertyBType PropertyB {get; set; }
public PropertyBType GetPropertyBOrDefault()
{
return PropertyB != null ? PropertyB : defaultValue;
}
}
you can use the following extension and I think it is really good:
/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<TF, TR>(TF t, Func<TF, TR> f)
where TF : class
{
return t != null ? f(t) : default(TR);
}
/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
where T1 : class
where T2 : class
{
return Get(Get(p1, p2), p3);
}
/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
where T1 : class
where T2 : class
where T3 : class
{
return Get(Get(Get(p1, p2), p3), p4);
}
And it is used like this:
int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);
Just stumbled accross this post.
Some time ago I made a suggestion on Visual Studio Connect about adding a new ???
operator.
http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera
This would require some work from the framework team but don't need to alter the language but just do some compiler magic. The idea was that the compiler should change this code (syntax not allowed atm)
string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined";
into this code
Func<string> _get_default = () => "no product defined";
string product_name = Order == null
? _get_default.Invoke()
: Order.OrderDetails[0] == null
? _get_default.Invoke()
: Order.OrderDetails[0].Product == null
? _get_default.Invoke()
: Order.OrderDetails[0].Product.Name ?? _get_default.Invoke()
For null check this could look like
bool isNull = (Order.OrderDetails[0].Product ??? null) == null;
I wrote a method that accepts a default value, here is how to use it:
var teacher = new Teacher();
return teacher.GetProperty(t => t.Name);
return teacher.GetProperty(t => t.Name, "Default name");
Here is the code:
public static class Helper
{
/// <summary>
/// Gets a property if the object is not null.
/// var teacher = new Teacher();
/// return teacher.GetProperty(t => t.Name);
/// return teacher.GetProperty(t => t.Name, "Default name");
/// </summary>
public static TSecond GetProperty<TFirst, TSecond>(this TFirst item1,
Func<TFirst, TSecond> getItem2, TSecond defaultValue = default(TSecond))
{
if (item1 == null)
{
return defaultValue;
}
return getItem2(item1);
}
}
It is not possible.
ObjectA.PropertyA.PropertyB
will fail if ObjectA
is null due to null dereferencing, which is an error.
if(ObjectA != null && ObjectA.PropertyA
... works due to short circuiting, ie ObjectA.PropertyA
will never be checked if ObjectA
is null
.
The first way you propose is the best and most clear with intent. If anything you could try to redesign without having to rely on so many nulls.
var result = nullableproperty ?? defaultvalue;
The ??
(null-coalescing operator) means if the first argument is null
, return the second one instead.
精彩评论