C# 我可以在方法调用中强制自己短路吗?

C# 我可以在方法调用中强制自己短路吗?,c#,.net,short-circuiting,C#,.net,Short Circuiting,假设我要检查一组对象,以确保没有一个对象为空: if (obj != null && obj.Parameters != null && obj.Parameters.UserSettings != null) { // do something with obj.Parameters.UserSettings } 编写一个helper函数来接受可变数量的参数并简化此类检查是一个诱人的前景: static bool NoNulls(pa

假设我要检查一组对象,以确保没有一个对象为空:

if (obj != null &&
    obj.Parameters != null &&
    obj.Parameters.UserSettings != null) {

    // do something with obj.Parameters.UserSettings
}
编写一个helper函数来接受可变数量的参数并简化此类检查是一个诱人的前景:

static bool NoNulls(params object[] objects) {
    for (int i = 0; i < objects.Length; i++)
        if (objects[i] == null) return false;

    return true;
}
对吧?错误。如果
obj
为空,那么当我尝试将
obj.Parameters
传递到
NoNulls
时,我将得到一个
NullReferenceException


因此,上述方法显然是错误的。但是使用
&
操作符的
if
语句工作正常,因为它短路了。所以:有没有办法使一个方法短路,使其参数在方法中被显式引用之前不会被计算?

好吧,这很难看,但是

static bool NoNulls(params Func<object>[] funcs) {
    for (int i = 0; i < funcs.Length; i++)
        if (funcs[i]() == null) return false;

    return true;
}
基本上,您为代理提供的是惰性地评估值,而不是值本身(因为评估这些值是导致异常的原因)

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


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

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

我怀疑,虽然生成的代码可能很酷,但它会让人困惑,而且比仅仅编写一堆短路的
a!=空和a.b!=空…
检查。事实上,它的性能可能不如只检查所有值并捕获
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提到的
操作符。正如另一位Eric(Cartman)可能会说的那样,这会“踢屁股”。

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

这个问题我想了好几次。Lisp中的宏可以按照您提到的方式解决问题,因为它们允许您自定义计算

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

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

哎呀,我没跟上。很抱歉:)

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

new ReflectionHelper(obj)["Parameters"]["UserSettings"]
它通过方法链接工作,在每个级别返回ReflectionHelper。我知道在那个例子中NullReferenceException是个问题。我只是想演示如何将评估推迟到运行时

一个稍微有帮助的例子:

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

同样,这种语法很糟糕。但这演示了如何使用字符串+反射将计算推迟到运行时。

这既难看又令人困惑,但是空合并操作符不起作用吗:
if((object z=a??b??c)!=null)DoSomething()另一个想法是将一条lambda语句作为表达式树传递给一个方法,该方法可以分解表达式树并执行短路求值。我确实考虑过这一点,信不信由你。当然,对于给定的目的——检查空值——这将需要与原始方法大致相同的代码量,因此并不十分有用。当然,出于其他目的,它可能更好。不过,我不想使用它的主要原因是,它需要使用lambdas,这是VB无法使用的(我想至少在vb10之前?),而且我想从C#和VB中得到一些有用的东西,因为我们在工作中使用这两种语言。@LBushkin:??只有当所有表达式都是同一类型(模为空)或一种类型可以转换为另一种类型时,才会工作。例如,我认为它在这里给出的案例中不起作用。@Jon:是的,你说得对??。必须将每个元素强制转换为object才能使其工作,这并不一定会使其更简单:
if((object z=(object)a???(object)b???(object)c)!=null)DoSomething()解决这个问题实际上需要的不是对方法参数的惰性求值——不过,正如您所注意到的,这可以解决问题。更好的是运算符x.?y,其语义为(x==null?null:x.y)——这样您可以说“if(obj.?Parameters.?UserSettings==null)”。我们考虑过C#4的这种操作符,但从未实现过。也许是一个假设的未来版本。那真的很有趣。还有其他语言实现了等效的运算符吗?@Chris:Groovy实现了。它被称为空安全解引用操作符。很高兴听到C的团队已经考虑过了,可能会再次考虑:)这是出于某种原因,提醒我在Lisp引用代码,以便以后的评估。通常情况下,Lisp的设计者预计到了这一需求,并在20世纪50年代实现了它。您不需要将它设为三个单独的参数-只需使用
()=>obj.Parameters.UserSettings
并获取表达式树来检查每次属性调用之间的结果是否为null。Ou
new ReflectionHelper(obj)["Parameters"]["UserSettings"]
public class Something
{
  public static object ResultOrDefault(object baseObject, params string[] chainedFields)
  {
    // ...
  }
}