Après avoir migré mon projet de VS2013 vers VS2015, le projet ne se construit plus. Une erreur de compilation se produit dans l'instruction LINQ suivante:
static void Main(string[] args)
{
decimal a, b;
IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
var result = (from v in array
where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
orderby decimal.Parse(v)
select v).ToArray();
}
Le compilateur renvoie une erreur:
Erreur CS0165 Utilisation de la variable locale non affectée 'b'
Qu'est-ce qui cause ce problème? Est-il possible de le corriger via un paramètre de compilation?
Qu'est-ce qui cause ce problème?
Cela ressemble à un bug de compilation pour moi. Du moins, c'était le cas. Bien que les expressions decimal.TryParse(v, out a)
et decimal.TryParse(v, out b)
soient évaluées dynamiquement, je attendais que le compilateur comprenne toujours que par le fois qu'il atteint a <= b
, a
et b
sont définitivement attribués. Même avec les bizarreries que vous pouvez trouver en typage dynamique, je m'attends à n'évaluer que a <= b
Après avoir évalué les deux appels TryParse
.
Cependant, il s'avère que grâce à l'opérateur et à la conversion délicate, il est tout à fait possible d'avoir une expression A && B && C
Qui évalue A
et C
mais pas B
- si vous êtes assez rusé. Voir le rapport de bogue de Roslyn pour l'exemple ingénieux de Neal Gafter.
Faire fonctionner cela avec dynamic
est encore plus difficile - la sémantique impliquée lorsque les opérandes sont dynamiques est plus difficile à décrire, car pour effectuer une résolution de surcharge, vous devez évaluer les opérandes pour savoir quels types sont impliqués, ce qui peut être contre-intuitif. Cependant, encore une fois, Neal a trouvé un exemple qui montre que l'erreur du compilateur est requise ... ce n'est pas un bug, c'est un bug corrigé . D'énormes quantités de félicitations à Neal pour le prouver.
Est-il possible de le corriger via les paramètres du compilateur?
Non, mais il existe des alternatives qui évitent l'erreur.
Tout d'abord, vous pouvez l'empêcher d'être dynamique - si vous savez que vous n'utiliserez que des chaînes, vous pouvez utiliser IEnumerable<string>
ou donne à la variable de plage v
un type de string
(c'est-à-dire from string v in array
). Ce serait mon option préférée.
Si vous avez vraiment besoin de le garder dynamique, donnez simplement à b
une valeur pour commencer:
decimal a, b = 0m;
Cela ne fera aucun mal - nous savons que en fait votre évaluation dynamique ne fera rien de fou, vous finirez donc par assigner une valeur à b
avant de l'utiliser, rendant la valeur initiale non pertinente.
De plus, il semble que l'ajout de parenthèses fonctionne également:
where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)
Cela change le point auquel divers éléments de résolution de surcharge sont déclenchés et rend le compilateur heureux.
Il y a un problème toujours en suspens - les règles de la spécification sur l'affectation définitive avec l'opérateur &&
Doivent être clarifiées pour indiquer qu'elles s'appliquent uniquement lorsque l'opérateur &&
est utilisé dans son implémentation "régulière" avec deux opérandes bool
. Je vais essayer de m'assurer que cela est corrigé pour la prochaine norme ECMA.
Cela semble être un bogue, ou au moins une régression, dans le compilateur Roslyn. Le bogue suivant a été déposé pour le suivre:
En attendant, Jon's excellente réponse a quelques contournements.
Depuis que j'ai été si durement scolarisé dans le rapport de bogue, je vais essayer de l'expliquer moi-même.
Imaginez que T
est un type défini par l'utilisateur avec un transtypage implicite en bool
qui alterne entre false
et true
, en commençant par false
. Pour autant que le compilateur le sache, le premier argument dynamic
au premier &&
pourrait correspondre à ce type, il doit donc être pessimiste.
Si, alors, il laissait le code se compiler, cela pourrait arriver:
&&
, il fait ce qui suit: T
- transtypez implicitement en bool
.false
, nous n'avons donc pas besoin d'évaluer le deuxième argument.&&
évalue comme premier argument. (Non, pas false
, pour une raison quelconque.)&&
, il fait ce qui suit: T
- transtypez implicitement en bool
.true
, alors évaluez le deuxième argument.b
n'est pas attribué.En termes de spécification, en bref, il existe des règles spéciales "d'affectation définitive" qui nous permettent de dire non seulement si une variable est "définitivement affectée" ou "non définitivement affectée", mais aussi si elle est "définitivement affectée après false
instruction "ou" définitivement attribué après true
instruction ".
Ceux-ci existent de sorte que lorsque vous traitez avec &&
et ||
(et !
et ??
et ?:
) le compilateur peut examiner si des variables peuvent être affectées dans des branches particulières d'une expression booléenne complexe.
Cependant, ceux-ci ne fonctionnent que tandis que les types d'expressions restent booléens . Lorsqu'une partie de l'expression est dynamic
(ou un type statique non booléen), nous ne pouvons plus dire de manière fiable que l'expression est true
ou false
- la prochaine fois que nous lancerons à bool
pour décider quelle branche prendre, il a peut-être changé d'avis.
Mise à jour: cela a maintenant été résol et documenté :
Les règles d'affectation définies implémentées par les compilateurs précédents pour les expressions dynamiques ont permis certains cas de code pouvant entraîner la lecture de variables qui ne sont pas définitivement attribuées. Voir https://github.com/dotnet/roslyn/issues/4509 pour un rapport à ce sujet.
...
En raison de cette possibilité, le compilateur ne doit pas autoriser la compilation de ce programme si val n'a pas de valeur initiale. Les versions précédentes du compilateur (avant VS2015) permettaient à ce programme de compiler même si val n'a pas de valeur initiale. Roslyn diagnostique maintenant cette tentative de lecture d'une variable éventuellement non initialisée.
Ce n'est pas un bug. Voir https://github.com/dotnet/roslyn/issues/4509#issuecomment-13087271 pour un exemple de la façon dont une expression dynamique de ce formulaire peut laisser une telle variable out non affectée.