【问题标题】:Is there a "true or throw" or "non-null or throw" expression in C#?C# 中是否存在“true or throw”或“non-null or throw”表达式?
【发布时间】:2019-10-30 00:11:54
【问题描述】:

鉴于此工作代码:

if (!this.TryParse()) throw new Exception();
// or
if (this.Parse() == null) throw new Exception();

由于 C# 7 已经知道 throw 表达式???:,我想知道是否(或为什么不)有一种方法可以将其写为“true or throw”或 C# 中的“非空或抛出”语句,类似于 JavaScript 或 PHP 等其他语言所允许的。这是我尝试过的:

// COMPILE ERROR:

this.TryParse() || throw new Exception();

this.Parse() ?? throw new Exception();

_ = this.TryParse() || throw new Exception();


// WORKS:

_ = this.TryParse() ? 0 : throw new Exception();

_ = this.Parse() ?? throw new Exception();

特别是,为什么_ = <obj> ?? <throw> 有效而_ = <bool> || <throw> 无效?

我为什么要这样做?纯粹的可读性:我想保持主要目的是做而不是测试的方法作为调用语句在行的开头清晰可见,而不是在 if 条件中隐藏它们。以 PHP 为例:

mysql_connect(...)
    OR die();

【问题讨论】:

  • Exception > trigger_error > die

标签: c# expression throw


【解决方案1】:

_ = <obj> ?? <throw> 有效,因为如果对象是null,则会引发异常而不是返回值。在这种情况下,?? 右侧的表达式类型无关紧要。

_ = <bool> || <throw> 不起作用,因为|| 只能应用于两个布尔值。右侧的表达式不是布尔值。 (我会说,它是未定义的。)

【讨论】:

    【解决方案2】:

    为什么要使用这样的语法?代码应该尽可能地可读,因为虽然它被编写一次,但它通常可能被阅读数十次。如果我看到以下任一情况:

    if (!this.TryParse()) throw new Exception();
    // or
    if (this.Parse() == null) throw new Exception();
    

    我自动假设程序员很困惑。为什么?因为两件事:

    1. TryParse 函数返回一个布尔值并填充一个输出参数。当您完全可以接受无法解析的输入值但不希望出现异常时使用它。 示例:int.TryParse()

    2. Parse 函数假定输入有效并返回有效的非空值,或引发异常。在这种情况下抛出一个新的异常是多余的,并且可能会混淆实际问题。示例:int.Parse()

    请记住,这个例子可能很糟糕,让我们看看几个替代方案。首先,您可以为此编写一个扩展方法:

    public static AssertNotNull(this object item) {
      if (item == null)
        throw new ArgumentException(“Object was null.”);
    }
    

    因此,我们有两种替代语法:

    1. 你的语法(_ = this.TryParse() ? 0 : throw new Exception();
    2. 我的语法:
        var val = this.TryParse();
        val.AssertNotNull();
    

    两者哪个更容易阅读?我最近在一位同事使用您的语法时对他的同行评审发表了评论。在他的例子中,如果三元组的第一部分是非空的,他会在三元组的右侧执行分配。不好。

    底线:应该始终尽可能明确地编写代码。

    【讨论】:

      【解决方案3】:

      这种情况可以使用扩展方法...

      static void Main(string[] args)
      {
          var a = false;
      
          a.OrThrow();
      }
      
      
      }
      public static class ThrowExtension {
          public static void OrThrow(this bool b)
          {
              if(!b)
                  throw new Exception();
          }
      }
      

      【讨论】:

        【解决方案4】:

        显而易见的答案是“因为他们没有那样指定”。关于 throw 表达式的总结可以在here 找到。我想我们只能猜测为什么,但实际上我认为这是有道理的。所有新的语言特性似乎都符合这个目的:能够以更实用的方法编写代码。让我们看看您的解析示例:

        var parsed = Parse() ?? throw new Exception();
        

        要在不使用 throw 表达式的情况下编写此代码,您要么必须计算 Parse 两次,要么为了一个目的而记下 parsed 变量两次。

        if (Parse() == null) throw new Exception();
        var parsed = Parse();
        

        var parsed = Parse();
        // variable `parsed` can be null at this point
        if (parsed == null) throw new Exception();
        // variable `parsed` cannot be null anymore, the type of the variable is essentially mutated
        

        所以前两个例子很简单,写this.TryParse() || throw new Exception(); 而不是if (!this.TryParse()) throw new Exception(); 在各个方面实际上都是一样的。那么,为什么要为我们已有的相同概念引入一种新语法呢?记住 Python 的语言设计原则:

        There should be one-- and preferably only one --obvious way to do it.
        

        最后一个例子更棘手。声明var a_non_false = this.TryParse() || throw new Exception(); 在语法上与var a_non_null = this.Parse() ?? throw new Exception(); 非常相似,但是我认为有很大的不同。变量a_non_false 本质上只是一个true 常量(好像它是假的,我们已经抛出了异常),所以我们可以在函数的其余部分用true 替换a_non_false,并获得相同的行为.

        至于_ = 部分,我认为这是一个不同的问题。我想知道他们为什么不禁止在如此简单的作业中使用discard variables。我肯定会避免使用它们来代替经典的流控制。

        因此,总而言之,这些设计决策起初看起来很随意,但经过仔细考虑后,它看起来更加经过深思熟虑。

        【讨论】:

        • 我最近对代码审查发表了评论,效果与此答案相同。似乎很多人不明白代码是写一次,而是读了很多遍。
        猜你喜欢
        • 2022-01-06
        • 2019-05-04
        • 1970-01-01
        • 2012-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-14
        相关资源
        最近更新 更多