C#优雅的方式来检查属性的属性是否为空

在C#中,假设你想在这个例子中从PropertyC中取出一个值,并且ObjectA,PropertyA和PropertyB都可以为null。

ObjectA.PropertyA.PropertyB.PropertyC

如何以最less的代码安全地获取PropertyC?

现在我会检查:

if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null) { // safely pull off the value int value = objectA.PropertyA.PropertyB.PropertyC; } 

这样做更好(伪代码)是很好的。

 int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal; 

可能甚至进一步崩溃与空合并运算符。

编辑本来我说我的第二个例子就像js,但我把它改为伪代码,因为它正确地指出,它不会在js中工作。

在C#6中,您可以使用空条件运算符。 所以原来的testing将是:

  int? value = objectA?.PropertyA?.PropertyB?.PropertyC; 

短延伸方法:

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

运用

 PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC); 

这个简单的扩展方法和更多你可以findhttp://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

编辑:

在使用它之后,我认为这个方法的名字应该是IfNotNull()而不是原来的With()。

你可以添加一个方法到你的class级? 如果没有,你有没有想过使用扩展方法? 您可以为您的对象types创build一个名为GetPropC()的扩展方法。

例:

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

用法:

 int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue) 

顺便说一句,这假定你正在使用.NET 3或更高版本。

你这样做的方式是正确的。

可以使用像这里描述的一个技巧,使用Linqexpression式:

 int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0); 

但手动检查每个属性的速度要慢得多…

重构观察得墨忒耳定律

你显然在寻找Nullable Monad

 string result = new A().PropertyB.PropertyC.Value; 

 string result = from a in new A() from b in a.PropertyB from c in b.PropertyC select c.Value; 

如果任何可为空的属性为null,则返回null; 否则, Value

 class A { public B PropertyB { get; set; } } class B { public C PropertyC { get; set; } } class C { public string Value { get; set; } } 

LINQ扩展方法:

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

此代码是“最less的代码”,但不是最佳实践:

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

假设你有types的空值,一种方法是这样的:

 var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString; 

我是C#的粉丝,但在新的Java(1.7?)是一个非常好的东西是。 运营商:

  var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString; 

检查这篇博客文章 。 我认为这是一个链式空检查非常优雅的方法。 这里有很多类似的实现,但是我喜欢这个,因为只要在链中findnull就会停止计算。

所有的源代码都在github上 。

当我需要这样的连锁调用时,我依靠一个我创build的帮助器方法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); } 

在你的情况下,你会像这样使用它:

  int value = ObjectA .TryGet(p => p.PropertyA) .TryGet(p => p.PropertyB) .TryGet(p => p.PropertyC, defaultVal); 

我在新的C#6.0中看到了一些东西,这是通过使用'?' 而不是检查

例如,而不是使用

 if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null) { var city = person.contact.address.city; } 

你只需使用

 var city = person?.contact?.address?.city; 

我希望这有助于某人。


更新:

你现在可以这样做

  var city = (Person != null)? ((Person.Contact!=null)? ((Person.Contact.Address!= null)? ((Person.Contact.Address.City!=null)? Person.Contact.Address.City : null ) :null) :null) : null; 

你可以这样做:

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

或者更好的可能是这样的:

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

这不可能。 ObjectA.PropertyA.PropertyB将失败,如果ObjectA为空,由于null解引用,这是一个错误。

如果(ObjectA!= null && ObjectA.PropertyA …由于短路而工作,即,如果ObjectA为null,将永远不会检查ObjectA.PropertyA。

你提出的第一种方法是最好的,最明确的意图。 如果有什么你可以尝试重新devise,而不必依赖这么多的空值。

刚刚偶然发现这个post。

前一段时间,我提出了关于添加一个新的Visual Studio连接的build议??? 运营商。

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera

这需要来自框架团队的一些工作,但不需要改变语言,只是做一些编译器魔术。 这个想法是,编译器应该改变这个代码(语法不允许atm)

 string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined"; 

进入这个代码

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

对于null检查,这可能看起来像

 bool isNull = (Order.OrderDetails[0].Product ??? null) == null; 

你可以使用下面的扩展名,我认为它是非常好的:

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

它是这样使用的:

 int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC); 

在C#vNext中计划的空传播,由Roslyn提供支持


不是一个答案,而是一个更新。 看起来好像UserVoice已经推动了这一切,以及其他一些新事物,因为Roslyn显然是一个计划的增加:

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

我将使用与Nullabletypes相似的模式在PropertyAtypes(或扩展方法,如果它不是您的types)中编写自己的方法。

 class PropertyAType { public PropertyBType PropertyB {get; set; } public PropertyBType GetPropertyBOrDefault() { return PropertyB != null ? PropertyB : defaultValue; } } 

一旦你完成了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); } } 

用法可能如下所示:

 ObjectA objectA = null; Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID)); Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB)); 

我写了一个接受默认值的方法,下面是如何使用它:

 var teacher = new Teacher(); return teacher.GetProperty(t => t.Name); return teacher.GetProperty(t => t.Name, "Default name"); 

这里是代码:

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

?? 运算符表示如果第一个参数为空,则返回第二个参数。