2017-07-05 7 views
3

En C# 7, il est possible de soulever une exception dans une expression:Quel est le type de compilation de l'expression "throw" dans C# 7?

int n = list != null ? list.Count : throw new NullReferenceException("list"); 

Dans cette position, le jet expression peut substituer une expression de tout type.

Maintenant, je veux définir une fonction qui exécute une action avant de soulever une exception:

??? DoSomethingAndThrowException(Exception e) 
{ 
    MessageBox.Show("Prepare for an exception!"); 
    throw e; 
} 

Quel serait le type de retour de cette fonction, de sorte qu'il peut être utilisé dans les mêmes endroits que l'original jet expression:

int n = list != null ? list.Count : DoSomethingAndThrowException(new NullReferenceException("list")); 

il y a la possibilité de le déclarer comme méthode générique:

T DoSomethingAndThrowException<T>(Exception e) {...} 

Mais cela semble fastidieux, car le type générique n'apparaît nulle part dans le corps de la fonction. Est-ce la seule façon de le faire, ou y a-t-il un type que je ne connais pas, et qui est assignable à n'importe quel type (un type "anti-objet", pour ainsi dire)?

+5

De la [proposition] (https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/throw-expression.md): * "Un throw_expression n'a pas de type. "* (et *" Une expression_course est convertible en chaque type par une conversion implicite. ") – UnholySheep

+0

Non, C# ne correspond pas à un type" Nothing "de Scala. – Lee

+0

@UnholySheep - Merci pour le lien! –

Répondre

6

Le type dont vous parlez est connu comme the bottom type, le sous-type de tous les types, par opposition à the top type, le super-type de tous les types.

Comme vous l'avez noté, le type supérieur en C# est appelé object *. D'un autre côté, C# n'a pas le type de bas (bien qu'il y ait a proposal to add it).

Bien qu'il existe réellement un type qui a des conversions implicites à tout autre type: dynamic. Cela signifie que si vous définissez le type de retour DoSomethingAndThrowException sur dynamic, votre code sera compilé. Mais je ne pense pas que dynamic soit un bon choix ici, car c'est trop contagieux. Par exemple, si vous avez utilisé var dans votre relevé (var n = list != null ? list.Count : DoSomethingAndThrowException(new NullReferenceException("list"));), le type de n sera dynamic, avec tous les bagages que vous apporterez.

Je pense que cela signifie que votre conclusion est correcte: les génériques sont votre meilleure option.

* Techniquement, object n'est pas le type supérieur, car object n'est pas le supertype des types de pointeurs. Mais je pense que c'est une distinction qui n'a pas beaucoup d'importance.

+0

(Pas que je sois le downvoter.) Les conversions autour de 'dynamic' sont compliquées. Ce n'est pas qu'il y a une conversion du type 'dynamic' vers un autre type de non-pointeur; c'est qu'il y a une conversion de n'importe quelle expression avec le type 'dynamic' vers n'importe quel autre type de non-pointeur. Il y a quelques endroits où la différence entre une conversion d'une expression et une conversion d'un type est importante. –

+0

@svick - Merci pour le lien vers la proposition, je serais très favorable à la fourniture d'un tel type. Je pense aussi que le nom proposé _Never_ est très approprié: les expressions de ce type _never_ evaluate, et les fonctions de ce type _never_ return. Merci aussi pour l'idée de déclarer la fonction comme dynamique, même si je suis d'accord avec toi qu'elle est inférieure à l'implémentation générique, car je ne veux pas que l'expression conditionnelle soit évaluée à une valeur dynamique. –

0

L'opérateur ternaire ne compilera pas à moins que les deux expressions retournent le même type ou qu'il y ait une conversion d'un type à l'autre. Donc, peu importe le type de l'expression throw. À moins que DoSomethingAndThrowException renvoie int ou quelque chose qui peut être implicitement converti en int, il ne sera pas compilé.

L'opérateur ternaire est juste pour plus de commodité, raccourci pour une instruction if. Mais dans ce cas, ce n'est évidemment pas pratique, donc il n'y a aucun avantage à l'utiliser ou à écrire du code supplémentaire dans d'autres endroits pour que vous puissiez l'utiliser.

Ce serait plus clair:

int n; 
if(list!=null) 
    n = list.Count; 
else 
    DoSomethingAndThrowException(new NullReferenceException("list")); 

Mieux encore,

if(list==null) DoSomethingAndThrowException(new NullReferenceException("list")); 
var n = list.Count; 

Ce serait encore plus explicite et claire:

if(list==null) 
{ 
    DoSomething(); 
    throw new NullReferenceException("list"); 
} 
var n = list.Count(); 

Il peut sembler couper les cheveux, mais cas comme ceux-ci, il est souvent préférable de suivre les conventions typiques que de faire quelque chose de créatif qui fonctionne le même wa y. L'exécution est la même, mais quelqu'un qui lit le code plus tard pourrait passer dix secondes supplémentaires à essayer de comprendre ce qui se passe.

+0

"L'opérateur ternaire ne compilera pas à moins que les deux expressions ne renvoient le même type." Ce n'est pas vrai. Il doit y avoir une conversion du type d'un opérande au type de l'autre, mais ils ne doivent pas nécessairement être du même type. (Ainsi, 'DoSomethingAndThrowException' pourrait renvoyer' byte', par exemple.) –

+0

Assez juste. Édité. –

+0

Pour être précis, vous devrez également modifier la dernière phrase du premier paragraphe. (J'ai édité mon commentaire précédent - la méthode pourrait retourner 'byte' par exemple ... et si elle renvoie' object' alors l'expression conditionnelle compile, mais le résultat ne peut pas être assigné à une variable 'int'. –

1

Il existe une solution qui vous permet de conserver votre motif. Le seul changement est que vous lancez votre méthode, au lieu de lancer à l'intérieur de la méthode. Ne pas trop penser!

private static Exception DoSomethingAndReturnException(Exception exception) 
{ 
    // Do something 

    // in the end, return the exception 
    return exception; 
} 

Vous pouvez l'utiliser comme ceci:

int n = (list != null) ? list.Count : throw DoSomethingAndReturnException(new NullReferenceException("list")); 
+0

@Downvoter l'esprit expliquant le vote? – Mafii

+0

C'est clairement la bonne approche. Vous pouvez même en faire une méthode d'extension sur 'Exception' afin que la syntaxe soit très élégante. –

0

Voici court specification for the throw-expression feature.

La partie pertinente à votre question:

Les règles de type sont les suivantes:

  • A throw_expression n'a pas de type.
  • Une throw_expression est convertible en chaque type par une conversion implicite.