【问题标题】:Why can't I throw exceptions from an expression-bodied member?为什么我不能从表达式体成员抛出异常?
【发布时间】:2015-11-17 05:34:21
【问题描述】:

使用表达式主体成员允许您将方法或属性的主体定义为没有 return 关键字的单个表达式(它是否应该返回某些内容)。

比如它转这些

int Method1()
{
    return 5;
}

void Method2()
{
    Console.WriteLine();
}

进入这些

int Method1() => 5;

void Method2() => Console.WriteLine();

当您从主体中抛出异常时,差异就会发挥作用:

void Method3()
{
    throw new Exception();
}

但是,以下内容不会编译:

void Method3() => throw new Exception();

带有以下消息:

Warning The member 'Program.Exception()' does not hide an inherited member. The new keyword is not required.  
Error   'Program.Exception()' must declare a body because it is not marked abstract, extern, or partial  
Error   ; expected  
Error   Invalid token 'throw' in class, struct, or interface member declaration  
Error   Method must have a return type
Error   Invalid expression term 'throw' 

为什么?

【问题讨论】:

  • 不错。然而,只抛出异常的方法是异常抛出器!而且没有用,因为你可以直接抛出异常:)
  • @M.kazemAkhgary:我预计这是在尚未实现的方法中的常见情况,显然,抛出NotImplementedException

标签: c# roslyn c#-6.0


【解决方案1】:

发生这种情况是因为前两个代码 sn-ps(5Console.WriteLine)是表达式。更具体地说,它们分别是NumericLiteralExpressionInvocationExpression

后一个 (throw new Exception()) 是一个声明——在本例中为:ThrowStatement

如果您查看 Roslyn SDK,您会注意到 MethodDeclarationSyntax 对象具有 ExpressionBody 类型的 ArrowExpressionClauseSyntax 属性,而该属性又具有 ExpressionSyntax 类型的属性。这应该清楚地表明,在表达式主体成员中只接受表达式。

如果您查看最后一个代码示例,您会注意到它包含一个ThrowStatementSyntax,而ExpressionSyntax 又具有一个ExpressionSyntax 属性。在我们的例子中,我们使用 ObjectCreationExpressionSyntax 对象填充它。


表达式和语句有什么区别?

为什么它也不接受语句?

我只能在这里猜测,但我认为这是因为这会带来太多的副作用,以至于无法抛出异常。我不相信表达式和语句在继承中有共同的祖先,所以会有很多代码重复。最后我认为它归结为根本不值得麻烦,即使它在某种程度上是有道理的。

当您编写一个简单的表达式作为方法体的一部分时,该方法体实际上被包裹在ExpressionStatementSyntax 下——是的,两者结合!这允许它与方法的Body 属性下的其他语句组合在一起。在幕后,他们必须展开这个并从中提取表达式。这又可以用于表达式主体成员,因为此时您只剩下一个表达式,而不再是一个语句。

然而,这里的一个重要注意事项是,return 语句是.. 一个语句。更具体地说是ReturnStatementSyntax。他们一定已经明确地处理了这个问题并应用了编译器魔法,尽管这确实引出了一个问题:为什么不对ThrowStatementSyntax 做同样的事情?

考虑以下场景:突然间,throw 语句也被接受了。但是,由于表达式主体成员只能将表达式作为其主体 (duh),这意味着您必须省略 throw 关键字,而是留下 new Exception()。你将如何区分打算使用return 语句和throw 语句?

这两种方法的表达体变体之间没有区别:

public Exception MyMethod()
{
    return new Exception();
}

public Exception MyMethod()
{
    throw new Exception();
}

throwreturn 语句都是有效的方法结尾。但是,当您省略它们时,两者并没有什么区别——因此:您永远不会知道是返回还是抛出新创建的异常对象。

我应该从中得到什么?

