如何检查深度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,这在理论上很难维持。