为什么我不能从表达式身体成员中抛出exception?

使用表达式成员允许您将方法或属性的主体定义为单个表达式而不使用return关键字(如果它返回了某些内容)。

例如,它变成了这些

int Method1() { return 5; } void Method2() { Console.WriteLine(); } 

进入这些

 int Method1() => 5; void Method2() => Console.WriteLine(); 

当您从正文中抛出exception时,会产生差异:

 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' 

为什么?

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

后一个( throw new Exception() )是一个语句 – 在这种情况下: ThrowStatement

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

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


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

  • 表达与声明 [Stackoverflow]
  • 语句,表达式和运算符 [MSDN]

为什么它也不接受陈述?

我只能在这里猜测,但我认为这是因为这会打开太多的副作用,只是为了能够抛出exception。 我不相信表达式和语句在inheritance中有共同的祖先,所以会有很多代码重复。 最后,我认为它归结为简直不值得麻烦,尽管它在某种程度上是有道理的。

当你将一个简单的表达式写成一个方法体的一部分时,它实际上被包装在ExpressionStatementSyntax – 是的,两者结合在一起! 这允许它与方法的Body属性下的其他语句组合在一起。 在引擎盖下,他们必须展开它并从中提取表达式。 这反过来可以用于表达式身体成员,因为此时你只剩下一个表达式而不再是一个语句。

然而,一个重要的注意事项是,return语句是一个声明。 更具体地说是ReturnStatementSyntax 。 他们必须已经明确地处理了这个并且应用了编译器魔术,虽然这确实提出了一个问题:为什么不对ThrowStatementSyntax做同样的ThrowStatementSyntax

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

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

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

throwreturn语句都是有效的方法结束。 但是当你省略它们时,没有什么可以区分两者 – ergo:你永远不会知道是返回还是抛出新创建的exception对象。

我应该从中拿走什么?

一个表达体的成员正如名字所说的那样:一个在其体内只有一个表达式的成员。 这意味着您必须了解表达式的确切构成。 仅仅因为它是一个“陈述”并不能使它成为一种表达。

这个function将在C#7中出现。 来自https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

在表达式中间抛出exception很容易:只需调用一个为您执行此操作的方法! 但是在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已发布:

C#7中的新function – 抛出表达式 。

C#7.0中的新function 。

不是为什么而是解决方法的答案:

 void Method3() => ThrowNotImplemented(); int Method4() => ThrowNotImplemented(); private static void ThrowNotImplemented() { throw new NotImplementedException(); } private static T ThrowNotImplemented() { throw new NotImplementedException(); } 

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

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

这样你仍然可以在表达式bodied成员的一行中抛出exception!

可能有充分的理由不这样做。 但我认为这是一个设计缺陷,表达身体的成员被限制在这样的表达式,可以在这个例子中解决。

虽然它是一个旧线程,但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();