Por: Roberto Alexis Farah

 

Oi pessoal!

 

Eis a resposta do Desafio da Semana #3 http://blogs.technet.com/latam/archive/2006/05/05/427415.aspx .

O desafio ilustrou uma situação que pode ocorrer quando projetos são migrados de VB.NET para C# ou vice-versa.

 

 

PROBLEMA

 

O C# faz o que chamamos de curto-circuito ao resolver expressões lógicas. Ou seja a expressão pode não ser inteiramente analizada se parte da expressão for suficiente para se concluir o resultado.

Essa é uma herança do C/C++ e funciona assim:

 

AND                                    OR                                 XOR

1  0   = 0                           1   0   = 1                        1   0    = 1

0  1   = 0                           0   1   = 1                        0   1    = 1

1  1   = 1                           1   1   = 1                        1   1    = 0

0  0   = 0                           0   0   = 0                        0   0    = 0

 

Portanto em C# (ou C/C++) em situações como no exemplo abaixo resolver a primeira expressão é suficiente para se chegar a correta conclusão lógica. Se a primeira expressão (10 == numA) for falsa (False, 0) não é necessário se analisar a segunda expressão pois, independente do resultado da segunda expressão, a expressão toda será falsa, de acordo com a tabela acima.

 

If((10 == numA) && (20 == numB))

{

        // Executa…

}

 

O mesmo ocorre no exemplo abaixo. Se a primeira expressão for verdadeira a segunda não é analizada pois não vai influenciar no resultado final:

 

If((10 == numA) || (20 == numB))

{

        // Executa...

}

 

Pois bem, VB.NET por default mantém compatibilidade com VB 6.0 portanto, não faz esse tipo de análise otimizada de expressões booleanas. Entretanto, há operadores especiais do VB.NET usados justamente para isso.

 

Há um artigo que explica isso:

 

Description of "short-circuit" evaluation in Visual Basic

http://support.microsoft.com/kb/817250/en-us#EKACAAA

 

Portanto, VB.NET age como Visual Basic 6 quando usando os operadores herdados do Visual Basic 6, logo, a expressão é sempre completamente analizada.

E é esse comportamento que faz com que o resultado das expressões seja diferente no desafio proposto.

Entretanto, o VB.NET tem operadores especiais que fazem a análise da expressão do mesmo modo que o C#, conforme explicado no artigo acima.

 

Usando a ferramenta ILDASM que vem com o Visual Studio é possível ver o código Intermediate Language que é onde deveriam haver diferenças.

 

Código VB.NET original:

 

.method /*06000012*/ public static void  Main() cil managed

// SIG: 00 00 01

{

  .entrypoint

  .custom /*0C000045:0A00001F*/ instance void [mscorlib/*23000001*/]System.STAThreadAttribute/*0100001A*/::.ctor() /* 0A00001F */ = ( 01 00 00 00 )

  // Method begins at RVA 0x22d4

  // Code size       138 (0x8a)

  .maxstack  3

  .locals /*1100000C*/ init ([0] bool VB$CG$t_bool$S0)

  .language '{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'

// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\VB\Module1.vb'

  .line 5,5 : 5,15 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\VB\\Module1.vb'

//000005:     Sub Main()

  IL_0000:  /* 00   |                  */ nop

  .line 6,6 : 3,49 ''

//000006:         If (DoSubtraction() > 0) Or (DoSum() > 0) Then

  IL_0001:  /* 28   | (06)000013       */ call       int32 VB.Module1/*02000007*/::DoSubtraction() /* 06000013 */

  IL_0006:  /* 16   |                  */ ldc.i4.0

  IL_0007:  /* FE02 |                  */ cgt

  IL_0009:  /* 28   | (06)000014       */ call       int32 VB.Module1/*02000007*/::DoSum() /* 06000014 */

  IL_000e:  /* 16   |                  */ ldc.i4.0

  IL_000f:  /* FE02 |                  */ cgt

  IL_0011:  /* 60   |                  */ or

  IL_0012:  /* 0A   |                  */ stloc.0

  IL_0013:  /* 06   |                  */ ldloc.0

  IL_0014:  /* 2C   | 1B               */ brfalse.s  IL_0031

  .line 7,7 : 7,82 ''

//000007:                  Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

  IL_0016:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}" /* 70000001 */

  IL_001b:  /* 7E   | (04)000006       */ ldsfld     int32 VB.Module1/*02000007*/::sum /* 04000006 */

  IL_0020:  /* 7E   | (04)000007       */ ldsfld     int32 VB.Module1/*02000007*/::subtraction /* 04000007 */

  IL_0025:  /* D6   |                  */ add.ovf

  IL_0026:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_002b:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_0030:  /* 00   |                  */ nop

  .line 8,8 : 3,9 ''

