如何检查深层lamda表达式中的空值?
比如说我有一个嵌套了几层深度的类结构,我想执行以下lambda:
x => x.Two.Three.Four.Foo
如果Two,Three或Four为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:
另一个解决方案是修改属性getter:
public class One
{
private Two two;
public Two Two
{
get
{
return two ?? new Two();
}
set
{
two = value;
}
}
}
对于我的域名来说,这是最好的,但有时候我真的期望一个属性返回null。我检查了Josh E的答案是有帮助的,因为它非常接近我在某些情况下所需要的。
答案 0 :(得分:17)
您可以使用通用帮助程序扩展方法执行此操作,例如:
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);
答案 1 :(得分:12)
你不能以简洁的方式做到这一点。您可以使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;
丑陋,我知道。
答案 2 :(得分:7)
简明扼要地要求一个尚未实现的运算符。我们考虑过添加一个运算符“。?”到C#4.0会有你想要的语义,但不幸的是它不符合我们的预算。我们会将其视为该语言的假设未来版本。
答案 3 :(得分:4)
您现在可以在codeplex上使用Maybe项目。
语法是:
string result = One.Maybe(o => o.Two.Three.Four.Foo);
string cityName = Employee.Maybe(e => e.Person.Address.CityName);
答案 4 :(得分:3)
我写了一个扩展方法,可以让你这样做:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo);
它使用表达式树在返回表达式值之前为每个节点上的空值构建嵌套条件检查;创建的表达式树被编译为Func
并被缓存,因此相同调用的后续使用应该以几乎本机的速度运行。
如果您愿意,也可以传入默认值返回:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo, Foo.Empty);
我写了一篇关于它的博客here。
答案 5 :(得分:2)
我不熟悉c#,但也许有一些方法可以实现ruby中的“andand”模式,它可以解决这个问题而不会污染实现。
这个概念也被称为Haskell中的Maybe Monad。
this文章的标题似乎很有希望。
答案 6 :(得分:0)
在使用之前始终初始化您的属性。将构造函数添加到类One,Two,Three和Four。在构造函数中初始化属性,使它们不为空。
答案 7 :(得分:0)
您可以修改您的getters以阅读:
private Two _two;
public Two Two
{
get
{
if (null == _two)
return new Two();
else
return _two;
}
}
答案 8 :(得分:0)
我发现coalesce运算符有时对此很有用。这只有在你可以放入的对象的默认/ null等效版本时才有用。
例如,有时我正在破解XML ...
IEnumeratable<XElement> sample;
sample.Where(S => (S.Attribute["name"] ?? new XAttribute("name","")).Value.StartsWith("Hello"))...
根据检索默认对象的方式,这可能是冗长的,上面的示例并不是很有用,但您可以理解。对于读取XML属性的特殊情况,我有一个返回属性值或空字符串的扩展方法。
答案 9 :(得分:0)
我转换了一个使用了大量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)这种新方法运行的时间比使用If语句的变化长3.7409倍 2)我的功能行数从157减少到59 3)来自DevExpress的CodeRush具有“维护复杂性”分数。当我转换为Lambda语句时,它从984增加到2076,这在理论上更难维护。