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

C# elegant way to check if a property's property is null

提问人:Jon Kragh 提问时间:8/12/2010 最后编辑:EvilDrJon Kragh 更新时间:3/3/2023 访问量:175920

问:

在 C# 中,假设您要在此示例中提取一个值 和 ,并且都可以为 null。PropertyCObjectAPropertyAPropertyB

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;

甚至可能因 null 合并运算符而进一步崩溃。

编辑最初我说我的第二个例子就像 js,但我把它改成了 psuedo-code,因为它被正确地指出它在 js 中不起作用。

C# nullreferenceException 可以为 null 的 null 条件运算符

评论


答:

1赞 Steve Danner 8/12/2010 #1

我会使用与 Nullable 类型类似的模式,在 PropertyA 类型(如果不是您的类型,则为扩展方法)中编写您自己的方法。

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

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

评论

0赞 recursive 8/12/2010
好吧,在这种情况下,显然 PropertyB 永远不能为 null。
12赞 Thomas Levesque 8/12/2010 #2

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

您可以使用此处描述的技巧,使用 Linq 表达式:

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

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

0赞 DanDan 8/12/2010 #3

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

if(ObjectA != null && ObjectA.PropertyA...由于短路而工作,即永远不会检查是否是。ObjectA.PropertyAObjectAnull

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

4赞 Boris Modylevsky 8/12/2010 #4

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

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

评论

1赞 Just another metaprogrammer 10/16/2013
我经常看到这样的代码,不考虑性能损失,最大的问题是它使调试复杂化,因为真正的异常淹没在数百万个无用的 null ref 异常中。
0赞 Boris Modylevsky 10/17/2013
有时在 3 年后阅读我自己的答案很有趣。我想,我今天会有不同的回答。我会说该代码违反了德墨忒尔定律,我建议重构它,这样它就不会了。
1赞 Boris Modylevsky 9/12/2017
从今天起,在最初答案的 7 年后,我将加入 @Phillip Ngan 并使用 C# 6 和以下语法:int?值 = objectA?。PropertyA?.属性B?。属性C;
16赞 Sam 8/12/2010 #5

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

2赞 Jeffrey L Whitledge 8/12/2010 #6

你可以这样做:

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);
11赞 rtalbot 8/12/2010 #7

重构以遵守得墨忒耳定律

评论

1赞 Darren Lewis 8/12/2010
我不认为只有三级深度的对象图在您只读取属性时需要重构。如果 OP 想要对通过 PropertyC 引用的对象调用方法,我会同意,但当它是在读取之前只需要检查 null 的属性时,我不同意。在此示例中,它可以像 Customer.Address.Country 一样简单,其中 Country 可以是引用类型,例如 KeyValuePair。您将如何重构它以防止需要 null ref 检查?
0赞 rtalbot 8/12/2010
OP 示例实际上是 4 深度。我的建议不是删除空引用检查,而是将它们放置在最有可能正确处理它们的对象中。像大多数“经验法则”一样,也有例外,但我不相信这是其中之一。我们可以同意不同意吗?
4赞 Jeffrey L Whitledge 8/12/2010
我同意@rtalbot(不过,公平地说@Daz刘易斯提出了一个 4 深度的例子,因为最后一项是 KeyValuePair)。如果某些东西扰乱了 Customer 对象,那么我看不到它通过 Address 对象层次结构查看的业务。假设您后来决定 KeyValuePair 对于 Country 属性来说不是一个好主意。在这种情况下,每个人的代码都必须更改。这不是一个好的设计。
6赞 Just another metaprogrammer 8/12/2010 #8

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

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

我是 C# 的忠实粉丝,但在新 Java (1.7?) 中,一个非常好的事情是 .?算子:

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

评论

1赞 Thomas Levesque 8/12/2010
它真的会在 Java 1.7 中吗?在 C# 中已经要求很长时间了,但我怀疑它永远不会发生......
0赞 Jon Kragh 8/12/2010
不幸的是,我没有空值。不过,Java 语法看起来很贴心!我要投赞成票,只是因为我想要那个语法!
3赞 Just another metaprogrammer 8/13/2010
Thomas:上次我检查 tech.puredanger.com/java7 它暗示 Java 会得到它。但是现在当我重新检查它时,它说:空安全处理:否。因此,我撤销了我的声明,并用一个新的声明取而代之:它是为 Java 1.7 提出的,但没有实现。
0赞 Just another metaprogrammer 10/16/2013
另一种方法是 monad.net
1赞 Edward 12/18/2015
似乎是?。运算符位于 Visual Studio 2015 中 https://msdn.microsoft.com/en-us/library/dn986595.aspx
8赞 dtb 8/12/2010 #9

您显然在寻找可为 Nullable 的单子

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,则返回 ;否则,的值为 。nullValue

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

评论

0赞 Mladen Mihajlovic 2/4/2014
为什么扩展方法在这里?它没有被使用。
1赞 dtb 2/4/2014
@MladenMihajlovic:扩展方法由语法使用。SelectManyfrom ... in ... from ... in ...
1赞 BlackjacketMack 9/22/2012 #10

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

评论

