【问题标题】:How to check for nulls in a deep lambda expression? [duplicate]如何检查深层 lambda 表达式中的空值? [复制]
【发布时间】:2009-05-12 20:01:21
【问题描述】:

如何检查深层 lamda 表达式中的空值?

例如,我有一个嵌套了好几层的类结构,我想执行以下 lambda:

x => x.Two.Three.Four.Foo

如果二、三或四为 null,我希望它返回 null,而不是抛出 System.NullReferenceException。

public class Tests
{
    // This test will succeed
    [Fact]
    public void ReturnsValueWhenClass2NotNull()
    {
        var one = new One();
        one.Two = new Two();
        one.Two.Three = new Three();
        one.Two.Three.Four = new Four();
        one.Two.Three.Four.Foo = "blah";

        var result = GetValue(one, x => x.Two.Three.Four.Foo);

        Assert.Equal("blah", result);
    }

    // This test will fail
    [Fact]
    public void ReturnsNullWhenClass2IsNull()
    {
        var one = new One();

        var result = GetValue(one, x => x.Two.Three.Four.Foo);

        Assert.Equal(null, result);
    }

    private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
    {
        var func = expression.Compile();
        var value = func(model);
        return value;
    }

    public class One
    {
        public Two Two { get; set; }
    }

    public class Two
    {
        public Three Three { get; set; }
    }

    public class Three
    {
        public Four Four { get; set; }
    }

    public class Four
    {
        public string Foo { get; set; }
        public string Bar { get; set; }
    }
}

更新:

一种解决方案是像这样捕获 NullReferenceException:

    private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
    {
        TResult value;
        try
        {
            var func = expression.Compile();
            value = func(model);
        }
        catch (NullReferenceException)
        {
            value = default(TResult);
        }
        return value;
    }

但我不想承担捕获在我看来并不例外的异常的费用。我希望在我的域中经常出现这种情况。

更新 2:

另一种解决方案是像这样修改属性获取器:

    public class One
    {
        private Two two;
        public Two Two
        {
            get
            {
                return two ?? new Two();
            }
            set
            {
                two = value;
            }
        }
    }

这对我的域来说基本没问题,但有时我真的希望属性返回 null。我检查了 Josh E 的回答,认为它很有帮助,因为它在某些情况下非常接近我的需要。

