C# elegant way to check if a property's property is null

不打扰是莪最后的温柔 提交于 2019-11-26 15:14:21

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().

Sam

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

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);
    }
}

This code is "the least amount of code", but not the best practice:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}

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;

Check this blog post out. I think it's a very elegant method for chained null checks. There are many similar implementations of this, but I like this one because it stops evaluating as soon as a null is found in the chain.

All the source code is on github.

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);

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;

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);

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.

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;

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);

Null propagation planned in C# vNext, powered by Roslyn


Not an answer, but more of an update. It appears as though UserVoice has driven this, along with a raft of other new things, since Roslyn and is apparently a planned addition:

https://roslyn.codeplex.com/discussions/540883

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;
   }
}

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);
    }
}
BlackjacketMack

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));
Aridane Álamo
var result = nullableproperty ?? defaultvalue;

The ?? operator means if the first argument is null, return the second one instead.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!