Защо не мога да хвърля изключения от член с изразно тяло?

Използването на членове с тяло на израз ви позволява да дефинирате тялото на метод или свойство като един израз без ключова дума за връщане (ако върне нещо).

Например превръща тези

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
хубаво. но метод, който хвърля само изключение, е Exception thrower! и няма полза, тъй като можете директно да хвърлите изключение :)   -  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 на метода. Под капака сигурно развиват това и извличат изражението от него. Това от своя страна може да се използва за член с тяло на израз, тъй като в този момент оставате само с израз, а не с израз.

Една важна бележка тук обаче е фактът, че изразът за връщане е... израз. По-точно ReturnStatementSyntax. Сигурно са се справили с това изрично и са приложили магия на компилатора, въпреки че това повдига въпроса: защо да не направят същото за ThrowStatementSyntax?

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

Няма да има разлика между вариацията на изразното тяло на тези два метода:

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

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

Както throw, така и return израз са валидни окончания на метода. Въпреки това, когато ги пропуснете, няма нищо, което да ги отличава - ето защо: никога няма да знаете дали да върнете или да хвърлите този новосъздаден обект на изключение.

Какво трябва да взема от това?

Член с изражение е точно както казва името: член само с изражение в тялото. Това означава, че трябва да сте наясно какво точно представлява израз. Само защото това е едно „изявление“ не го прави израз.

person Jeroen Vannevel    schedule 23.08.2015
comment
Имаше разговори на github.com/dotnet/roslyn/issues за изрази за хвърляне и такива изрази за превръщане в 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 - Изхвърляне на изрази.

Нови функции в 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 Изразът за хвърляне е изрично само разрешен в операндите на условния оператор ?:, като втори оператор на оператора null coalescing ?? и като тяло на израз в ламбда или методи. - person poke; 22.06.2017
comment
Иска ми се да можем да направим това с изявлението за връщане. 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# вече поддържа изрази за хвърляне, които бяха добавени в 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");}

Няма повече. Сега, като Null coalescing operator:

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