简体   繁体   中英

Is there a handy syntax to return null rather than exception when accessing a property of a null object

Consider this simple c# example:

var person = new Person {Name = "Fred", MailingAddress=null };
var result = String.Format("{0} lives at {1}",person.Name, person.MailingAddress.Street);

clearly this will throw a NullReferenceException because the MailingAddress proptery is null.

I could rewrite the second line as:

var result = String.Format("{0} lives at {1}", person.Name, person.MailingAddress == null ? (String)null : person.MailingAddress.Street);

Is there a simpler way to say express this?

This code is technically a violation of the Law of Demeter , so some would consider it bad form to write this in the first place.

So no, there's no native syntax to accomplish what you want, but moving this code to a property in your Person class would make this calling code more clean, and also bring you in line with the law of demeter.

public string StreetAddress{
   get { return this.MailingAddress == null ? 
                   (String)null : person.MailingAddress.Street; }
}

There's not really any good syntax for this. The coalesce operator is part of it, but you need to handle traversing through a null, not just replacing a null. One thing you could do would be to have a static "null object" for the class, something like:

public class Address 
{
  public static Address Null = new Address();
  // Rest of the class goes here
}

Then you could use the coalesce operator like so:

(person.MailingAddress ?? Address.Null).Street

If you want to go the extension method route, you could do something like this:

public static class NullExtension
{
    public static T OrNew<T>(this T thing)
        where T: class, new()
    {
        return thing ?? new T();
    }
}

Then you could do:

(person.MailingAddress.OrNew().Street)

You could use a device based on expression trees, so you'd write

var n = person.NullPropagate(p => p.Contact.MailingAddress.StreetAddress.Number);

/* having the effect of:

   (person == null)
   ? defaultValue
   : (person.Contact == null)
     ? defaultValue
     : (person.Contact.MailingAddress == null)
       ? defaultValue
       : (person.Contact.MailingAddress.StreetAddress == null)
         ? defaultValue
         : person.Contact.MailingAddress.StreetAddress.Number;
*/

Disclaimer : I haven't written this code, I just don't know where I originally found it either. Anyone recognize this helper?

public static R NullPropagate<T, R>(this T source, Expression<Func<T, R>> expression, R defaultValue)
{
    var safeExp = Expression.Lambda<Func<T, R>>(
        WrapNullSafe(expression.Body, Expression.Constant(defaultValue)),
        expression.Parameters[0]);

    var safeDelegate = safeExp.Compile();
    return safeDelegate(source);
}

private static Expression WrapNullSafe(Expression expr, Expression defaultValue)
{
    Expression obj;
    Expression safe = expr;

    while (!IsNullSafe(expr, out obj))
    {
        var isNull = Expression.Equal(obj, Expression.Constant(null));

        safe = Expression.Condition (isNull, defaultValue, safe);

        expr = obj;
    }
    return safe;
}

private static bool IsNullSafe(Expression expr, out Expression nullableObject)
{
    nullableObject = null;

    if (expr is MemberExpression || expr is MethodCallExpression)
    {
        Expression obj;
        MemberExpression memberExpr = expr as MemberExpression;
        MethodCallExpression callExpr = expr as MethodCallExpression;

        if (memberExpr != null)
        {
            // Static fields don't require an instance
            FieldInfo field = memberExpr.Member as FieldInfo;
            if (field != null && field.IsStatic)
                return true;

            // Static properties don't require an instance
            PropertyInfo property = memberExpr.Member as PropertyInfo;
            if (property != null)
            {
                MethodInfo getter = property.GetGetMethod();
                if (getter != null && getter.IsStatic)
                    return true;
            }
            obj = memberExpr.Expression;
        }
        else
        {
            // Static methods don't require an instance
            if (callExpr.Method.IsStatic)
                return true;

            obj = callExpr.Object;
        }

        // Value types can't be null
        if (obj.Type.IsValueType)
            return true;

        // Instance member access or instance method call is not safe
        nullableObject = obj;
        return false;
    }
    return true;
}

You can use this Extension method:

public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator, TResult failureValue)
            where TInput : class
        {
            return (value != null) ? evaluator(value) : failureValue;
        }

Example:

person.MailingAddress.MayBe(p=>p.Street,default(Street))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM