sábado, 17 de julho de 2010

Curto-circuito no C#!!!

A seguir está um código que recebe duas strings, e se a conversão de ambas para valores int for feita com sucesso, realiza um processamento qualquer. Se qualquer uma das conversões falhar, o processamento não é executado, e a conversão inválida deve ser escrita em um log de erros.


int int1 = -1, int2 = -1;
if (int.TryParse(string1, out int1) && int.TryParse(string2, out int2))
{
// Conversões ok - usa os valores em int1 e int2
}
else
{
// Pelos menos uma das conversões falhou - loga o erro
if(int1 == -1) LogaErro("Erro ao converter string1");
if(int2 == -1) LogaErro("Erro ao converter string2");
}


Testei o código com string1 = "XX" e string2 = "000001". E o erro que apareceu no log foi "Erro ao converter string2"!!!


[...Tire alguns momentos para revisar o código e tentar ver o que deu errado...]


O problema é que C# realiza a avaliação de uma expressão booleana em "curto-circuito" (shor-circuit boolean evaluation), ou seja, a expressão é executada até o ponto no qual se conheça seu resultado; os "pedaços" restantes da expressão não são executados. Se string1 = "XX" e string2 = "00001", ao executar a linha

if (int.TryParse(string1, out int1) && int.TryParse(string2, out int2))

o primeiro int.TryParse() falha, já que "XX" não é um número válido. E duas coisas acontecem:

Coisa 1: Como o primeiro int.TryParse() retornou falso, o resultado da expressão já é conhecido, pois (falso && qualquer coisa) é falso. Então o segundo int.TryParse() não roda.

Coisa 2: Quando int.TryParse() falha, ele coloca 0 (zero) como valor do inteiro que armazenaria o resultado da conversão (o segundo parâmetro).

Como resultado da coisa 1, int2 permance com o valor inicial -1. Como resultado da coisa 2, int1 recebe 0. Então quando o else é executado, o programa entende que a conversão de string1 (valor "XX") deu certo, enquanto que a conversão de string2 (valor "000001") deu errado! De forma geral, devem ser evitadas na expressão booleana de um if chamadas que modificam dados, pois como C# usa avaliação booleana em curto-cirtuito, pode ser que nem todas as partes da expressão sejam avaliadas, o que podem causar resultados imprevisíveis no código.

Para corrigir o código inicial, devemos então fazer as chamadas a int.Parse() antes do if:


int int1, int2;
bool conversao1ok = int.TryParse(string1, out int1);
bool conversao2ok = int.TryParse(string2, out int2);
if (conversao1ok && conversao2ok)
{
// Conversões ok - usa os valores em int1 e int2
}
else
{
// Pelos menos uma das conversões falhou - loga o erro
if(!conversao1ok) LogaErro("Erro ao converter string1");
if(!conversao2ok) LogaErro("Erro ao converter string2");
}