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); } }
更新2014年:C#6有一个新的运营商?.
各种所谓的“安全导航”或“空传播”
parent?.child
此代码是“最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议???
运营商。
这需要来自框架团队的一些工作,但不需要改变语言,只是做一些编译器魔术。 这个想法是,编译器应该改变这个代码(语法不允许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显然是一个计划的增加:
我将使用与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;
?? 运算符表示如果第一个参数为空,则返回第二个参数。