//000008:         End If

  IL_0031:  /* 00   |                  */ nop

  .line 10,10 : 3,11 ''

//000009:

//000010:         sum = -5

  IL_0032:  /* 1F   | FB               */ ldc.i4.s   -5

  IL_0034:  /* 80   | (04)000006       */ stsfld     int32 VB.Module1/*02000007*/::sum /* 04000006 */

  .line 12,12 : 3,50 ''

//000011:

//000012:         If (DoSubtraction() < 0) And (DoSum() < 0) Then

  IL_0039:  /* 28   | (06)000013       */ call       int32 VB.Module1/*02000007*/::DoSubtraction() /* 06000013 */

  IL_003e:  /* 16   |                  */ ldc.i4.0

  IL_003f:  /* FE04 |                  */ clt

  IL_0041:  /* 28   | (06)000014       */ call       int32 VB.Module1/*02000007*/::DoSum() /* 06000014 */

  IL_0046:  /* 16   |                  */ ldc.i4.0

  IL_0047:  /* FE04 |                  */ clt

  IL_0049:  /* 5F   |                  */ and

  IL_004a:  /* 0A   |                  */ stloc.0

  IL_004b:  /* 06   |                  */ ldloc.0

  IL_004c:  /* 2C   | 1D               */ brfalse.s  IL_006b

  .line 13,13 : 5,80 ''

//000013:           Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

  IL_004e:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}" /* 70000001 */

  IL_0053:  /* 7E   | (04)000006       */ ldsfld     int32 VB.Module1/*02000007*/::sum /* 04000006 */

  IL_0058:  /* 7E   | (04)000007       */ ldsfld     int32 VB.Module1/*02000007*/::subtraction /* 04000007 */

  IL_005d:  /* D6   |                  */ add.ovf

  IL_005e:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_0063:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_0068:  /* 00   |                  */ nop

  IL_0069:  /* 2B   | 1C               */ br.s       IL_0087

  .line 14,14 : 3,7 ''

//000014:         Else

  IL_006b:  /* 00   |                  */ nop

  .line 15,15 : 4,84 ''

//000015:          Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)

  IL_006c:  /* 72   | (70)000049       */ ldstr      "Valor da subtracao das variaveis = {0:d}" /* 70000049 */

  IL_0071:  /* 7E   | (04)000006       */ ldsfld     int32 VB.Module1/*02000007*/::sum /* 04000006 */

  IL_0076:  /* 7E   | (04)000007       */ ldsfld     int32 VB.Module1/*02000007*/::subtraction /* 04000007 */

  IL_007b:  /* DA   |                  */ sub.ovf

  IL_007c:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_0081:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_0086:  /* 00   |                  */ nop

  .line 16,16 : 3,9 ''

//000016:         End If

  IL_0087:  /* 00   |                  */ nop

  .line 18,18 : 5,12 ''

//000017:

//000018:     End Sub

  IL_0088:  /* 00   |                  */ nop

  IL_0089:  /* 2A   |                  */ ret

} // end of method Module1::Main

 

 

Código C#:

 

.method /*06000001*/ private hidebysig static

        void  Main(string[] args) cil managed

// SIG: 00 01 01 1D 0E

