假设我想检查一堆对象以确保none为null:
if (obj != null &&
obj.Parameters != null &&
obj.Parameters.UserSettings != null) {
// do something with obj.Parameters.UserSettings
}
编写一个辅助函数来接受可变数量的参数并简化这种检查是一个诱人的前景:
static bool NoNulls(params object[] objects) {
for (int i = 0; i < objects.Length; i++)
if (objects[i] == null) return false;
return true;
}
然后上面的代码可以变成:
if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) {
// do something
}
右? 错误。如果obj
为空,那么当我尝试将NullReferenceException
传递给obj.Parameters
时,我会收到NoNulls
。
所以上述方法显然是错误的。但是使用if
运算符的&&
语句可以正常工作,因为它是短路的。那么:是否有办法使方法短路,以便在方法中明确引用之前不对其参数进行求值?
答案 0 :(得分:9)
嗯,这很难看但是......
static bool NoNulls(params Func<object>[] funcs) {
for (int i = 0; i < funcs.Length; i++)
if (funcs[i]() == null) return false;
return true;
}
然后用:
调用它if (NoNulls(() => obj,
() => obj.Parameters,
() => obj.Parameters.UserSettings)) {
// do something
}
基本上,你提供委托来懒惰地评估值,而不是值本身(因为评估这些值是导致异常的原因)。
我不是说这是很好,但它是作为一个选项......
编辑:我认为这实际上(并且意外地)触及了Dan所追求的核心。 所有方法的参数在方法本身执行之前进行评估。有效地使用委托可以让您延迟评估,直到方法需要调用委托来检索值。答案 1 :(得分:1)
您可以编写一个接受表达式树的函数,并将该树转换为将检查空值的表单,并返回可以安全评估的Func<bool>
以确定是否存在空值。 强>
我怀疑虽然生成的代码可能很酷,但它会让人感到困惑,而且不仅仅是编写一堆短路的a != null && a.b != null...
支票。实际上,它可能不仅仅是检查所有值并捕获NullReferenceException
(不是我主张将异常处理作为控制机制流)而不是性能。
这种功能的签名类似于:
public static Func<bool> NoNulls( Expression<Func<object>> expr )
它的用法看起来像:
NoNulls( () => new { a = obj,
b = obj.Parameters,
c = obj.Parameters.UserSettings } )();
如果我有空闲时间,我会写一个函数来完成这样的表达式树转换并更新我的帖子。但是,我确信Jon Skeet或Mark Gravell可以用一只眼睛闭合,一只手背后写出这样的功能。
我还希望看到C#实现了Eric提到的.?
运算符。正如另一个埃里克(卡特曼)可能会说的那样,那会“踢屁股”。
答案 2 :(得分:0)
如果你不介意失去静态类型的安全性,你可以使用反射。我记得,所以我只使用你的第一个短路结构。 Eric提到的功能将受到欢迎:)
我几次想过这个问题。 Lisp具有以您提到的方式解决问题的宏,因为它们允许您自定义您的评估。
我也尝试使用扩展方法来解决这个问题,但没有什么比原始代码更难看。
编辑:(回复不要让我插入代码块,所以编辑我的帖子)
哎呀,没跟上这个。抱歉:)您可以使用反射通过字符串查找和评估成员或属性。我的朋友写的一个类的语法如下:
new ReflectionHelper(obj)["Parameters"]["UserSettings"]
它通过方法链接工作,在每个级别返回一个ReflectionHelper。我知道NullReferenceException是该示例中的一个问题。我只是想证明如何将评估推迟到运行时。
稍微接近帮助的一个例子:
public class Something
{
public static object ResultOrDefault(object baseObject, params string[] chainedFields)
{
// ...
}
}
同样,这种语法很糟糕。但这表明使用字符串+反射将评估推迟到运行时。