a的最佳语法是什么?(x == null) ? null : x.func()

58

基本问题 - 我有许多代码行看起来像这样:

var a = (long_expression == null) ? null : long_expression.Method();

这个函数中有很多相似的行,其中long_expression每次都不同。我正在尝试找到一种避免重复long_expression但保持紧凑的方法。类似于operator ??的相反方式。目前我考虑只是放弃,像这样把它分成多行:

var temp = long_expression;
var a = (temp == null) ? null : temp.Method();

但是我很好奇是否有一些聪明的语法我不知道,能够使这个更加简洁。


3
var a = temp ? null : temp.Method(); 我认为你的意思是 var a = temp == null ? null : temp.method(); 不过这只是一个小细节。 - Sephallia
我认为你目前的方法是正确的。 - Jonathon Reinhart
3
在“语言==C#”这样的限制条件下,“最佳语法”是什么?或者在类似情况下,例如使用(适用的)函子/单子等功能语言可以更好地编写此类内容。 - leftaroundabout
2
@leftaroundabout:是的,这个操作就是 >>= 用于 Maybe monad。 - Jon Purdy
1
虽然这只是小问题,但我真的认为将逻辑反转为:"long_expression!= null?long_expression.Method():null"更清晰 - 重要逻辑首先出现。 这也与逻辑一致,例如“if(long_expression!= null)do_something”,如果它不是变量初始化,那么它将被编写成这样。 - Ken Beckett
可能是如何在深度Lambda表达式中检查null?的重复问题。 - nawfal
7个回答

77

可以使用这样的扩展方法:

public static TResult NullOr<TSource, TResult>(this TSource source,
    Func<TSource, TResult> func) where TSource : class where TResult : class
{
    return source == null ? null : func(source);
}

那么:

var a = some_long_expression.NullOr(x => x.Method());

或者(根据您使用的 C# 版本)

var a = some_long_expression.NullOr(Foo.Method);

Foosome_long_expression 的类型。

不过,我认为我不会这样做。我只会使用两行的版本。它更简单、不那么聪明——虽然"聪明"对于 Stack Overflow 来说很有趣,但通常在实际代码中并不是一个好主意。


1
抢先一步了!我正要发表类似的内容。 - Mike Bailey
4
我一直不喜欢在空引用上运行的扩展方法。在空引用上调用方法应该始终抛出空引用异常。我知道扩展方法实际上是静态帮助方法,但这对于代码的读者并不总是清晰明了。 - Pauli Østerø
1
它可能不一致,但也非常有用。以前必须是静态类方法的方法,现在作为操作空值的扩展方法更容易被发现。在String上添加一个IsNullOrWhiteSpace的扩展方法就是一个很好的例子。 - MgSam
1
@JohnLBevan:个人偏好——我通常更喜欢简单的代码而不是“聪明”的代码。对于使用它,我不知道任何风险 - Jon Skeet
1
@wintersylf:嗯,你需要在声明 NullOr 方法的地方使用 System 命名空间的 using 指令,并且你需要在想要使用扩展方法的任何地方使用包含声明扩展方法的类的命名空间的 using 指令,但这就是全部。 - Jon Skeet
显示剩余5条评论

49

我发现这个答案很有见地。

通过执行此任务,您会将 null 值传播到系统的更深处。您将不得不再次编写您的 null 处理条件(并且一遍又一遍)来处理这些传播。

与其允许执行继续使用 null 值,不如用更好的表示取代 null 值 (引自链接)

  1. 如果 null 表示空集合,请使用一个空集合。
  2. 如果 null 表示异常情况,请抛出异常。
  3. 如果 null 表示意外未初始化的值,请明确初始化它。
  4. 如果 null 表示合法值,请进行测试 - 或者更好地使用执行 null 操作的 NullObject。

尤其是,用空集合替换 null 集合引用已经帮助我省去了很多 null 测试。


我非常同意;但不幸的是,在许多情况下,您会发现自己正在使用大量 null 的 API 或遗留代码。 - Nate C-K

5
var x = "";

/* try null instead */
string long_expression = "foo";

var a = ((x = long_expression) == null) ? null : x.ToUpper();

/* writes "FOO" (or nothing) followed by newline */
Console.WriteLine(a);

x的初始化值类型必须与long_expression的类型兼容(这里是string)。适用于Mono C#编译器,版本2.10.8.1(来自Debian软件包mono-gmcs=2.10.8.1-4)。


@MirceaChirea 好的,那么它在C#中能用吗?我这里无法测试。 - PointedEars
是的,它可以。var 告诉编译器从表达式中推断类型;在处理像 IEnumerable<MyLongClassName> 这样长的类型时特别有用。 - CMircea
@MirceaChirea 谢谢,我已经在规范中查找了 :) 现在正在使用Mono进行测试。 - PointedEars