{

  .entrypoint

  // Method begins at RVA 0x2050

  // Code size       119 (0x77)

  .maxstack  3

  .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'

// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\CSharp\Program.cs'

  .line 15,15 : 12,54 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\CSharp\\Program.cs'

//000015:            if((DoSubtraction() > 0) || (DoSum() > 0))

  IL_0000:  /* 28   | (06)000002       */ call       int32 CSharp.Program/*02000002*/::DoSubtraction() /* 06000002 */

  IL_0005:  /* 16   |                  */ ldc.i4.0

  IL_0006:  /* 30   | 08               */ bgt.s      IL_0010

  IL_0008:  /* 28   | (06)000003       */ call       int32 CSharp.Program/*02000002*/::DoSum() /* 06000003 */

  IL_000d:  /* 16   |                  */ ldc.i4.0

  IL_000e:  /* 31   | 1A               */ ble.s      IL_002a

  .line 17,17 : 7,85 ''

//000016:            {

//000017:                  Console.WriteLine("Valor da soma das variaveis = {0:d}\n", sum + subtraction);              

  IL_0010:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}\n" /* 70000001 */

  IL_0015:  /* 7E   | (04)000001       */ ldsfld     int32 CSharp.Program/*02000002*/::sum /* 04000001 */

  IL_001a:  /* 7E   | (04)000002       */ ldsfld     int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */

  IL_001f:  /* 58   |                  */ add

  IL_0020:  /* 8C   | (01)000012       */ box        [mscorlib/*23000001*/]System.Int32/*01000012*/

  IL_0025:  /* 28   | (0A)000010       */ call       void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,

                                                                                                                      object) /* 0A000010 */

  .line 20,20 : 10,19 ''

//000018:            }

//000019:          

//000020:          sum = -5;

  IL_002a:  /* 1F   | FB               */ ldc.i4.s   -5

  IL_002c:  /* 80   | (04)000001       */ stsfld     int32 CSharp.Program/*02000002*/::sum /* 04000001 */

  .line 22,22 : 11,53 ''

//000021:            

//000022:           if((DoSubtraction() < 0) && (DoSum() < 0))

  IL_0031:  /* 28   | (06)000002       */ call       int32 CSharp.Program/*02000002*/::DoSubtraction() /* 06000002 */

  IL_0036:  /* 16   |                  */ ldc.i4.0

  IL_0037:  /* 2F   | 23               */ bge.s      IL_005c

  IL_0039:  /* 28   | (06)000003       */ call       int32 CSharp.Program/*02000002*/::DoSum() /* 06000003 */

  IL_003e:  /* 16   |                  */ ldc.i4.0

  IL_003f:  /* 2F   | 1B               */ bge.s      IL_005c

  .line 24,24 : 6,84 ''

//000023:           {

//000024:                 Console.WriteLine("Valor da soma das variaveis = {0:d}\n", sum + subtraction);             

  IL_0041:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}\n" /* 70000001 */

  IL_0046:  /* 7E   | (04)000001       */ ldsfld     int32 CSharp.Program/*02000002*/::sum /* 04000001 */

  IL_004b:  /* 7E   | (04)000002       */ ldsfld     int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */

  IL_0050:  /* 58   |                  */ add

  IL_0051:  /* 8C   | (01)000012       */ box        [mscorlib/*23000001*/]System.Int32/*01000012*/

  IL_0056:  /* 28   | (0A)000010       */ call       void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,

                                                                                                                      object) /* 0A000010 */

  IL_005b:  /* 2A   |                  */ ret

  .line 28,28 : 5,88 ''

//000025:           } 

//000026:           else

//000027:          {

//000028:                Console.WriteLine("Valor da subtracao das variaveis = {0:d}\n", sum - subtraction);                      

  IL_005c:  /* 72   | (70)00004B       */ ldstr      "Valor da subtracao das variaveis = {0:d}\n" /* 7000004B */

  IL_0061:  /* 7E   | (04)000001       */ ldsfld     int32 CSharp.Program/*02000002*/::sum /* 04000001 */

  IL_0066:  /* 7E   | (04)000002       */ ldsfld     int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */

  IL_006b:  /* 59   |                  */ sub

  IL_006c:  /* 8C   | (01)000012       */ box        [mscorlib/*23000001*/]System.Int32/*01000012*/

  IL_0071:  /* 28   | (0A)000010       */ call       void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,

                                                                                                                      object) /* 0A000010 */

  .line 31,31 : 9,10 ''

//000029:          } 

//000030:             

//000031:         }

  IL_0076:  /* 2A   |                  */ ret

} // end of method Program::Main

 

 

 

 

 

