您所说的称为 null 安全解除引用 - 这个 SO 特别提出了这个问题:C# if-null-then-null expression。
表达式并不是真正的答案(请参阅下文以澄清我做出该声明的原因)。不过,这种扩展方法可能是:
public static TResult? GetValueSafe<TInstance, TResult>(this TInstance instance,
Func<TInstance, TResult> accessor)
where TInstance : class
where TResult : struct
{
return instance != null ? (TResult?)accessor(instance) : (TResult?)null;
}
现在你可以这样做了:
MyObject o = null;
int? i = o.GetValueSafe(obj => obj.SomeIntProperty);
Assert.IsNull(i);
显然,当属性是结构时,这是最有用的;你可以减少到任何类型,只使用default(TResult) - 但是你会得到0 用于整数、双精度等:
public static TResult GetValueSafe<TInstance, TResult>(this TInstance instance,
Func<TInstance, TResult> accessor, TResult def = default(TResult))
where TInstance : class
{
return instance != null ? accessor(instance) : def;
}
第二个版本特别有用,因为它适用于任何TResult。我已经扩展了一个可选参数以允许调用者提供默认值,例如(使用之前代码中的o):
int i = o.GetValueSafe(obj => obj.SomeIntProperty); //yields 0
i = o.GetValueSafe(obj => obj.SomeIntProperty, -1); //yields -1
//while this yields string.Empty instead of null
string s = o.GetValueSafe(obj => obj.SomeStringProperty, string.Empty);
编辑 - 回应大卫的评论
David 认为我的回答是错误的,因为它没有提供基于表达式的解决方案,而这正是我们所要求的。我的观点是,任何真正正确且确实负责任的 SO 答案都应始终尝试为提出问题的人寻求更简单的解决方案(如果存在)。我相信人们普遍认为,在我们的日常职业生活中,应该避免对原本简单的问题采取过于复杂的解决方案。 SO 之所以如此受欢迎,是因为它的社区行为方式相同。
David 还对我的“它们不是解决方案”这一不合理的说法提出了质疑 - 所以我现在将对此进行扩展,并说明为什么基于表达式的解决方案在很大程度上毫无意义,除非在极少数情况下OP 实际上并没有要求(顺便说一句,大卫的回答也没有涵盖)。
具有讽刺意味的是,它本身可能使这个答案变得不必要地复杂:) 如果您实际上并不关心为什么表达式不是最佳途径,那么您可以放心地从这里忽略
虽然说你可以用表达式解决这个问题是正确的,但对于问题中列出的示例来说,根本没有理由使用它们 - 它使最终相当复杂的事情变得过于复杂简单的问题;并且在运行时编译表达式的开销(然后将其丢弃,除非您放入缓存,除非您发出诸如调用站点之类的东西,如 DLR 使用的东西,否则很难正确处理)与解决方案相比我在这里介绍。
任何解决方案的最终动机都是尽量减少调用者所需的工作,但同时您还需要将表达式分析器要做的工作保持在最低限度,否则如果没有大量工作,解决方案将变得几乎无法解决。为了说明我的观点 - 让我们看一下我们可以使用带有表达式的静态方法实现的最简单的方法,给定我们的对象o:
var i = GetValueSafe(obj => obj.SomeIntProperty);
呃-哦,那个表达式实际上并没有做任何事情 - 因为它没有 传递 o 给它 - 表达式本身对我们没有用,因为我们需要实际的 对o 的引用可能是null。所以 - 第一个解决方案自然是明确地传递引用:
var i = GetValueSafe(o, obj => obj.SomeIntProperty);
(注意——也可以写成扩展方法)
因此,静态方法的工作是获取第一个参数并在调用它时将其传递给编译后的表达式。这也有助于识别要寻找其属性的表达式的类型。然而,它也完全否定了首先使用表达式的理由;因为方法本身可以立即决定是否访问该属性 - 因为它具有对可能是 null 的对象的引用。因此,在这种情况下,像我的扩展方法一样,简单地传递引用和访问器 delegate(而不是表达式)会更容易、更简单且更快。
正如我所提到的,有一种方法可以绕过必须传递实例,那就是执行以下操作之一:
var i = GetValueSafe(obj => o.SomeIntProperty);
或者
var i = GetValueSafe(() => o.SomeIntProperty);
我们正在打折扩展方法版本 - 因为这样我们获得了传递给方法的引用,并且一旦我们获得引用,我们就可以取消表达式,正如我最后一点所证明的那样。 em>
这里我们依赖调用者来理解他们必须在表达式,在成员 read 的左侧,以便我们实际上可以从中获取具体值,以便进行 null 检查。
首先,这不是表达式参数的自然使用,所以我相信您的调用者可能会感到困惑。还有另一个问题,如果您打算经常使用它,我认为这将是一个杀手 - 您无法缓存这些表达式,因为每次您想要回避其“null-ness”的实例都被烘焙到表达式中即通过。这意味着您总是必须为每次调用重新编译表达式;这将是真的缓慢。如果您在表达式中对实例进行参数化,则可以将其缓存 - 但最终您会得到我们的第一个解决方案,该解决方案需要传递实例;我已经再次证明了我们可以使用委托!
相对容易——使用ExpressionVisitor 类——编写可以将所有属性/字段读取(以及与此相关的方法调用)转换为您想要的“安全”调用的东西。但是,除非您打算安全阅读以下内容,否则我看不出这样做有什么好处:a.b.c.d。但是随后将值类型扩充为它们自身的可空版本将在表达式树重写中给您带来一些麻烦,我可以告诉您;留下一个几乎没有人会理解的解决方案:)