C# 如何检查深度 lambda 表达式中的空值?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/854591/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to check for nulls in a deep lambda expression?
提问by JohnRudolfLewis
How can I check for nulls in a deep lamda expression?
如何在深度 lamda 表达式中检查空值?
Say for example I have a class structure that was nested several layers deep, and I wanted to execute the following lambda:
举例来说,我有一个嵌套多层的类结构,我想执行以下 lambda:
x => x.Two.Three.Four.Foo
I want it to return null if Two, Three, or Four were null, rather than throwing a System.NullReferenceException.
如果两个、三个或四个为 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; }
}
}
UPDATE:
更新:
One solution would be to catch the NullReferenceException like this:
一种解决方案是像这样捕获 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;
}
But I hate to incur the expense of catching an exception that is not, in my mind, exceptional. I expect this to be the case quite often in my domain.
但是我讨厌为捕获一个在我看来并不特殊的异常而付出代价。我希望在我的领域中经常出现这种情况。
UPDATE 2:
更新 2:
Another solution would be modify the property getters like this:
另一种解决方案是像这样修改属性 getter:
public class One
{
private Two two;
public Two Two
{
get
{
return two ?? new Two();
}
set
{
two = value;
}
}
}
Which is mostly ok for my domain, but there are times when I really to expect a property to return null. I checked the answer from Josh E as helpful since it comes pretty close to what I need in some cases.
这对我的域来说基本没问题,但有时我真的希望属性返回 null。我检查了 Josh E 的回答很有帮助,因为它在某些情况下非常接近我需要的。
采纳答案by Gabe Moothart
You can't do that in a concise way. You can either make the lambda multiple lines, or use nested ternary operators:
你不能以简洁的方式做到这一点。您可以使 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;
Ugly, I know.
丑,我知道。
回答by Lucero
You could do this with a generic helper extension method, something like:
您可以使用通用的辅助扩展方法来做到这一点,例如:
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);
回答by Wallstreet Programmer
Always initialize your properties before using them. Add a constructor to class One, Two, Three and Four. In the constructor initialize your properties so they are not null.
在使用它们之前总是初始化你的属性。向类一、二、三和四添加一个构造函数。在构造函数中初始化您的属性,使它们不为空。
回答by Josh E
You could modify your getters to read something like:
你可以修改你的 getter 来读取类似的内容:
private Two _two;
public Two Two
{
get
{
if (null == _two)
return new Two();
else
return _two;
}
}
回答by krusty.ar
I'm not skilled in c#, but maybe there's some way to implement the "andand" pattern from ruby that solves exactly this problem without polluting the implementation.
我不擅长 c#,但也许有一些方法可以从 ruby 中实现“andand”模式,可以在不污染实现的情况下完全解决这个问题。
The concept is also known as the Maybe Monad in Haskell.
这个概念在 Haskell 中也被称为 Maybe Monad。
The title of thisarticle seems promising.
这篇文章的标题看起来很有希望。
回答by Frank Schwieterman
I find the coalesce operator useful for this at times. This only helps though if there is a default/null equivalent version of the object you can drop in.
我发现 coalesce 运算符有时对此很有用。但是,如果存在可以放入的对象的默认/空等效版本,这只会有所帮助。
For instance, sometimes when I'm cracking open XML...
例如,有时当我打开 XML 时......
IEnumeratable<XElement> sample;
sample.Where(S => (S.Attribute["name"] ?? new XAttribute("name","")).Value.StartsWith("Hello"))...
Depending on how the default objects are retrieved this can be verbose, and the above example is not a great use but you get the idea. For the particular case of reading XML attributes I have an extension method that returns the attribute value or an empty string.
根据检索默认对象的方式,这可能很冗长,上面的示例不是很好用,但您明白了。对于读取 XML 属性的特殊情况,我有一个返回属性值或空字符串的扩展方法。
回答by Eric Lippert
Doing this concisely requires an as-yet-unimplemented operator. We considered adding an operator ".?" to C# 4.0 which would have your desired semantics, but unfortunately it did not fit into our budget. We'll consider it for a hypothetical future version of the language.
简洁地执行此操作需要一个尚未实现的运算符。我们考虑添加一个运算符“.?” 到 C# 4.0,它会有你想要的语义,但不幸的是它不符合我们的预算。我们将考虑将其用于该语言的假设未来版本。
回答by Maslow
回答by M Akin
I converted a function that used a lot of if statements to avoid the nulls to the .IFNotNull method for classes that I converted from an XSD that are 4 and 5 levels deep.
对于我从 4 级和 5 级深的 XSD 转换的类,我将使用大量 if 语句以避免空值的函数转换为 .IFNotNull 方法。
Here are a few lines of the converted code:
下面是几行转换后的代码:
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);
Here are some interesting stats about it:
以下是一些有趣的统计数据:
1) This new method took 3.7409 times longer to run that the variation with the If Statements.
2) I decreased my function line count from 157 to 59.
3) CodeRushfrom DevExpress has a "Maintenance Complexity" score. When I converted to the Lambda statements, it increased from 984 to 2076, which is theoretically much harder to maintain.
1) 这种新方法用了 3.7409 倍的时间来运行 If 语句的变体。
2) 我将函数行数从 157 减少到 59。
3) DevExpress 的CodeRush有一个“维护复杂性”分数。当我转换为 Lambda 语句时,它从 984 增加到 2076,这在理论上更难维护。
回答by Steve Wilkes
I've written an extension method which enables you to do this:
我编写了一个扩展方法,使您能够做到这一点:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo);
It uses Expression Trees to build a nested conditional checking for nulls at each node before returning the expression value; the created expression tree is compiled to a Func
and cached, so subsequent uses of the same call should run at almost native speed.
在返回表达式值之前,它使用表达式树在每个节点上构建一个嵌套的条件检查空值;创建的表达式树被编译为 aFunc
并缓存,因此同一调用的后续使用应该以几乎本机的速度运行。
You can also pass in a default value to return if you like:
如果你愿意,你也可以传入一个默认值来返回:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo, Foo.Empty);
I've written a blog about it here.
我在这里写了一篇关于它的博客。