5
我认为C#语言可以真正用一个新的操作符来处理这种逻辑 - 这是相当常见的,而且一个操作符将简化无数行代码。
我们需要类似于 "?? "或 "if null then" 操作符,但它作为一个带有 "if not null" 条件的特殊 '.' 点运算符。对于 "?? ",第一个 "?" 就像 "?:" if-then 的 'if' 部分,而第二个 "?" 表示可空类型的 'null'。我认为我们需要一个 ".?" 运算符,它的工作方式与 "." 完全相同,只是如果左表达式为空,则它将简单地计算出 null 而不是抛出异常。我想它会是一个 "dot not null" 运算符(好吧,也许 ".!?" 更合理,但让我们不要去那里)。
因此,您如此常见的示例可以像这样去掉冗余的符号名称:
var a = long_expression.?Method();

有大量的C#代码中存在嵌套的空值检查,这些代码可以从中受益。想象一下,如果代码能够像这样:

if (localObject != null)
{
    if (localObject.ChildObject != null)
    {
        if (localObject.ChildObject.ChildObject != null)
            localObject.ChildObject.ChildObject.DoSomething();
    }
}

可以简单地变成:

localObject.?ChildObject.?ChildObject.?DoSomething();

还有很多代码缺少应该存在的空值检查,导致偶尔出现运行时错误。因此,默认情况下使用新的“.? ”运算符将消除此类问题...或许不幸的是,我们无法在这一点上扭转 "." 和 ".?" 的行为,因此仅当左侧为空时,新的 ".?" 才会抛出异常——这将是更加清晰和合乎逻辑的方法,但破坏性变化非常糟糕。虽然,人们可以认为这种特定的变化可能会解决很多隐含的问题,并且不太可能破坏任何东西(只有期望引发 null 引用异常的代码)。哦,好吧...人们总是可以梦想...
唯一的缺点确实是检查 null 会稍微降低一些性能,这绝对是 "." 不检查 null 的原因。但我真的认为,如果你关心性能(并知道左侧永远不会为空),只需使用 ' .?' 就是更好的选择。
同样的,让 'for each' 检查并忽略空集合也会更好。不再需要将大多数 'for each' 语句包装在“if(collection != null)”中,或者更糟糕的是,养成始终使用空集合而不是 null 的共同习惯...在某些情况下,这样做是可以接受的,但是当在复杂的对象树中进行操作并且大多数集合为空时,这会比空值检查更糟糕(我见过很多这样的情况)。我认为,不检查 null 以提供更好的性能的良好意图在大多数情况下都产生了反作用。
Anders,现在引入这些变化并添加编译器开关永远不会太迟!

1
这被称为空安全导航器。请参阅C#语言团队成员的回答:https://dev59.com/AVDTa4cB1Zd3GeqPMMtW#3818256 - Fowl
1
"?? 的相反显然是 !??,这就是运算符应该是什么。" - Amy B
@DavidB - 我们讨论的不是'??'的相反,而是一个空值安全版本的'.',这是一个完全不同的运算符。我提到'??'只是为了解释为什么'.?'可能有意义,但其他人显然已经得出了相同的结论。 - Ken Beckett

1

您可以为 long_expression 评估的任何类型编写扩展方法:

public static object DoMethod(this MyType pLongExpression)
{
   return pLongExpression == null ? null : pLongExpression.Method();
}

这将可在任何 MyType 引用上调用,即使该引用为 null


1
我一直不喜欢在空引用上运行的扩展方法。在空引用上调用方法应该始终抛出 Null 引用异常。我知道扩展方法实际上是静态辅助方法,但对于代码读者来说并不总是清晰明了。 - Pauli Østerø
来自 OP - 长表达式每次都不同(也可以表示返回类型)。 - nawfal

0
如果 (!String.IsNullOrEmpty(x)) x.func()

0
你可以将long_expression == null放入一个函数中,用一个简短的名称命名,并在每次需要时调用该函数。

1
如果这个 long_expression 在所有地方都是相同的 - poncha
3
会太容易啦 :D 更新了问题,每个地方都不一样。 - tenfour
1
这就是我得到它的方式(许多不同的长表达式)。 - poncha
1
@Daniel:没错,那就是问题所在。 - Jonathon Reinhart
哦,我误读了问题。我没有意识到long_expression在同一行中出现了两次。如果它只出现一次,你无法避免每次都写它,但如果它出现两次,肯定有办法像Jon Skeet那样绕过它。 - Daniel
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接