SOLUÇÃO

 

Module Module1

            Dim sum As Integer = 50

            Dim subtraction As Integer = 100

 

            Sub Main()

                        If (DoSubtraction() > 0) OrElse (DoSum() > 0) Then

                                       Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

                        End If

 

                        sum = -5

 

                        If (DoSubtraction() < 0) AndAlso (DoSum() < 0) Then

                          Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

                        Else

                         Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)

                        End If

 

            End Sub

            Function DoSubtraction() As Integer

                        subtraction -= sum

                        DoSubtraction = subtraction

                        Exit Function

            End Function

 

            Function DoSum() As Integer

                        sum += subtraction

                        DoSum = sum

                        Exit Function

            End Function

End Module

 

Intermediate Language do código VB.NET usando os operadores que resolvem a expressão como em C#:

 

.method /*06000012*/ public static void  Main() cil managed

// SIG: 00 00 01

{

  .entrypoint

  .custom /*0C000045:0A00001F*/ instance void [mscorlib/*23000001*/]System.STAThreadAttribute/*0100001A*/::.ctor() /* 0A00001F */ = ( 01 00 00 00 )

  // Method begins at RVA 0x22d4

  // Code size       144 (0x90)

  .maxstack  3

  .locals /*1100000C*/ init ([0] bool VB$CG$t_bool$S0)

  .language '{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'

// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\Solution\Module1.vb'

  .line 5,5 : 2,12 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\Solution\\Module1.vb'

//000005:   Sub Main()

  IL_0000:  /* 00   |                  */ nop

  .line 6,6 : 3,53 ''

//000006:         If (DoSubtraction() > 0) OrElse (DoSum() > 0) Then

  IL_0001:  /* 28   | (06)000013       */ call       int32 Solution.Module1/*02000007*/::DoSubtraction() /* 06000013 */

  IL_0006:  /* 16   |                  */ ldc.i4.0

  IL_0007:  /* 30   | 0B               */ bgt.s      IL_0014

  IL_0009:  /* 28   | (06)000014       */ call       int32 Solution.Module1/*02000007*/::DoSum() /* 06000014 */

  IL_000e:  /* 16   |                  */ ldc.i4.0

  IL_000f:  /* 30   | 03               */ bgt.s      IL_0014

  IL_0011:  /* 16   |                  */ ldc.i4.0

  IL_0012:  /* 2B   | 01               */ br.s       IL_0015

  IL_0014:  /* 17   |                  */ ldc.i4.1

  IL_0015:  /* 0A   |                  */ stloc.0

  IL_0016:  /* 06   |                  */ ldloc.0

  IL_0017:  /* 2C   | 1B               */ brfalse.s  IL_0034

  .line 7,7 : 7,82 ''

//000007:                  Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

  IL_0019:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}" /* 70000001 */

  IL_001e:  /* 7E   | (04)000006       */ ldsfld     int32 Solution.Module1/*02000007*/::sum /* 04000006 */

  IL_0023:  /* 7E   | (04)000007       */ ldsfld     int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */

  IL_0028:  /* D6   |                  */ add.ovf

  IL_0029:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_002e:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_0033:  /* 00   |                  */ nop

  .line 8,8 : 3,9 ''

//000008:         End If

  IL_0034:  /* 00   |                  */ nop

  .line 10,10 : 3,11 ''

//000009:

//000010:         sum = -5

  IL_0035:  /* 1F   | FB               */ ldc.i4.s   -5

  IL_0037:  /* 80   | (04)000006       */ stsfld     int32 Solution.Module1/*02000007*/::sum /* 04000006 */

  .line 12,12 : 3,54 ''

//000011:

