web-dev-qa-db-fra.com

Pourquoi ne puis-je pas lancer d'exceptions d'un membre avec une expression?

L'utilisation de membres dotés d'une expression vous permet de définir le corps d'une méthode ou d'une propriété comme une expression unique sans mot-clé return (si elle renvoie quelque chose).

Par exemple, il tourne ces

int Method1()
{
    return 5;
}

void Method2()
{
    Console.WriteLine();
}

dans ces

int Method1() => 5;

void Method2() => Console.WriteLine();

Une différence entre en jeu lorsque vous lancez une exception du corps:

void Method3()
{
    throw new Exception();
}

Toutefois, les éléments suivants ne seront pas compilés:

void Method3() => throw new Exception();

avec les messages suivants:

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' 

Pourquoi?

28
Jeroen Vannevel

Cela est dû au fait que les deux premiers extraits de code (5 et Console.WriteLine) sont des expressions. Plus spécifiquement, il s'agit respectivement de NumericLiteralExpression et InvocationExpression.

La dernière (throw new Exception()) est une déclaration - dans ce cas: ThrowStatement.

Si vous examinez le SDK de Roslyn, vous remarquerez qu'un objet MethodDeclarationSyntax a une propriété ExpressionBody de type ArrowExpressionClauseSyntax qui, à son tour, a une propriété de type ExpressionSyntax. Cela devrait rendre évident que seules les expressions sont acceptées dans un membre contenant une expression.

Si vous examinez le dernier exemple de code, vous remarquerez qu'il se compose d'une ThrowStatementSyntax qui possède à son tour une propriété ExpressionSyntax. Dans notre cas, nous remplissons cela avec un objet ObjectCreationExpressionSyntax.


Quelle est la différence entre une expression et une déclaration?

Pourquoi n'accepte-t-il pas les déclarations aussi?

Je ne peux que deviner, mais je suppose que c’est parce que cela entraînerait beaucoup trop d’effets secondaires simplement pour pouvoir lancer une exception. Je ne crois pas qu'une expression et une déclaration aient un ancêtre commun dans l'héritage, il y aurait donc beaucoup de duplication de code. En fin de compte, je suppose que cela n’a tout simplement pas valu la peine d’être un problème, même si cela a un sens.

Lorsque vous écrivez une expression simple dans le cadre d'un corps de méthode qui est en fait enveloppé par une ExpressionStatementSyntax - oui, les deux sont combinés! Cela lui permet d'être regroupé avec d'autres instructions sous la propriété Body de la méthode. Sous le capot, ils doivent dérouler cela et en extraire l'expression. Ceci peut à son tour être utilisé pour le membre avec une expression car à ce stade, il ne vous reste plus qu'une expression et non plus une instruction.

Une remarque importante ici cependant est le fait qu'une déclaration de retour est une déclaration. Plus spécifiquement une ReturnStatementSyntax. Ils doivent avoir manipulé cela explicitement et appliqué la magie du compilateur, ce qui soulève la question: pourquoi ne pas faire la même chose pour ThrowStatementSyntax?

Considérez le scénario suivant: tout à coup, les instructions throw sont également acceptées. Cependant, puisqu'un membre avec une expression ne peut avoir que des expressions en tant que corps (duh), cela signifie que vous devez omettre le mot clé throw et laisser à la place la propriété new Exception(). Comment allez-vous faire la distinction entre l'intention d'utiliser une instruction return et une instruction throw?

Il n'y aurait pas de différence entre la variation de l'expression de ces deux méthodes:

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

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

Les instructions throw et return sont des terminaisons de méthodes valides. Cependant, lorsque vous les omettez, rien ne distingue les deux - ergo: vous ne saurez jamais s'il faut revenir ou jeter cet objet exception nouvellement créé.

Que devrais-je retirer de cela?

Un membre avec une expression est exactement comme son nom l'indique: un membre avec seulement une expression dans son corps. Cela signifie que vous devez être conscient de ce qui constitue exactement une expression. Le simple fait que ce soit une "déclaration" n'en fait pas une expression.

34
Jeroen Vannevel

Cette fonctionnalité arrive en C # 7. De https://blogs.msdn.Microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

Il est facile de lancer une exception au milieu d’une expression: il suffit d’appeler une méthode qui le fait pour vous! Mais en C # 7.0, nous autorisons directement throw comme expression à certains endroits:

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

Modifier:

Mise à jour de cette question pour ajouter des liens vers des informations plus récentes sur la manière dont throw peut maintenant être utilisé comme expression dans les membres dotés d'expression, les expressions ternaires et les expressions à fusion nulle, maintenant que C # 7 est publié:

Nouveautés de C # 7 - Expressions de jet .

Nouvelles fonctionnalités de C # 7.0 .

15
Curtis Lusmore

Pas une réponse sur pourquoi mais une solution de contournement:

void Method3() => ThrowNotImplemented();

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

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

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

Comme Jeroen Vannevel l'a expliqué, nous ne pouvons utiliser que des expressions pour les membres dotés d'une expression. Je ne le recommanderais pas, mais vous pouvez toujours encapsuler votre code (complexe) dans une expression en écrivant une expression lambda en le transformant en un type approprié. et l'invoquer.

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

De cette façon, vous pouvez toujours lancer une exception dans une ligne d'un membre avec une expression!

Il y a probablement de bonnes raisons de ne pas le faire. Mais je pense que le fait que les membres dotés d'une expression soient restreints à des expressions comme celle-ci est un défaut de conception, et peut être contourné comme dans cet exemple.

1
Felix Keil

Bien que ce soit un vieux fil, mais C # supporte maintenant les expressions de jet ajoutées dans C # 7. 

Précédemment,

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

Pas plus. Maintenant, en tant que Opérateur de coalescence nul:

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

En tant que Opérateur conditionnel:

C'est également possible dans l'opérateur conditionnel.

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

Membre avec corps d'expression:

public string GetPhoneNumber () => throw new NotImplementedException();
0
Gauravsa