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

时间:2015-08-23 20:48:04

标签: c# roslyn c#-6.0

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

例如,它会变成这些

autoforms = []
part_formsets = []
autos = Auto.objects.all()
PartFormSet = inlineformset_factory(Auto, Part, fields=('field1', 'field2'))
for auto in autos:
    autoform = AutoForm(instance=auto)
    autoforms.append(autoform)
    part_formset = PartFormSet(instance=auto)
    part_formsets.append(part_formset)
c = {'autoforms': autoforms, 'part_formsets': part_formsets}
return render(request, 'some.html', c)

进入这些

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();

为什么?

5 个答案:

答案 0 :(得分:35)

这是因为前两个代码段(5Console.WriteLine)是表达式。更具体地说,这些分别是NumericLiteralExpressionInvocationExpression

后一个(throw new Exception())是一个陈述 - 在这种情况下:ThrowStatement

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

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

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

为什么它也不接受陈述?

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

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

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

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

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

return

import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import com.amazonaws.services.lambda.runtime.Context; public class MyHandler { public void handler(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int letter; while((letter = inputStream.read()) != -1) { baos.write(letter); } //Send the contents of baos to a JSON deserializer ... } } 和{{1}}语句都是有效的方法结尾。但是当你省略它们时,没有什么可以区分两者 - ergo:你永远不会知道是返回还是抛出新创建的异常对象。

我应该从中拿走什么?

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

答案 1 :(得分:15)

此功能将在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

答案 2 :(得分:1)

不是解决原因的答案,而是解决方法:

void Method3() => ThrowNotImplemented();

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

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

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

答案 3 :(得分:1)

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

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

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

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

答案 4 :(得分:0)

尽管它是一个旧线程,但是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");}

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

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();