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

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

Почему?


person Jeroen Vannevel    schedule 23.08.2015    source источник
comment
красивый. однако метод, который генерирует только исключение, является генератором исключений! и бесполезно, так как вы можете напрямую генерировать исключение :)   -  person M.kazem Akhgary    schedule 24.08.2015
comment
@M.kazemAkhgary: распространенный сценарий, в котором я ожидаю, что это еще не реализованные методы, которые, очевидно, выдают NotImplementedException   -  person Jeroen Vannevel    schedule 24.08.2015


Ответы (5)


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

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

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

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


В чем разница между выражением и оператором?

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

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

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

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

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

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

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

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

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

Что я должен вынести из этого?

Член с телом-выражением в точности соответствует названию: член, содержащий только выражение в своем теле. Это означает, что вы должны знать, что именно представляет собой выражение. То, что это одно «утверждение», не делает его выражением.

person Jeroen Vannevel    schedule 23.08.2015
comment
На github.com/dotnet/roslyn/issues были разговоры о операторах throw и подобных выражениях в C# 7. Однако я не помню какой-либо конкретной проблемы. - person Paulo Morgado; 24.08.2015
comment
Было бы неплохо увидеть рабочий фрагмент в этом ответе. - person Stefan Steinegger; 24.08.2015
comment
@StefanSteinegger нет рабочего фрагмента - создание исключения из члена с телом выражения просто не поддерживается - person Jeroen Vannevel; 24.08.2015
comment
@StefanSteinegger: Действительно, вы должны поместить выражение в элемент с телом выражения. Синтаксис просто предназначен для быстрого доступа. Если вы находитесь за пределами этого ярлыка, просто используйте обычный синтаксис. - person Jason Malinowski; 24.08.2015
comment
@JasonMalinowski: Я знаю, я просто сказал, что было бы неплохо увидеть это. - person Stefan Steinegger; 25.08.2015

Эта функция появится в 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:

Что нового в C# 7 — выражения Throw< /а>.

Новые функции в C# 7.0< /а>.

person Curtis Lusmore    schedule 25.09.2016
comment
Проблема в том, что это не работает: false || throw new Exception("what about my throw is an expression?") - person binki; 08.04.2017
comment
Какого поведения вы ожидаете от этого фрагмента? - person Curtis Lusmore; 10.04.2017
comment
Такое же поведение, как (object)null ?? throw new ArgumentNullException() - person binki; 10.04.2017
comment
@binki Выражение throw явно разрешено только в операндах условного оператора ?:, в качестве второго оператора объединяющего null оператора ?? и в качестве тела выражения в лямбда-выражениях или методах. - person poke; 22.06.2017
comment
Хотелось бы, чтобы мы могли сделать это с помощью оператора return. var something = GetSomethingOrDefault() ?? return; - person Dave Cousineau; 12.04.2018

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

void Method3() => ThrowNotImplemented();

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

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

private static T ThrowNotImplemented<T>()
{
    throw new NotImplementedException();
}
person Johan Larsson    schedule 25.09.2016

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

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

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

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

person Felix Keil    schedule 12.10.2016

Хотя это старый поток, C# теперь поддерживает выражения throw, которые были добавлены в C# 7.

Ранее,

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();
person Gauravsa    schedule 13.08.2018