C # 检查属性属性是否为 null 的优雅方法

在 C # 中,假设在这个示例中您想从 PropertyC 中取出一个值,那么 ObjectA、 PropertyA 和 PropertyB 都可以是 null。

PropertyA.PropertyB.PropertyC

How can I get PropertyC safely with the least amount of code?

现在我要检查一下:

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;

甚至可能使用空聚合运算符进一步崩溃。

EDIT Originally I said my second example was like js, but I changed it to psuedo-code since it was correctly pointed out that it would not work in js.

156502 次浏览

我会使用与 Nullable 类型相似的模式,用 PropertyA 类型(或者扩展方法,如果它不是您的类型)编写您自己的方法。

class PropertyAType
{
public PropertyBType PropertyB {get; set; }


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

你的做法是正确的。

You 可以 use a trick like the one described 给你, using Linq expressions :

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

但手动检查每个属性要慢得多。

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

工程由于短路,即 ObjectA.PropertyA将永远不会检查,如果 ObjectAnull

你提出的第一个方法是最好的,最清楚的意图。如果有什么你可以尝试重新设计,而不必依赖于这么多的空值。

这个代码是“最少量的代码”,但不是最佳实践:

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

可以向类中添加方法吗?如果没有,您是否考虑过使用扩展方法?您可以为对象类型创建一个名为 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;
}
}

用法:

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

顺便说一下,这里假设您使用的是.NET3或更高版本。

你可以这样做:

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

重构以观察 得墨忒耳定律

假设类型的值为空,一种方法是:

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

我是 C # 的忠实粉丝,但是在新 Java (1.7?)中一个非常好的东西是. ? 操作符:

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

你显然是在找 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);
}
}

这种方法是相当直接的,一旦你得到了超过 lambda 吞吞吐吐:

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

??(空结合运算符)意味着如果第一个参数是 null,则返回第二个参数。

当我需要这样的链式调用时,我依赖于我创建的一个 helper 方法 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);

Just stumbled accross this post.

不久前,我在 VisualStudioConnect 上提出了一个关于添加一个新的 ???操作符的建议。

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

短期延伸法:

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

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

编辑:

在使用了一会儿之后,我认为这个方法的正确名称应该是 IfNotNull (),而不是原来的 With ()。

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

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

In C# 6 you can use the 没有条件运算符. So the original test will be:

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

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

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

你只需要

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

I hope it helped somebody.


更新:

你现在就可以这么做

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