//000012:         If (DoSubtraction() < 0) AndAlso (DoSum() < 0) Then

  IL_003c:  /* 28   | (06)000013       */ call       int32 Solution.Module1/*02000007*/::DoSubtraction() /* 06000013 */

  IL_0041:  /* 16   |                  */ ldc.i4.0

  IL_0042:  /* 2F   | 08               */ bge.s      IL_004c

  IL_0044:  /* 28   | (06)000014       */ call       int32 Solution.Module1/*02000007*/::DoSum() /* 06000014 */

  IL_0049:  /* 16   |                  */ ldc.i4.0

  IL_004a:  /* 32   | 03               */ blt.s      IL_004f

  IL_004c:  /* 16   |                  */ ldc.i4.0

  IL_004d:  /* 2B   | 01               */ br.s       IL_0050

  IL_004f:  /* 17   |                  */ ldc.i4.1

  IL_0050:  /* 0A   |                  */ stloc.0

  IL_0051:  /* 06   |                  */ ldloc.0

  IL_0052:  /* 2C   | 1D               */ brfalse.s  IL_0071

  .line 13,13 : 5,80 ''

//000013:           Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)

  IL_0054:  /* 72   | (70)000001       */ ldstr      "Valor da soma das variaveis = {0:d}" /* 70000001 */

  IL_0059:  /* 7E   | (04)000006       */ ldsfld     int32 Solution.Module1/*02000007*/::sum /* 04000006 */

  IL_005e:  /* 7E   | (04)000007       */ ldsfld     int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */

  IL_0063:  /* D6   |                  */ add.ovf

  IL_0064:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_0069:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_006e:  /* 00   |                  */ nop

  IL_006f:  /* 2B   | 1C               */ br.s       IL_008d

  .line 14,14 : 3,7 ''

//000014:         Else

  IL_0071:  /* 00   |                  */ nop

  .line 15,15 : 4,84 ''

//000015:          Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)

  IL_0072:  /* 72   | (70)000049       */ ldstr      "Valor da subtracao das variaveis = {0:d}" /* 70000049 */

  IL_0077:  /* 7E   | (04)000006       */ ldsfld     int32 Solution.Module1/*02000007*/::sum /* 04000006 */

  IL_007c:  /* 7E   | (04)000007       */ ldsfld     int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */

  IL_0081:  /* DA   |                  */ sub.ovf

  IL_0082:  /* 8C   | (01)000018       */ box        [mscorlib/*23000001*/]System.Int32/*01000018*/

  IL_0087:  /* 28   | (0A)00001E       */ call       void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,

                                                                                                                      object) /* 0A00001E */

  IL_008c:  /* 00   |                  */ nop

  .line 16,16 : 3,9 ''

//000016:         End If

  IL_008d:  /* 00   |                  */ nop

  .line 18,18 : 2,9 ''

//000017:

//000018:   End Sub

  IL_008e:  /* 00   |                  */ nop

  IL_008f:  /* 2A   |                  */ ret

} // end of method Module1::Main

 

 

Comparando o código IL da solução em VB.NET ou do código C# contra o código original VB.NET notamos, em vermelho acima, os seguintes mnemônicos:

 

OR         VB.NET original         C#         Solução em VB.NET

 

              cgt                         bgt          bgt

              or

 

 

AND        VB.NET original         C#         Solução em VB.NET

 

              clt                          bge         bge

              and

 

A principal diferença é que o código C# e a solução em VB.NET usam instruções de branch, ou seja, instruções IL que mudam o fluxo de execução de acordo com uma condição. Os comandos de branch são identificados pela letra b inicial. No caso temos:

 

bgt = branch if greater than

bge = branch if greater or equal

 

Ainda olhando o IL notamos que há desvios baseados em condições que fazem com que a segunda comparação dos if() possa ou não ser executada.

E olhando o código VB.NET da solução original, tanto no fragmento usando OR quanto no fragmento usando AND notamos que não há desvios e que ambas condições de cada comparação são sempre executadas!

Note que o comando clt e cgt iniciam com c, são comandos de check, ou seja, eles não desviam o fluxo de execução como um branch apenas colocam a condição da checagem na pilha.

 

Se você quiser entender o código disassemblado em IL, eis uma boa referência:

http://msdn2.microsoft.com/en-us/library/system.reflection.emit(VS.80).aspx

 

 

 

 

Embora esse seja um problema simples com uma solução conhecida e documentada, ter conhecimento dessa particularidade dos operadores pode salvá-lo de bugs difíceis de serem detectados.

 

Além disso, AndAlso e OrElse geram um código mais otimizado, portanto, é preferível usar eles ao invés dos correspondentes And e Or em VB.NET.