2009-12-16 44 views
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为空,那么当我尝试将obj.Parameters传递给NoNulls时,我会得到NullReferenceException

所以上述方法显然是错误的。但if声明使用&&运算符工作得很好,因为它是短路的。所以:是否有任何方法使方法短路,以便它的参数不被评估,直到在方法中明确引用?

+11

你真正需要解决这个问题并不是懒惰的方法论证评估 - 尽管如你所见,这可以做到这一点。最好是运算符x.?y具有(x == null?null:x.y)的语义 - 这样你可以说“if(obj.?Parameters.?UserSettings == null)”。我们考虑过C#4的这样一个运算符,但从未实现它。也许对于假设的未来版本。 – 2009-12-16 20:59:00

+0

这真的很有趣。有没有其他语言实现等效的运算符? – 2009-12-16 21:08:14

+1

@Chris:Groovy呢。它被称为无效的解除引用操作符。很高兴听到C#团队考虑到它并可能再次考虑它:) – 2009-12-16 21:21:35

回答

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 
} 

基本上你提供委托延迟计算的值,而不是值本身(因为评估这些值是导致异常的原因)。

我不是说这是不错,但它的存在作为一个选项...

编辑:其实,这(和意外)获取到什么丹后的心脏,我想。 全部在方法本身执行之前评估方法的参数。有效地使用委托让您可以延迟评估,直到方法需要调用委托来检索值。

+4

这是丑陋的,令人困惑的,但不是空合并运算符的窍门:'if((object z = a ?? b ?? c)!= null)DoSomething();' – LBushkin 2009-12-16 20:49:34

+0

另一个想法是通过将单个lambda表达式作为表达式树用于可以分解表达式树并执行短路评估的方法。 – LBushkin 2009-12-16 20:51:02

+0

我其实是想过,不管你信不信。当然,对于给定的目的 - 检查空值 - 这需要与原始方法大致相同的代码量,因此并不是那么有用。当然,为了其他目的,它可能会更好。然而,我不想使用它的主要原因是它需要使用lambda表达式,这对于VB来说是不可用的(至少在VB 10之前,我想呢?),并且我想要一些有用的既C#和VB,因为我们在工作中使用这两种语言。 – 2009-12-16 20:52:50

1

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

我怀疑,虽然最终的代码可能很酷,但它会令人困惑,并且性能远远低于仅编写一堆短路检查。事实上,它可能不如仅检查所有值并捕获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所暗示的.?运算符。正如一个不同的埃里克(卡特曼)可能会说,那会“踢屁股”。

+1

你不需要使它成为三个单独的参数 - 只需使用'()=> obj.Parameters.UserSettings'并获取表达式树来检查结果每个属性调用之间为空。 – 2009-12-16 23:39:51

0

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

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

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

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

哎呀,没跟上这一点。对不起,关于:)

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

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

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

一个例子稍微更接近于有用的:

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

再次,此语法臭。但是,这表明使用字符串+反射来推迟评估运行时。

+0

出于好奇,如何用反射来达到这个目的呢?问题似乎是,一旦你输入了方法,它的所有参数都已经被评估过了。我不清楚如何反思可以弥补这一点;但也许我错过了一些东西。 – 2009-12-17 15:23:49

+0

修改帖子以回复您的评论 – 2010-02-04 05:06:51

相关问题