【问题标题】:C# elegant way to check if a property's property is nullC# 优雅的方法来检查属性的属性是否为空
【发布时间】:2011-03-28 22:48:03
【问题描述】:

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

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,但我把它改成了伪代码,因为它被正确地指出它在 js 中不起作用。

【问题讨论】:

    标签: c# nullreferenceexception nullable null-conditional-operator


    【解决方案1】:

    在 C# 6 中,您可以使用 Null Conditional Operator。所以原来的测试是:

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

    【讨论】:

    • 你能解释一下这是做什么的吗?如果PropertyC 为空,value 等于什么?或者如果PropertyB 为空?如果Object A 为空怎么办?
    • 如果这些属性中的任何一个为空,则整个语句返回为null。它从左到右开始。如果没有语法糖,这相当于一系列 if 语句,其中 if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if...... 最终的最后一个表达式是 if(propertyZ != null) { value = propertyZ }
    • @DetectivePikachu - 或者更简单地说,objectA == null || objectA.PropertyA == null || objectA.PropertyA.PropertyB == null ? null : objectA.PropertyA.PropertyB.PropertyC
    【解决方案2】:

    短扩展方法:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        你的做法是正确的。

        可以使用类似于here 描述的技巧,使用 Linq 表达式:

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

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

        【讨论】:

          【解决方案5】:

          重构观察Law of Demeter

          【讨论】:

          • 当您只读取属性时,我不认为只有三层深度的对象图需要重构。我同意如果 OP 想要调用通过 PropertyC 引用的对象的方法,但不是当它是一个只需要在读取之前检查 null 的属性时。在此示例中,它可以像 Customer.Address.Country 一样简单,其中 Country 可以是引用类型,例如 KeyValuePair。您将如何重构它以防止需要进行空 ref 检查?
          • OP 示例实际上是 4 深。我的建议不是删除空引用检查,而是将它们定位在最有可能能够正确处理它们的对象中。像大多数“经验法则”一样,也有例外,但我不相信这是一个例外。我们可以同意不同意吗?
          • 我同意@rtalbot(不过,公平地说,@Daz Lewis 提出了一个 4-deep 示例,因为最后一项是 KeyValuePair)。如果某事弄乱了客户对象,那么我看不到它通过地址对象层次结构查看的业务。假设您后来决定 KeyValuePair 对于 Country 属性来说并不是一个好主意。在这种情况下,每个人的代码都必须更改。这不是一个好的设计。
          【解决方案6】:

          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

          【讨论】:

            【解决方案7】:

            您显然在寻找 可空单子

            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;否则,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);
                }
            }
            

            【讨论】:

            • 这里为什么是扩展方法?它没有被使用。
            • @MladenMihajlovic:SelectMany 扩展方法由 from ... in ... from ... in ... 语法使用。
            【解决方案8】:

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

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

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

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

            【讨论】:

            • 真的会出现在 Java 1.7 中吗?在 C# 中已经请求了很长时间,但我怀疑它会不会发生......
            • 不幸的是我没有空值。不过,Java 语法看起来很可爱!我要对此投赞成票,只是因为我想要那种语法!
            • Thomas:上次我检查了tech.puredanger.com/java7,它暗示 Java 会得到它。但是现在当我重新检查它时说:空安全处理:否。所以我撤销了我的声明并用一个新的替换它:它是为 Java 1.7 提出的,但没有成功。
            • 另一种方法是 monad.net 使用的方法
            • 看起来像?。运算符在 Visual Studio 2015 https://msdn.microsoft.com/en-us/library/dn986595.aspx
            【解决方案9】:

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

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

            【讨论】:

            • 我见过很多这样的代码,不考虑性能损失,最大的问题是它使调试变得复杂,因为真正的异常淹没在数百万无用的空引用异常中。
            • 有时在 3 年后阅读我自己的答案很有趣。我想,我今天会以不同的方式回答。我会说该代码违反了得墨忒耳定律,我会建议对其进行重构,这样它就不会了。
            • 截至今天,在原始答案 7 年后,我将加入 @Phillip Ngan 并使用具有以下语法的 C# 6:int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
            【解决方案10】:

            当我需要像这样链接调用时,我依赖于我创建的辅助方法 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。您的代码似乎试图从“FirstName”加载一个名为“LastName”的属性,这不是预期的用途。更正确的例子是: var postcode = person.TryGet(p => p.Address).TryGet(p => p.Postcode);顺便说一句,我的 TryGet() 辅助方法非常类似于 C# 6.0 中的一个新功能 - 空条件运算符。它的用法是这样的: var postcode = person?.Address?.Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

              你可以这样做:

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                • 好吧,在那种情况下,显然 PropertyB 永远不能为空。
                【解决方案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);
                

                【讨论】:

                  【解决方案15】:

                  偶然发现这篇文章。

                  前段时间,我在 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()
                  

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

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

                  【讨论】:

                    【解决方案16】:

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

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

                    【讨论】:

                    • 此解决方案已在其他答案中提供(重复)。完全没有理由再次发布
                    • 我没有看到任何接受默认值的内容。
                    • 我统计了另外 6 个使用已定义默认值的值。显然你看起来并不那么努力。
                    【解决方案17】:

                    一旦你克服了 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 的空合并出现之前的几年)。
                    【解决方案18】:

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

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

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

                    【讨论】:

                      【解决方案19】:
                      var result = nullableproperty ?? defaultvalue;
                      

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

                      【讨论】:

                      • 这个答案不能解决 OP 的问题。您将如何应用解决方案?当表达式的所有部分(ObjectA、PropertyA 和 PropertyB)都可以为 null 时,运算符为 ObjectA.PropertyA.PropertyB?
                      • 没错,我想我什至根本没有读过这个问题。无论如何,不​​可能就是不去做:P static void Main(string[] args) { a ca = new a(); var default_value = new a() { b = new object() }; var value = (ca ?? default_value).b ??默认值.b; } 类 a { 公共对象 b = null; }
                      • (ObjectA ?? DefaultMockedAtNull).PropertyA != null?ObjectA.PropertyA.PropertyB: null
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-07-11
                      • 2016-12-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-08-22
                      • 1970-01-01
                      相关资源
                      最近更新 更多