Depois de migrar meu projeto do VS2013 para o VS2015, o projeto não é mais compilado. Um erro de compilação ocorre na seguinte instrução LINQ:
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();
}
O compilador retorna um erro:
Erro CS0165 Uso de variável local não atribuída 'b'
O que causa esse problema? É possível corrigir isso por meio de uma configuração do compilador?
b
depois de atribuí-lo por meio de umout
parâmetro.out
argumentos. IssoTryParse
retornaria um valor anulável (ou equivalente).where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= b
parece muito melhordecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;
. Eu já abriu um bug Roslyn levantar esta.Respostas:
Parece um bug do compilador para mim. Pelo menos, sim. Embora as expressões
decimal.TryParse(v, out a)
edecimal.TryParse(v, out b)
sejam avaliadas dinamicamente, eu esperava que o compilador ainda entendesse que, no momento em que atingea <= b
, ambosa
eb
estão definitivamente atribuídos. Mesmo com as estranhezas que você pode descobrir na digitação dinâmica, eu esperaria apenas avaliara <= b
depois de avaliar ambas asTryParse
chamadas.No entanto, verifica-se que por meio de operador e conversão complicada, é inteiramente viável ter uma expressão
A && B && C
que avaliaA
eC
mas nãoB
- se você for astuto o suficiente. Veja o relatório de bug de Roslyn para ver o exemplo engenhoso de Neal Gafter.Fazer isso funcionar
dynamic
é ainda mais difícil - a semântica envolvida quando os operandos são dinâmicos são mais difíceis de descrever, porque para realizar a resolução de sobrecarga, você precisa avaliar operandos para descobrir quais tipos estão envolvidos, o que pode ser contra-intuitivo. No entanto, novamente Neal veio com um exemplo que mostra que o erro do compilador é necessário ... isso não é um bug, é uma correção de bug . Muitos elogios a Neal por provar isso.Não, mas existem alternativas que evitam o erro.
Em primeiro lugar, você pode impedir que seja dinâmico - se você sabe que só usará strings, pode usar
IEnumerable<string>
ou dar à variável de intervalov
um tipo destring
(ou sejafrom string v in array
). Essa seria minha opção preferida.Se você realmente precisa mantê-lo dinâmico, basta fornecer
b
um valor para começar:Isso não fará mal nenhum - sabemos que na verdade sua avaliação dinâmica não fará nada maluco, então você ainda acabará atribuindo um valor a
b
antes de usá-lo, tornando o valor inicial irrelevante.Além disso, parece que adicionar parênteses também funciona:
Isso muda o ponto em que várias peças de resolução de sobrecarga são acionadas e deixa o compilador feliz.
Ainda existe um problema - as regras da especificação sobre atribuição definitiva com o
&&
operador precisam ser esclarecidas para afirmar que elas só se aplicam quando o&&
operador está sendo usado em sua implementação "regular" com doisbool
operandos. Vou tentar garantir que isso seja corrigido para o próximo padrão ECMA.fonte
IEnumerable<string>
ou adicionar colchetes funcionou para mim. Agora o compilador constrói sem erros.decimal a, b = 0m;
pode remover o erro, mas entãoa <= b
seria sempre usar0m
, já que o valor fora não foi calculado ainda.decimal? TryParseDecimal(string txt)
pode ser uma solução tambémb
que não pode ser atribuído"; Eu sei que esse raciocínio é inválido, mas explica porque os parênteses o corrigem ...Isso parece ser um bug, ou pelo menos uma regressão, no compilador Roslyn. O seguinte bug foi registrado para rastreá-lo:
Nesse ínterim, a excelente resposta de Jon tem algumas alternativas.
fonte
Já que fui educado tanto no relatório de bug, tentarei explicar isso sozinho.
Imagine
T
é algum tipo definido pelo usuário com uma conversão implícitabool
que alterna entrefalse
etrue
, começando comfalse
. Até onde o compilador sabe, odynamic
primeiro argumento para o primeiro&&
pode ser avaliado como esse tipo, portanto, ele deve ser pessimista.Se, então, ele permitir a compilação do código, isso pode acontecer:
&&
, ele faz o seguinte:T
- implicitamente lançado parabool
.false
, então não precisamos avaliar o segundo argumento.&&
avaliação como o primeiro argumento. (Não, nãofalse
, por algum motivo.)&&
, ele faz o seguinte:T
- implicitamente lançado parabool
.true
, então avalie o segundo argumento.b
não foi atribuído.Em termos específicos, em suma, existem regras especiais de "atribuição definida" que nos permitem dizer não apenas se uma variável é "definitivamente atribuída" ou "não definitivamente atribuída", mas também se ela é "definitivamente atribuída após a
false
declaração" ou "definitivamente atribuído apóstrue
declaração ".Eles existem para que, ao lidar com
&&
e||
(e!
e??
e?:
), o compilador possa examinar se as variáveis podem ser atribuídas em ramos específicos de uma expressão booleana complexa.No entanto, eles só funcionam enquanto os tipos das expressões permanecem booleanos . Quando parte da expressão é
dynamic
(ou um tipo estático não booleano), não podemos mais dizer com segurança que a expressão étrue
oufalse
- na próxima vez em que a lançarmosbool
para decidir qual ramificação tomar, ela pode ter mudado de ideia.Atualização: isso agora foi resolvido e documentado :
fonte
out
a um método que tenharef
. Ele o fará de bom grado e atribuirá variáveis, sem alterar o valor.false
/true
em oposição ao operador de conversão implícito? Localmente, ele chamaráimplicit operator bool
no primeiro argumento e, em seguida, no segundo operando,operator false
no primeiro operando e novamenteimplicit operator bool
no primeiro operando . Isso não faz sentido para mim, o primeiro operando deve basicamente resumir-se a um booleano uma vez, não?dynamic
acorrentado&&
? Eu vi basicamente ir (1) avaliar o primeiro argumento (2) usar elenco implícito para ver se consigo curto-circuito (3) Não posso, então analise o segundo argumento (4) agora que conheço os dois tipos, posso ver o melhor&&
é um&
operador de chamada definido pelo usuário (5)false
no primeiro argumento para ver se consigo curto-circuito (6) posso (porquefalse
eimplicit bool
discordo), então o resultado é o primeiro argumento ... e então o próximo&&
, (7) use elenco implícito para ver se consigo curto-circuito (novamente).Este não é um bug. Consulte https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 para um exemplo de como uma expressão dinâmica desta forma pode deixar essa variável de fora sem atribuição.
fonte