深度空检查,有更好的方法吗?
注意:这个问题是在引入 < C# 6 / Visual Studio 2015 中的 code>.? 运算符。
我们都经历过这样的情况,我们有一些深层属性,比如 cake.frosting.berry.loader,我们需要检查它是否为 null,所以也不例外。方法是使用短路 if 语句,
if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...
这并不完全优雅,也许应该有一种更简单的方法来检查整个链并查看它是否遇到空变量/属性。
是否可以使用某种扩展方法,或者它是一种语言功能,还是只是一个坏主意?
Note: This question was asked before the introduction of the .?
operator in C# 6 / Visual Studio 2015.
We've all been there, we have some deep property like cake.frosting.berries.loader that we need to check if it's null so there's no exception. The way to do is is to use a short-circuiting if statement
if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...
This is not exactly elegant, and there should perhaps be an easier way to check the entire chain and see if it comes up against a null variable/property.
Is it possible using some extension method or would it be a language feature, or is it just a bad idea?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
我们考虑过添加一个新的操作“?”。具有您想要的语义的语言。 (现在已经添加了;见下文。)也就是说,您会说
编译器将为您生成所有短路检查。
它没有达到 C# 4 的标准。也许是该语言假设的未来版本。
更新(2014):
?.
运算符现已计划中< /a> 用于下一个 Roslyn 编译器版本。请注意,对于运算符的确切语法和语义分析仍然存在一些争论。更新(2015 年 7 月): Visual Studio 2015 已发布,并附带支持 空条件运算符
?.
和?[]
。We have considered adding a new operation "?." to the language that has the semantics you want. (And it has been added now; see below.) That is, you'd say
and the compiler would generate all the short-circuiting checks for you.
It didn't make the bar for C# 4. Perhaps for a hypothetical future version of the language.
Update (2014):
The
?.
operator is now planned for the next Roslyn compiler release. Note that there is still some debate over the exact syntactic and semantic analysis of the operator.Update (July 2015): Visual Studio 2015 has been released and ships with a C# compiler that supports the null-conditional operators
?.
and?[]
.我受到这个问题的启发,尝试找出如何使用表达式树通过更简单/更漂亮的语法来完成这种深度空检查。虽然我确实同意答案,如果您经常需要访问层次结构深处的实例,那么它可能是一个糟糕的设计,但我也确实认为在某些情况下,例如数据呈现,它可以非常有用。
所以我创建了一个扩展方法,它允许您编写:
如果表达式的任何部分都不为空,这将返回浆果。如果遇到null,则返回null。但有一些警告,在当前版本中,它仅适用于简单的成员访问,并且仅适用于 .NET Framework 4,因为它使用 MemberExpression.Update 方法,这是 v4 中的新方法。这是 IfNotNull 扩展方法的代码:
它的工作原理是检查表示表达式的表达式树,并逐个评估各个部分;每次检查结果是否不为空。
我相信这可以扩展,以便支持除 MemberExpression 之外的其他表达式。将此视为概念验证代码,请记住,使用它会带来性能损失(在许多情况下这可能并不重要,但不要在紧密循环中使用它:-))
I got inspired by this question to try and find out how this kind of deep null checking can be done with an easier / prettier syntax using expression trees. While I do agree with the answers stating that it might be a bad design if you often need to access instances deep in the hierarchy, I also do think that in some cases, such as data presentation, it can be very useful.
So I created an extension method, that will allow you to write:
This will return the Berries if no part of the expression is null. If null is encountered, null is returned. There are some caveats though, in the current version it will only work with simple member access, and it only works on .NET Framework 4, because it uses the MemberExpression.Update method, which is new in v4. This is the code for the IfNotNull extension method:
It works by examining the expression tree representing your expression, and evaluating the parts one after the other; each time checking that the result is not null.
I am sure this could be extended so that other expressions than MemberExpression is supported. Consider this as proof-of-concept code, and please keep in mind that there will be a performance penalty by using it (which will probably not matter in many cases, but don't use it in a tight loop :-) )
我发现这个扩展对于深度嵌套场景非常有用。
这是我从 C# 和 T-SQL 中的空合并运算符衍生出来的想法。好处是返回类型始终是内部属性的返回类型。
这样你就可以这样做:
...或者上述内容的轻微变化:
这不是我所知道的最好的语法,但它确实有效。
I've found this extension to be quite useful for deep nesting scenarios.
It's an idea I derrived from the null coalescing operator in C# and T-SQL. The nice thing is that the return type is always the return type of the inner property.
That way you can do this:
...or a slight variation of the above:
It's not the best syntax I know, but it does work.
除了违反德米特法则之外,正如 Mehrdad Afshari 已经指出的那样,在我看来,你还需要对决策逻辑进行“深度空检查”。
当您想要用默认值替换空对象时,最常见的情况是。在这种情况下,您应该考虑实施空对象模式。它充当真实对象的替代品,提供默认值和“非操作”方法。
Besides violating the Law of Demeter, as Mehrdad Afshari has already pointed out, it seems to me you need "deep null checking" for decision logic.
This is most often the case when you want to replace empty objects with default values. In this case you should consider implementing the Null Object Pattern. It acts as a stand-in for a real object, providing default values and "non-action" methods.
更新:从 Visual Studio 2015 开始,C# 编译器(语言版本 6)现在可以识别
?.
运算符,这使得“深度 null 检查”变得轻而易举。有关详细信息,请参阅此答案。除了重新设计你的代码之外,比如
建议这个已删除的答案,
另一个(尽管很糟糕)的选择是使用 try…catch 块来查看在深度属性查找期间是否发生 NullReferenceException 。
我个人不会这样做,原因如下:
这几乎肯定是一种语言功能(在 C# 6 中以
.?
和?[]
运算符),除非 C# 已经有更复杂的惰性求值,或者除非您想使用反射(这可能也不是一个好主意)性能和类型安全的原因)。由于无法简单地将 cake.frosting.berrys.loader 传递给函数(它将被评估并抛出空引用异常),因此您必须在以下方式:它接受一个对象和属性名称来查找:
(注意:代码已编辑。)
您很快就会发现这种方法存在一些问题。首先,您无法获得任何类型安全性以及简单类型的属性值的可能装箱。其次,如果出现问题,您可以返回 null,并且您必须在调用函数中检查这一点,或者抛出异常,然后返回到开始的地方。第三,可能会很慢。第四,它看起来比你开始时更难看。
我要么坚持:
要么接受 Mehrdad Afshari 的上述答案。
PS:当我写这个答案时,我显然没有考虑 lambda 函数的表达式树;请参阅@driis' 的答案以获取此方向的解决方案。它也基于一种反射,因此可能不如更简单的解决方案(
if (... != null & ... != null) ...
)执行得那么好,但它可能会被认为更好从语法的角度来看。Update: Starting with Visual Studio 2015, the C# compiler (language version 6) now recognizes the
?.
operator, which makes "deep null checking" a breeze. See this answer for details.Apart from re-designing your code, like
this deleted answer suggested,
another (albeit terrible) option would be to use a
try…catch
block to see if aNullReferenceException
occurs sometime during that deep property lookup.I personally wouldn't do this for the following reasons:
NullReferenceException
s should probably never be caught explicitly. (See this question.)This would almost certainly have to be a language feature (which is available in C# 6 in the form of the
.?
and?[]
operators), unless C# already had more sophisticated lazy evaluation, or unless you want to use reflection (which probably also isn't a good idea for reasons of performance and type-safety).Since there's no way to simply pass
cake.frosting.berries.loader
to a function (it would be evaluated and throw a null reference exception), you would have to implement a general look-up method in the following way: It takes in an objects and the names of properties to look up:(Note: code edited.)
You quickly see several problems with such an approach. First, you don't get any type safety and possible boxing of property values of a simple type. Second, you can either return
null
if something goes wrong, and you will have to check for this in your calling function, or you throw an exception, and you're back to where you started. Third, it might be slow. Fourth, it looks uglier than what you started with.I'd either stay with:
or go with the above answer by Mehrdad Afshari.
P.S.: Back when I wrote this answer, I obviously didn't consider expression trees for lambda functions; see e.g. @driis' answer for a solution in this direction. It's also based on a kind of reflection and thus might not perform quite as well as a simpler solution (
if (… != null & … != null) …
), but it may be judged nicer from a syntax point-of-view.虽然driis的答案很有趣,但我认为这在性能方面有点太昂贵了。我宁愿为每个属性路径编译一个 lambda,缓存它,然后重新调用它的多种类型,而不是编译许多委托。
下面的 NullCoalesce 就是这样做的,它返回一个带有 null 检查的新 lambda 表达式,并在任何路径为 null 时返回 default(TResult)。
示例:
将返回一个表达式
代码:
While driis' answer is interesting, I think it's a bit too expensive performance wise. Rather than compiling many delegates, I'd prefer to compile one lambda per property path, cache it and then reinvoke it many types.
NullCoalesce below does just that, it returns a new lambda expression with null checks and a return of default(TResult) in case any path is null.
Example:
Will return an expression
Code:
一种选择是使用 Null 对象 Patten,因此当您没有蛋糕时,您不必使用 null,而是使用返回 NullFosting 等的 NullCake。抱歉,我不太擅长解释这一点,但其他人很擅长,请参阅
One option is to use the Null Object Patten, so instead of having null when you don’t have a cake, you have a NullCake that returns a NullFosting etc. Sorry I am not very good at explaining this but other people are, see
我也经常希望有一个更简单的语法!当你的方法返回值可能为空时,它会变得特别难看,因为这样你就需要额外的变量(例如:cake.frosting.flavors.FirstOrDefault().loader)。
但是,这里有一个我使用的相当不错的替代方案:创建一个空安全链辅助方法。我意识到这与上面 @John 的答案(使用
Coal
扩展方法)非常相似,但我发现它更简单并且打字更少。它看起来像这样:这是实现:
我还创建了几个重载(带有 2 到 6 个参数),以及允许链以值类型或默认值结束的重载。这对我来说真的很有效!
I too have often wished for a simpler syntax! It gets especially ugly when you have method-return-values that might be null, because then you need extra variables (for example:
cake.frosting.flavors.FirstOrDefault().loader
)However, here's a pretty decent alternative that I use: create an Null-Safe-Chain helper method. I realize that this is pretty similar to @John's answer above (with the
Coal
extension method) but I find it's more straightforward and less typing. Here's what it looks like:Here's the implementation:
I also created several overloads (with 2 to 6 parameters), as well as overloads that allow the chain to end with a value-type or default. This works really well for me!
有 也许 codeplex 项目 实现
Maybe 或 IfNotNull 在 C# 中使用 lambda 进行深层表达式
使用示例:
在类似问题中建议链接如何检查深度 lambda 表达式中的 null 值?
There is Maybe codeplex project that Implements
Maybe or IfNotNull using lambdas for deep expressions in C#
Example of use:
The link was suggested in a similar question How to check for nulls in a deep lambda expression?
正如 John Leidegren 的 答案,解决此问题的一种方法是使用扩展方法和委托。使用它们可能看起来像这样:
实现很混乱,因为您需要让它适用于值类型、引用类型和可为 null 的值类型。您可以在 Timwi 的 回答检查空值的正确方法是什么?。
As suggested in John Leidegren's answer, one approach to work-around this is to use extension methods and delegates. Using them could look something like this:
The implementation is messy because you need to get it to work for value types, reference types and nullable value types. You can find a complete implementation in Timwi's answer to What is the proper way to check for null values?.
或者你可以使用反射:)
反射函数:
用法:
我的案例(在反射函数中返回 DBNull.Value 而不是 null):
Or you may use reflection :)
Reflection function:
Usage:
My Case(return DBNull.Value instead of null in reflection function):
试试这个代码:
Try this code:
我昨晚发布了这个,然后一个朋友向我指出了这个问题。希望有帮助。然后,您可以执行以下操作:
阅读 完整的博客文章在这里。
这位朋友还建议您观看此内容。
I posted this last night and then a friend pointed me to this question. Hope it helps. You can then do something like this:
Read the full blog post here.
The same friend also suggested that you watch this.
我稍微修改了此处的代码,使其适用于所提出的问题:
是的,这可能不是由于 try/catch 性能影响,最佳解决方案,但它有效:>
用法:
I slightly modified the code from here to make it work for the question asked:
And yes, this is probably not the optimal solution due to try/catch performance implications but it works :>
Usage:
如果您需要实现此目的,请执行以下操作:
Usage
或
Helper 类实现
Where you need to achieve this, do this:
Usage
or
Helper class implementation
我喜欢 Objective-C 所采取的方法:
“Objective-C 语言采用另一种方法来解决这个问题,它不会调用 nil 上的方法,而是为所有此类调用返回 nil。”
I like approach taken by Objective-C:
"The Objective-C language takes another approach to this problem and does not invoke methods on nil but instead returns nil for all such invocations."