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


97

在C#中,假设在此示例中要从PropertyC中提取一个值,并且ObjectA,PropertyA和PropertyB都可以为null。

ObjectA.PropertyA.PropertyB.PropertyC

如何以最少的代码安全地获取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,但是我将其更改为psuedo代码,因为正确地指出了它在js中不起作用。


我看不到您的js示例如何工作。每当ObjectAPropertyA为null 时,您都将收到“期望的对象”错误。
lincolnk

Answers:


117

在C#6中,可以使用Null条件运算符。因此原始测试将是:

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

2
你能解释一下这是什么吗?什么是value等于,如果PropertyC是空?还是PropertyB为空?如果Object A为空怎么办?
Kolob Canyon

1
如果这些属性中的ANY为null,则整个语句返回null。它从左到右开始。没有语法糖,这等效于一系列if语句,if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......最终的最后一个表达式为if(propertyZ != null) { value = propertyZ }
DetectivePikachu

27

简短扩展方法:

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


15

您可以在类中添加方法吗?如果没有,您是否考虑过使用扩展方法?您可以为您的对象类型创建一个扩展方法,称为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或更高版本。


11

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

可以使用Linq表达式使用类似此处所述的技巧:

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

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


10

重构以遵守Demeter定律


当您仅读取属性时,我不认为仅需要深入三个层次的对象图即可进行重构。我同意OP是否要在通过PropertyC引用的对象上调用方法,而不是在仅需要在读取之前检查null的属性时才调用该方法。在此示例中,它可以像Customer.Address.Country一样简单,其中Country可以是诸如KeyValuePair之类的引用类型。您将如何重构它以防止需要空引用检查?
达伦·刘易斯

OP示例实际上是4深。我的建议不是删除null ref检查,而是将它们放在最有可能正确处理它们的对象中。像大多数“经验法则”一样,也有例外,但我不认为这是一个例外。我们可以同意不同意吗?
rtalbot

3
我同意@rtalbot(尽管公平地说,@ Daz Lewis提出了一个4层深度的示例,因为最后一项是KeyValuePair)。如果某件事困扰着Customer对象,那么通过Address对象层次结构,我看不到它在从事什么业务。假设您后来决定,对于Country属性而言,KeyValuePair不是一个好主意。在这种情况下,每个人的代码都必须更改。那不是一个好的设计。
Jeffrey L Whitledge


8

您显然正在寻找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);
    }
}

为什么在这里使用扩展方法?没有被使用。
Mladen Mihajlovic 2014年

1
@MladenMihajlovic:语法SelectMany使用扩展方法from ... in ... from ... in ...
dtb 2014年

5

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

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

我是C#的忠实拥护者,但是新Java(1.7?)中的一个好东西就是。?。操作员:

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

1
真的是Java 1.7吗?它在C#中已经被要求很长时间了,但是我怀疑它是否会发生……
Thomas Levesque 2010年

不幸的是我没有空值。该Java语法看起来不错!我要对此投票,因为我想要那种语法!
乔恩·克拉格

3
托马斯:上一次我检查tech.puredanger.com/java7时,它暗示Java会得到它。但是现在当我重新检查时说:空安全处理:否。因此,我撤消了我的声明,并将其替换为新的声明:它是针对Java 1.7提出的,但没有提出。
另一个元编程人员,2010年

另一种方法是monad.net使用的方法
只是另一种元编程器

1
好像是?。运算符位于Visual Studio 2015中https://msdn.microsoft.com/zh-cn/library/dn986595.aspx
Edward

4

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

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

1
我已经看过很多这样的代码,而忽略了性能损失,最大的问题是它使调试变得复杂,因为真正的异常淹没了数百万个无用的null ref异常。
只是另一位元编程师,2013年

有时三年后读自己的回答很有趣。我认为,我今天的回答会有所不同。我会说该代码违反了Demeter的法律,我建议对其进行重构,这样就不会了。
鲍里斯·莫迪列夫斯基

1
到今天,在原始答案的7年之后,我将加入@Phillip Ngan,并使用具有以下语法的C#6:int?值= objectA?.PropertyA?.PropertyB?.PropertyC;
鲍里斯·莫迪列夫斯基

4

当我需要像这样链接调用时,我依赖于创建的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);

我认为这段代码行不通。defaultVal的类型是什么?var p = new Person(); Assert.AreEqual(p.TryGet(x => x.FirstName).TryGet(x => x.LastName).TryGet(x => x.NickName,“ foo”),“ foo”);
基思

我编写的示例应这样阅读:ObjectA.PropertyA.PropertyB.PropertyC。您的代码似乎正在尝试从“名字”中加载一个名为“姓氏”的属性,这不是预期的用法。一个更正确的例子是:var postcode = person.TryGet(p => p.Address).TryGet(p => p.Postcode); 顺便说一句,我的TryGet()帮助程序方法与C#6.0中的新功能-空条件运算符非常相似。其用法将如下所示:var postcode = person?.Address?.Postcode; msdn.microsoft.com/zh-CN/magazine/dn802602.aspx
Emanuel

3

我在新的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;

1

您可以这样做:

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

1

您可以使用以下扩展名,我认为它确实不错:

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

0

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

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

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

好吧,在那种情况下,显然PropertyB永远不能为null。
递归

0

刚刚偶然发现了这个职位。

前一段时间,我在Visual Studio Connect上提出了有关添加新???运算符的建议。

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;

0

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

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

1
该解决方案已经在其他答案中提供(重复)。完全没有理由再次发布它。
2013年

我没有看到任何使用默认值的文件。
2014年

我计算了其他6个使用定义的默认值的值。显然,您看起来并不那么难。
2014年

0

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

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

您提出的第一种方法是最好,最明确的意图。如果可以的话,您可以尝试重新设计而不必依赖太多的null。


-1

一旦克服了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));

只是好奇为什么有人会在我做出回应8年后就投票否决(这比C#6的空合并还早几年)。
BlackjacketMack

-3
var result = nullableproperty ?? defaultvalue;

??(空聚结操作者)的装置,如果第一个参数是null,返回第二个来代替。


2
这个答案不能解决OP的问题。您将如何应用解决方案?当表达式的所有部分(ObjectA,PropertyA和PropertyB)可以为null时,运算符是否为ObjectA.PropertyA.PropertyB?
Artemix 2013年

没错,我想我什至没有读过这个问题。无论如何,不​​可能的事就是什么都不做:P static void Main(string [] args){a ca = new a(); var default_value = new a(){b = new object()}; var value =(ca ?? default_value).b ?? default_value.b; }类a {公共对象b = null; }
AridaneÁlamo13年

(对象A ?? DefaultMockedAtNull).PropertyA = NULL ObjectA.PropertyA.PropertyB:?空
Aridane阿拉莫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.