Подтвердить что ты не робот

Почему я не могу исключать исключения из элемента с выраженным выражением?

Использование эксплицированных элементов позволяет вам определить тело метода или свойства как одно выражение без ключевого слова 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' 

Почему?

4b9b3361

Ответ 1

Это происходит потому, что первые два фрагмента кода (5 и Console.WriteLine) являются выражениями. Более конкретно, это соответственно NumericLiteralExpression и InvocationExpression.

Последний (throw new Exception()) - это утверждение - в этом случае: ThrowStatement.

Если вы посмотрите на SDK Roslyn, вы заметите, что объект MethodDeclarationSyntax имеет свойство ExpressionBody типа ArrowExpressionClauseSyntax, которое, в свою очередь, имеет свойство типа ExpressionSyntax. Это должно сделать очевидным, что только выражения принимаются в члене с выражением.

Если вы посмотрите на последний пример кода, вы заметите, что он состоит из ThrowStatementSyntax, который, в свою очередь, имеет свойство ExpressionSyntax. В нашем случае мы заполняем это с помощью объекта ObjectCreationExpressionSyntax.


Какая разница между выражением и выражением?

Почему он также не принимает выражения?

Я могу только догадываться здесь, но я бы предположил, потому что это откроет слишком много побочных эффектов, чтобы просто выбросить исключение. Я не верю, что выражение и утверждение имеют общий предок в наследовании, поэтому было бы много дублирования кода. В конце концов, я предполагаю, что это сводилось к тому, чтобы просто не стоило быть суетным, хотя это имеет смысл в некотором роде.

Когда вы пишете простое выражение как часть тела метода, которое фактически обернуто под ExpressionStatementSyntax - да, оба объединены! Это позволяет группировать его вместе с другими операторами в соответствии с свойством Body метода. Под капотом они должны развернуть это и извлечь из него выражение. Это, в свою очередь, может быть использовано для элемента с выраженным выражением, потому что в этот момент вы остаетесь с выражением и больше не являетесь выражением.

Однако одно важное замечание состоит в том, что оператор return - это оператор. Более конкретно a ReturnStatementSyntax. Они, должно быть, обработали это явно и применили магию компилятора, хотя это и ставит вопрос: почему бы не сделать то же самое для ThrowStatementSyntax?

Рассмотрим следующий сценарий: внезапно принимаются утверждения throw. Однако, поскольку элемент с выраженным выражением может иметь только выражения в качестве своего тела (duh), это означает, что вам нужно опустить ключевое слово throw, а вместо него оставить new Exception(). Как вы собираетесь различать намерение оператора return и оператора throw?

Не было бы никакой разницы между вариацией выражения двух этих методов:

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

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

Оба оператора a throw и a return являются допустимыми окончаниями метода. Однако, когда вы их опускаете, нет ничего, что отличает два - эрго: вы никогда не узнаете, следует ли возвращать или бросать этот вновь созданный объект исключения.

Что я должен отнять у этого?

Элемент, выраженный в выражении, точно так же, как имя говорит, что это: член с только выражением в его теле. Это означает, что вы должны знать, что именно составляет выражение. Просто потому, что один "оператор" не делает его выражением.

Ответ 2

Эта функция подходит для С# 7. Из https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

В середине выражения легко исключить исключение: просто вызовите метод, который сделает это за вас! Но в С# 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();
}

Edit:

Обновление этого вопроса для добавления ссылок на более новую информацию о том, как throw теперь можно использовать в качестве выражения в выражениях-членах, тройных выражениях и коллаборационных выражениях, теперь, когда выпущен С# 7:

Что нового в С# 7 - Выразить выражения.

Новые функции в С# 7.0.

Ответ 3

Как пояснил Jeroen Vannevel, мы можем использовать выражения только для выраженных членов. Я бы не рекомендовал этого, но вы всегда можете инкапсулировать свой (сложный) код в выражении, написав выражение лямбда, отбрасывая его соответствующему типу и вызывать его.

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

Таким образом, вы все равно можете генерировать исключение в одной строке в выраженном члене!

Возможно, есть веские причины не делать этого. Но я думаю, что это недостаток дизайна, который выражает членов, ограниченных выражениями, подобными этому, и может работать как в этом примере.

Ответ 4

Не ответ на вопрос, почему, но обходной путь:

void Method3() => ThrowNotImplemented();

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

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

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