0赞 BlackjacketMack 6/10/2020
只是好奇为什么有人会在我提供回复 8 年后投反对票(这是在 C# 6 的 null 合并成为现实之前的几年)。
0赞 Dai 5/27/2023
我投了反对票,因为在 C# 中不应该对控制流使用 try/catch 或异常:在 .NET 中,异常应该是例外的:如果你期望发生不好的事情,那么它不应该由异常表示(即预期的事情不是异常) - 首先检查空引用非常便宜,而 .NET 中的异常则很昂贵。
0赞 BlackjacketMack 5/27/2023
对于已经用 null 合并运算符 (.?) 解决的问题来说,这可能有点迂腐。无论如何,用法仍然符合你的措辞,我会为它辩护。我正在评估一个泛型委托,可能期望得到一个值,但可能存在值为 null 的异常。捕获特定类型的异常是“预期”可能发生不好的事情的本质,这就是存在特定异常类型的原因。让我们都使用 null 合并并提高工作效率。我很欣赏这种话语。
-3赞 Aridane Álamo 5/10/2013 #11
var result = nullableproperty ?? defaultvalue;

(null-coalescing 运算符)表示,如果第一个参数是 ,则返回第二个参数。??null

评论

2赞 Artemix 5/10/2013
这个答案并不能解决OP的问题。您将如何应用解决方案?当表达式的所有部分(ObjectA、PropertyA 和 PropertyB)都可以为 null 时,运算符更改为 ObjectA.PropertyA.PropertyB?
0赞 Aridane Álamo 10/22/2013
没错,我想我什至根本没有读过这个问题。无论如何,不可能什么都不是,只是不要去做:Pstatic 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; } class a { public object b = null; }
0赞 Aridane Álamo 6/10/2014
(对象A ??DefaultMockedAtNull)。PropertyA != null?ObjectA.PropertyA.PropertyB: 空
4赞 Emanuel 11/9/2013 #12

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

评论

0赞 Keith 3/5/2015
我不认为这段代码有效。defaultVal 的类型是什么?var p = 新 Person();Assert.AreEqual( p.TryGet(x => x.FirstName) 。TryGet(x => x.LastName) 。TryGet(x => x.NickName, “foo”), “foo”);
0赞 Emanuel 3/12/2015
我写的示例应该这样读:ObjectA.PropertyA.PropertyB.PropertyC。您的代码似乎正在尝试从“FirstName”加载名为“LastName”的属性,这不是预期的用法。一个更正确的例子是这样的:var postcode = person。TryGet(p => p.Address)。TryGet(p => p.邮政编码);顺便说一句,我的 TryGet() 帮助程序方法与 C# 6.0 中的新功能非常相似 - null 条件运算符。它的用法是这样的:var postcode = person?。地址?。邮政编码;msdn.microsoft.com/en-us/magazine/dn802602.aspx
0赞 Jürgen Steinblock 12/9/2013 #13

只是偶然发现了这篇文章。

前段时间,我在 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;
1赞 Tony 12/10/2013 #14

您可以使用以下扩展,我认为它真的很好:

/// <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);
28赞 Krzysztof Morcinek 4/2/2014 #15

短扩展方法:

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

11赞 Colonel Panic 5/20/2014 #16

2014 年更新:C# 6 有一个新运算符,称为“安全导航”或“空传播”?.

parent?.child

有关详细信息,请阅读 http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx

长期以来,这是一个非常受欢迎的请求 https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d

0赞 Akira Yamamoto 10/8/2014 #17

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

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赞 Servy 10/8/2014
该解决方案已在其他答案中提供(重复)。根本没有理由再次发布它。
0赞 Akira Yamamoto 10/8/2014
我没有看到任何接受默认值的内容。
0赞 Servy 10/8/2014
我数了 6 个使用定义默认值的其他 6 个。显然你看起来并没有那么努力。
164赞 Phillip Ngan 11/7/2014 #18

在 C# 6 中,可以使用 Null 条件运算符。所以最初的测试将是:

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

评论

5赞 Kellen Stuart 5/18/2019
你能解释一下这是做什么的吗?等于 if 为 null?或者如果为 null?如果为 null 怎么办?valuePropertyCPropertyBObject A
6赞 DetectivePikachu 7/29/2019
如果这些属性中的任何一个为 null,则整个语句返回为 .它从左到右开始。如果没有语法糖,这相当于一系列 if 语句,其中最终的最后一个表达式是nullif(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......if(propertyZ != null) { value = propertyZ }
0赞 ToolmakerSteve 8/6/2021
@DetectivePikachu - 或者更简单地说,.objectA == null || objectA.PropertyA == null || objectA.PropertyA.PropertyB == null ? null : objectA.PropertyA.PropertyB.PropertyC
5赞 iYazee6 6/28/2015 #19

我在新的 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;
0赞 PRAMODH 1/12/2023 #20

教师 teacher = 新教师();

teacher.name = teacher.name == null ?默认值:teacher.name;

评论

2赞 Yunnosch 1/12/2023
欢迎来到 Stack Overflow!虽然这段代码可能会解决这个问题,但包括解释它如何以及为什么解决这个问题将真正有助于提高你的帖子的质量,并可能导致更多的赞成票。请记住,您是在为将来的读者回答问题,而不仅仅是现在提问的人。请编辑您的答案以添加解释,并指出适用的限制和假设。