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 attribuée 'b'
Qu'est-ce qui cause ce problème? Est-il possible de le réparer via un paramètre de compilateur?
b
qu'après avoir été affecté via unout
paramètre.out
arguments. Serait-ceTryParse
retourné une valeur nullable (ou équivalent).where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= b
air beaucoup mieuxdecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;
. J'ai ouvert un bug de Roslyn soulevant ceci.Réponses:
Cela ressemble à un bogue du compilateur. Du moins, ça l'a fait. Bien que les expressions
decimal.TryParse(v, out a)
etdecimal.TryParse(v, out b)
soient évaluées dynamiquement, je m'attendais à ce que le compilateur comprenne encore qu'au moment où il atteinta <= b
, les deuxa
etb
sont définitivement attribués. Même avec les bizarreries que vous pouvez créer dans le typage dynamique, je m'attendrais à n'évaluera <= b
qu'après avoir évalué les deuxTryParse
appels.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 évalueA
etC
mais pasB
- si vous êtes assez rusé. Voir le rapport de bogue de Roslyn pour l'exemple ingénieux de Neal Gafter.Faire ce travail
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 est venu avec un exemple qui montre que l'erreur du compilateur est nécessaire ... ce n'est pas un bug, il est un bug fix . D'énormes félicitations à Neal pour l'avoir prouvé.Non, mais il existe des alternatives qui évitent l'erreur.
Premièrement, vous pouvez l'empêcher d'être dynamique - si vous savez que vous n'utiliserez jamais que des chaînes, vous pouvez alors utiliser
IEnumerable<string>
ou donner à la variable rangev
un type destring
(iefrom 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:Cela ne fera aucun mal - nous savons qu'en fait votre évaluation dynamique ne fera rien de fou, vous finirez donc par attribuer une valeur à
b
avant de l'utiliser, ce qui rendra la valeur initiale non pertinente.De plus, il semble que l'ajout de parenthèses fonctionne aussi:
Cela change le moment où les différentes parties de la résolution de surcharge sont déclenchées et rend le compilateur heureux.
Il reste un problème à résoudre - les règles de la spécification sur l'affectation définitive avec l'
&&
opérateur doivent être clarifiées pour indiquer qu'elles ne s'appliquent que lorsque l'&&
opérateur est utilisé dans son implémentation "régulière" avec deuxbool
opérandes. Je vais essayer de m'assurer que cela est corrigé pour la prochaine norme ECMA.la source
IEnumerable<string>
ou l'ajout de parenthèses a fonctionné pour moi. Maintenant, le compilateur se construit sans erreur.decimal a, b = 0m;
pourrait supprimer l'erreur, maisa <= b
utiliserait toujours0m
, car la valeur de sortie n'a pas encore été calculée.decimal? TryParseDecimal(string txt)
peut être une solution aussib
pourrait ne pas être attribué"; Je sais que c'est un raisonnement invalide, mais cela explique pourquoi les parenthèses leCela semble être un bogue, ou du moins une régression, dans le compilateur Roslyn. Le bogue suivant a été déposé pour le suivre:
En attendant, l' excellente réponse de Jon a quelques solutions.
la source
Depuis que j'ai été tellement instruit dans le rapport de bogue, je vais essayer de l'expliquer moi-même.
Imagine
T
est un type défini par l'utilisateur avec un cast implicitebool
qui alterne entrefalse
ettrue
, en commençant parfalse
. Pour autant que le compilateur le sache, ledynamic
premier argument du premier&&
pourrait être évalué à ce type, il doit donc être pessimiste.Si, alors, il laisse le code se compiler, cela peut arriver:
&&
, il effectue les opérations suivantes:T
- implicitement jeté surbool
.false
, donc nous n'avons pas besoin d'évaluer le deuxième argument.&&
évaluation comme premier argument. (Non, nonfalse
, pour une raison quelconque.)&&
, il effectue les opérations suivantes:T
- implicitement jeté surbool
.true
, alors évaluez le deuxième argument.b
n'est pas assigné.En termes de spécifications, en bref, il existe des règles spéciales "d'affectation définie" 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 l'
false
instruction" ou "définitivement attribué après latrue
déclaration ".Celles-ci existent pour que, lorsqu'il traite avec
&&
and||
(et!
et??
et?:
), le compilateur puisse 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 tant que les types des 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 esttrue
oufalse
- la prochaine fois que nous la castonsbool
pour décider quelle branche prendre, elle peut avoir changé d'avis.Mise à jour: ceci est maintenant résolu et documenté :
la source
out
à une méthode qui aref
. Il le fera avec plaisir, et il affectera des variables, sans changer la valeur.false
/true
par opposition à l'opérateur de conversion implicite? Localement, il appelleraimplicit operator bool
le premier argument, puis invoquera le deuxième opérande, appeleraoperator false
le premier opérande, suivi parimplicit operator bool
le premier opérande à nouveau . Cela n'a pas de sens pour moi, le premier opérande devrait essentiellement se résumer à un booléen une fois, non?dynamic
enchaîné&&
? Je l'ai vu en gros aller (1) évaluer le premier argument (2) utiliser le cast implicite pour voir si je peux court-circuiter (3) Je ne peux pas, alors évaluez le deuxième argument (4) maintenant je connais les deux types, je peut voir le meilleur&&
est un&
opérateur d'appel défini par l'utilisateur (5)false
sur le premier argument pour voir si je peux court-circuiter (6) Je peux (parce quefalse
etimplicit bool
pas d'accord), donc le résultat est le premier argument ... puis le suivant&&
, (7) utiliser le cast implicite pour voir si je peux (à nouveau) court-circuiter.Ce n'est pas un bug. Voir https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 pour un exemple de la façon dont une expression dynamique de cette forme peut laisser une telle variable non attribuée.
la source