2011-11-25 113 views
4

的属性时,一个方便的语法返回null,而不是例外考虑一个简单的C#示例:是否有访问一个空对象

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

显然,这将引发一个NullReferenceException因为MailingAddress proptery为空。

我可以重写第二行:

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

有没有更简单的说表达这样?

+1

[Demeter法](http://en.wikipedia.org/wiki/Law_of_Demeter) - 您不应该通过Person对象访问MailingAddress的Street属性。 –

回答

3

这没什么好的语法。 coalesce运算符是其中的一部分,但您需要处理遍历 null,而不仅仅是替换null。有一两件事你可以做的是有一个静态的“空对象”的类,类似:

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

那么你可以使用聚结运营商,像这样:

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

如果你想去扩展方法途径,你可以做这样的事情:

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

然后,你可以这样做:

(person.MailingAddress.OrNew().Street) 
+0

您可以在Address对象的定义中声明并实例化一个Address对象吗?这不会导致StackOverflowExcpetion? – Otiel

+1

不,因为它是静态的 –

+0

哦,是的,没错。做得好。 – Otiel

4

此代码在技术上违反了Law of Demeter,所以有些人会认为这是不好的形式写在第一位。

所以不,没有本地语法来实现你想要的,但是将这些代码移动到你的Person类中的属性会使这个调用代码更加干净,并且使你符合demeter的规律。

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

+ 1,得墨忒耳法则太不受重视和忽视。 – Lukazoid

+0

@Adam,虽然我经常面临不得不使用我不拥有的模型,并且没有提供如您所建议的替代方案,但这足够了。 –

+0

够公平的。你有权访问它们至少被声明的程序集吗?如果是这样,你可以通过部分课程来实现Xoltar的答案。 –

0

您可以使用此扩展方法:

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

例子:

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

可以使用设备基于表达式树,所以你会写

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; 
*/ 

免责声明:我没有写这段代码,我只是不知道我最初是在哪里找到它的。任何人都认可这个帮手吗?

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