如何检查深度lambdaexpression式中的空值?

我如何检查深度lamdaexpression式中的空值?

举例来说,我有一个嵌套多层的类结构,我想执行下面的lambda:

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

我希望它返回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; } } } 

更新:

一个解决scheme是像这样捕获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:

另一个解决scheme是修改属性getters像这样:

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

这对我的域来说大体上是可以的,但有时候我真的期待一个属性返回null。 我检查了Josh E的答案,因为它非常接近我在某些情况下所需要的。

你不能以简明的方式做到这一点。 您可以创build多行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; 

丑,我知道。

你可以用一个通用的助手扩展方法来做到这一点,比如:

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

简明扼要地要求一个尚未实现的操作符。 我们考虑增加一个运营商“。?” 到C#4.0这将有你想要的语义,但不幸的是,它不符合我们的预算。 我们会考虑这个假设的未来版本的语言。

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

语法是:

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

我写了一个扩展方法,可以让你做到这一点:

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

它使用expression式树来在返回expression式值之前为每个节点上的空值构build一个嵌套的条件检查; 创build的expression式树被编译成一个Func并被caching,所以后续使用相同的调用应该以几乎原生的速度运行。

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

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

我在这里写了一个关于它的博客。

我并不擅长c#,但也许有一些方法可以实现ruby中的“andand”模式,它可以解决这个问题,而不会污染实现。

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

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

在使用它们之前总是初始化你的属性。 给一,二,三,四级添加一个构造函数。 在构造函数中初始化你的属性,使它们不为null。

你可以修改你的getters读取类似于:

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

我发现有时这个coalesce操作符是有用的。 这只有帮助,虽然如果有一个默认/ null的对象版本的对象,你可以放入。

例如,有时当我打开XML的时候…

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

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

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

以下是转换的代码的几行:

 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)这个新方法花了3.7409倍的时间来运行If语句的变化。
2)我把function线数从157个减less到了59个。
3)来自DevExpress的CodeRush具有“维护复杂性”分数。 当我转换到Lambda声明时,它从984增加到2076,这在理论上很难维持。