5

假设我想检查一堆对象以确保没有一个为空:

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.ParametersNoNulls.

因此,上述方法显然是错误的。但是if使用运算符的语句&&工作得很好,因为它是短路的。那么:有没有办法使方法短路,以便在方法中明确引用之前不会评估其参数?

4

3 回答 3

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
}

基本上,您提供代表来懒惰地评估值,而不是值本身(因为评估这些值是导致异常的原因)。

我不是说它很好,但它是一种选择......

编辑:我认为这实际上(并且偶然地)触及了丹所追求的核心。在方法本身执行之前,对方法的所有参数进行评估。有效地使用委托可以让您延迟评估,直到方法需要调用委托来检索值。

于 2009-12-16T20:45:48.710 回答
1

您可以编写一个接受表达式树的函数,并将该树转换为检查空值的形式,并返回Func<bool>可以安全评估以确定是否存在空值的 a。

我怀疑虽然生成的代码可能很酷,但它会令人困惑,并且比仅仅编写一堆短路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 提到的运算符。正如另一个埃里克(卡特曼)可能会说的那样,那会“踢屁股”。

于 2009-12-16T21:21:31.100 回答
0

如果您不介意失去静态类型安全性,则可以使用反射。我确实介意,所以我只使用你的第一个短路结构。欢迎像 Eric 提到的功能 :)

这个问题我已经想过好几次了。Lisp 具有以您提到的方式解决问题的宏,因为它们允许您自定义评估。

我也尝试过使用扩展方法来解决这个问题,但没有什么比原始代码更难看的了。

编辑:(回复不允许我插入代码块,所以编辑我的帖子)

哎呀,没有跟上这个。对于那个很抱歉 :)

您可以使用反射通过字符串查找和评估成员或属性。我的一个朋友写的一个类的语法如下:

new ReflectionHelper(obj)["Parameters"]["UserSettings"]

它通过方法链接工作,在每个级别返回一个 ReflectionHelper。我知道 NullReferenceException 在该示例中是一个问题。我只是想演示如何将评估推迟到运行时。

一个更接近有用的例子:

public class Something
{
  public static object ResultOrDefault(object baseObject, params string[] chainedFields)
  {
    // ...
  }
}

同样,这种语法很臭。但这演示了使用字符串 + 反射将评估推迟到运行时。

于 2009-12-16T21:21:43.983 回答