Why can't I throw exceptions from an expression-bodied member?

前端 未结 5 1185
无人及你
无人及你 2021-01-07 16:51

Using expression-bodied members allows you to define the body of a method or property as a single expression without a return keyword (should it return something).

F

相关标签:
5条回答
  • 2021-01-07 17:21

    This happens because the first two code snippets (5 and Console.WriteLine) are expressions. More specifically these are respectively NumericLiteralExpression and InvocationExpression.

    The latter one (throw new Exception()) is a statement -- in this case: ThrowStatement.

    If you look at the Roslyn SDK you'll notice that a MethodDeclarationSyntax object has a property ExpressionBody of type ArrowExpressionClauseSyntax which in turn has a property of type ExpressionSyntax. This should make it obvious that only expressions are accepted in an expression-bodied member.

    If you look at the last code sample, you'll notice that it consists of a ThrowStatementSyntax which has in turn an ExpressionSyntax property. In our case we're filling that with an ObjectCreationExpressionSyntax object.


    What's the difference between an expression and a statement?

    • Expression Versus Statement [Stackoverflow]
    • Statements, Expressions, and Operators [MSDN]

    Why doesn't it accept statements as well?

    I can only guess here but I would assume it's because that would open up way too many side-effects just to be able to throw an exception. I don't believe an expression and a statement have a common ancestor in the inheritance so there'd be a lot of code duplication. In the end I assume it boiled down to simply not worth being the hassle, even though it makes sense in a way.

    When you write a simple expression as part of a method body that gets in fact wrapped under a ExpressionStatementSyntax -- yes, both combined! This allows it to be grouped together with other statements under the Body property of the method. Under the hood, they must be unrolling this and extracting the expression from it. This in turn can be used for the expression-bodied member because at this point you're left with just an expression and no longer a statement.

    One important note here however is the fact that a return statement is.. a statement. More specifically a ReturnStatementSyntax. They must have handled this explicitly and applied compiler magic though that does beg the question: why not do the same for ThrowStatementSyntax?

    Consider the following scenario: suddenly, throw statements are accepted as well. However since an expression-bodied member can only have expressions as its body (duh) that means you have to omit the throw keyword and instead are left with new Exception(). How are you going to distinguish between intending a return statement and a throw statement?

    There would be no difference between the expression-bodied variation of these two methods:

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

    Both a throw and a return statement are valid method-endings. However when you omit them there is nothing that distinguishes the two -- ergo: you would never know whether to return or to throw that newly created exception object.

    What should I take away from this?

    An expression-bodied member is exactly as the name says it is: a member with only an expression in its body. This means that you have to be aware of what exactly constitutes an expression. Just because it's one "statement" doesn't make it an expression.

    0 讨论(0)
  • 2021-01-07 17:29

    As Jeroen Vannevel explained, we can only use expressions for expression bodied members. I would not recommend this, but you can always encapsulate your (complex) code in an expression by writing a lambda expression casting it to an appropriate type and Invoke it.

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

    This way you can still throw an exception in one line in an expression bodied member!

    Probably there are good reasons not to do this. But I think that it is a design flaw that expression bodied members are restricted to expressions like this and can be worked around like in this example.

    0 讨论(0)
  • 2021-01-07 17:35

    Although its a old thread, but C# now supports throw expressions which were added in C# 7.

    Previously,

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

    No more. Now, as Null coalescing operator:

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

    As Conditional Operator:

    It is also possible in the Conditional Operator as well.

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

    Expression bodied member:

    public string GetPhoneNumber () => throw new NotImplementedException();
    
    0 讨论(0)
  • 2021-01-07 17:39

    Not an answer about why but a workaround:

    void Method3() => ThrowNotImplemented();
    
    int Method4() => ThrowNotImplemented<int>();
    
    private static void ThrowNotImplemented()
    {
        throw new NotImplementedException();
    }
    
    private static T ThrowNotImplemented<T>()
    {
        throw new NotImplementedException();
    }
    
    0 讨论(0)
  • 2021-01-07 17:45

    This feature is coming in C#7. From https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

    It is easy to throw an exception in the middle of an expression: just call a method that does it for you! But in C# 7.0 we are directly allowing throw as an expression in certain places:

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

    Edit:

    Updating this question to add links to newer info around how throw can now be used as an expression in expression-bodied members, ternary expressions and null-coalescing expressions, now that C# 7 is released:

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

    New Features in C# 7.0.

    0 讨论(0)
提交回复
热议问题