表达式体成员正如其名称所说的那样:在其主体中只有一个表达式的成员。这意味着您必须知道究竟是什么构成了表达式。仅仅因为它是一个“陈述”并不能使它成为一种表达方式。

【讨论】:

  • 在 github.com/dotnet/roslyn/issues 上讨论过关于 throw 语句和 C# 7 上的表达式等问题。不过,我不记得有什么特别的问题。
  • 很高兴看到这个答案中的工作 sn-p。
  • @StefanSteinegger 没有工作的 sn-p -- 根本不支持从表达式体成员抛出异常
  • @StefanSteinegger:确实——您必须在 expression 实体成员中放置一个 expression。该语法只是为了成为一种快捷方式。如果您不在该快捷方式之外,则只需使用常规语法。
  • @JasonMalinowski:我知道,我只是说很高兴看到它。
【解决方案2】:

不是关于原因的答案,而是一种解决方法:

void Method3() => ThrowNotImplemented();

int Method4() => ThrowNotImplemented<int>();

private static void ThrowNotImplemented()
{
    throw new NotImplementedException();
}

private static T ThrowNotImplemented<T>()
{
    throw new NotImplementedException();
}

【讨论】:

    【解决方案3】:

    此功能将在 C#7 中提供。来自https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

    在表达式中间抛出异常很容易:只需调用一个为您执行此操作的方法!但在 C# 7.0 中,我们直接允许 throw 在某些地方作为表达式:

    class Person
    {
        public string Name { get; }
        public Person(string name) => Name = name ?? throw new ArgumentNullException(name);
        public string GetFirstName()
        {
            var parts = Name.Split(" ");
            return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
        }
        public string GetLastName() => throw new NotImplementedException();
    }
    

    编辑:

    更新此问题以添加指向有关 throw 现在如何用作表达式主体成员、三元表达式和空合并表达式中的表达式的更新信息的链接,现在 C# 7 已发布:

    What's new in C# 7 - Throw expressions.

    New Features in C# 7.0.

    【讨论】:

    • 问题是这似乎不起作用:false || throw new Exception("what about my throw is an expression?")
    • 您希望该 sn-p 有什么行为?
    • (object)null ?? throw new ArgumentNullException()的行为相同
    • @binki 抛出表达式是explicitly only permitted?: 条件运算符的操作数中,作为空合并?? 运算符的第二个运算符和作为lambda 或方法中的表达式主体。
    • 我希望我们可以使用 return 语句来做到这一点。 var something = GetSomethingOrDefault() ?? return;
    【解决方案4】:

    正如 Jeroen Vannevel 所解释的,我们只能将表达式用于表达式主体成员。 我不建议这样做,但您始终可以通过编写 lambda 表达式将其转换为适当的类型并调用它,将(复杂)代码封装在表达式中。

    public void Method3() => ((Action)(() => { throw new Exception(); })).Invoke();
    

    这样你仍然可以在表达式主体成员的一行中抛出异常!

    可能有充分的理由不这样做。 但我认为这是一个设计缺陷,即表达式主体成员仅限于这样的表达式,并且可以像本示例中那样解决。

    【讨论】:

      【解决方案5】:

      虽然它是一个旧线程,但 C# 现在支持在 C# 7 中添加的 throw 表达式。

      以前,

      var colorString = "green,red,blue".Split(',');
      var colors = (colorString.Length > 0) ? colorString : null
      if(colors == null){throw new Exception("There are no colors");}
      

      没有了。现在,作为 Null 合并运算符:

      var firstName = name ?? throw new ArgumentException ();
      

      作为条件运算符:

      条件运算符也可以。

      var arrayFirstValue = (array.Length > 0)? array[1] : 
        throw new Expection("array contains no elements");
      

      表达式主体成员:

      public string GetPhoneNumber () => throw new NotImplementedException();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-09
        • 1970-01-01
        • 2021-04-17
        • 2014-10-19
        相关资源
        最近更新 更多