【问题讨论】:

    标签: c# linq lambda


    【解决方案1】:

    您可以使用通用的辅助扩展方法来做到这一点,例如:

    public static class Get {
        public static T IfNotNull<T, U>(this U item, Func<U, T> lambda) where U: class {
            if (item == null) {
                return default(T);
            }
            return lambda(item);
        }
    }
    
    var one = new One();
    string fooIfNotNull = one.IfNotNull(x => x.Two).IfNotNull(x => x.Three).IfNotNull(x => x.Four).IfNotNull(x => x.Foo);
    

    【讨论】:

    • 在这种情况下,我想为 Foo 或 Bar 的任何类型返回默认值。我真正想避免的是,如果表达式树中的某些内容为空,则出现异常。
    • 我编辑了我的答案并添加了一个代码示例,它编译得很好,应该可以解决问题。
    • 这么简单?这么优雅? +1 并且没有由于反射而影响运行时性能。有没有人将此与 Gabe 的解决方案或“正常”方法进行对比?
    【解决方案2】:

    你不能以简洁的方式做到这一点。您可以将 lambda 设置为多行,也可以使用嵌套的三元运算符:

    var result = GetValue(one, x => x.Two == null ? null :
                                    x.Two.Three == null ? null :
                                    x.Two.Three.Four == null ? null :
                                    x.Two.Three.Four.Foo;
    

    丑,我知道。

    【讨论】:

    【解决方案3】:

    简洁地做到这一点需要一个尚未实现的运算符。我们考虑添加一个运算符“.?”到 C# 4.0 这将具有您想要的语义,但不幸的是它不符合我们的预算。我们会考虑将其用于该语言的假设未来版本。

    【讨论】:

    • 这会很棒! Delphi prism 的 ":" 运算符也是如此:prismwiki.codegear.com/en/Colon_Operator
    • 我同意这一点。这会让很多代码变得更干净!
    • 这个功能现在在 c# 6 中!
    • 我尝试使用“?.”在这个问题的情况下,但这是不允许的......它导致错误Error CS8072 An expression tree lambda may not contain a null propagating operator.关于这里的一些讨论:Null-propagating operator ?. in Expression Trees
    【解决方案4】:

    您现在可以在 codeplex 上使用 Maybe 项目。

    语法是:

    string result = One.Maybe(o => o.Two.Three.Four.Foo);
    
    string cityName = Employee.Maybe(e => e.Person.Address.CityName);
    

    【讨论】:

    • 使用表达式树会影响性能吗?
    • 做一些不同的事情会改变性能特征吗?是的。您或用户会注意到它实际上是否足够?我不知道你的用法,请介绍一下。
    • 您有任何关于您(或其他人)使用情况的性能统计数据吗?
    • 我没有。如果您尝试一下,也许您可​​以与我分享结果? =)
    【解决方案5】:

    我已经编写了一个扩展方法,可以让你这样做:

    blah.GetValueOrDefault(x => x.Two.Three.Four.Foo);
    

    它使用表达式树在返回表达式值之前在每个节点处构建一个嵌套条件检查空值;创建的表达式树被编译为Func 并被缓存,因此同一调用的后续使用应该几乎以本机速度运行。

    如果你愿意,你也可以传入一个默认值来返回:

    blah.GetValueOrDefault(x => x.Two.Three.Four.Foo, Foo.Empty);
    

    我已经写了一篇关于它的博客here

    【讨论】:

      【解决方案6】:

      我不熟悉 c#,但也许有一些方法可以从 ruby​​ 中实现“andand”模式,可以在不污染实现的情况下解决这个问题。

      这个概念在 Haskell 中也被称为 Maybe Monad。

      this 文章的标题看起来很有希望。

      【讨论】:

      • 有意思,这篇文章和我想出来的解决方法几乎一模一样,看我的帖子...
      • 哇,完全错过了,我想我在找“也许”这个词
      • maybe.codeplex.com可以做到。
      【解决方案7】:

      始终在使用属性之前对其进行初始化。将构造函数添加到类一、二、三和四。在构造函数中初始化您的属性,使其不为空。

      【讨论】:

      • 我通常这样做,但在这个域中,属性有时会设置为 null。这是有效的行为。
      【解决方案8】:

      您可以修改您的吸气剂以读取如下内容:

      private Two _two;
      public Two Two
      {
           get 
           {
             if (null == _two)
               return new Two();
             else
               return _two;
            }
      }
      

      【讨论】:

      • 修改实现以在客户端代码中保存一些行应该会触发各种警报。
      • 我倾向于不同意这是一个问题:我称之为防御性编码。上面的代码确保在不与该属性/对象的任何使用者共享该知识的情况下,属性的值永远不会为空。
      • 如果我在 _two 为空时继续调用 Two,我会不断得到 Two...ewnew 个实例
      • 好点。在这种情况下,您可以修改它以在返回之前将 _two 设置为新的 Two() 实例,例如if (null == _two) _two = new Two();返回_两个;
      【解决方案9】:

      我发现合并运算符有时对此很有用。这仅在您可以放入对象的默认/空等效版本时才有帮助。

      例如,有时当我在破解打开的 XML 时...

      IEnumeratable<XElement> sample;
      sample.Where(S => (S.Attribute["name"] ?? new XAttribute("name","")).Value.StartsWith("Hello"))...
      

      根据检索默认对象的方式,这可能会很冗长,上面的例子不是很好用,但你明白了。对于读取 XML 属性的特殊情况,我有一个返回属性值或空字符串的扩展方法。

      【讨论】:

        【解决方案10】:

        我将一个使用大量 if 语句以避免空值的函数转换为 .IFNotNull 方法,用于从 4 和 5 级深的 XSD 转换的类。

        下面是几行转换后的代码:

        ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_VALUE = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.VALUE).ToDouble();  
        ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_UOM = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.UOM);  
        

        这里有一些有趣的统计数据:

        1) 这种新方法的运行时间是使用 If 语句的变体的 3.7409 倍。
        2) 我将函数行数从 157 减少到 59。
        3) DevExpress 的CodeRush 有一个“维护复杂性”分数。当我转换为 Lambda 语句时,它从 984 增加到 2076,这在理论上更难维护。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多