Tag Archives: msil

The inline if statement in C# : Surprise !

NET%20LogoHi, I got a strange issue today I posted on stackoverflow.

The Issue

Here’s the situation, I’ve abstracted the name and logic to focus on the issue. Got 3 types, A, B and C. B & C have implicit operators defined to convert to A object.

public class A
{
    public static implicit operator A(B input){ /* Convert B to A */ }
    public static implicit operator A(C input) { /* Convert C to A*/ }
}

public class B { }
public class C { }

Then, when I do this, the code compile and work fine :

A myObject = null;
if (condition)
    myObject = new B();
else
    myObject = new C();

But when I write the same logic with an inline if, I got an error :

A myObject = condition ? new B() : new C();
Type of conditional expression cannot be determined because there is no implicit conversion between 'B' and 'C'

WHY ? Why does it work in classical if statement and not in inline if statement ?

The Answer

1775769

Here’s his answer :

Absolutely. The type of a conditional operator expression must either be the type of the second operand or the type of the third operand. (And if those two types aren’t the same, exactly one of the types has to be implicitly convertible to the other.) The compiler doesn’t try to find a “lower common denominator” type, and the use of the result isn’t important either (the compiler doesn’t “notice” that you’re assigning the result to a variable of type A).

You can fix this yourself by just explicitly casting either operand:

A myObject = condition ? (A) new B() : new C();

or

A myObject = condition ? new B() : (A) new C();

Note that this isn’t limited to user-defined conversion operators; the same is true for simple reference conversions based on derived classes:

Button x = new Button();
String y = "foo";
object z = condition ? x : y; // Compile-time error

See section 7.14 of the C# specification for more details.

Behind the scene

But the answer wasn’t totally complete for me. So I took my lovely decompiler and anaylyze what’s generated in the case of an inline and classical if. The code is “self explanatory” and I’ve commented each MSIL instruction to make it readable.

static void ClassicalIf(bool condition)
{
    int i = 0;
    if (condition)
        i = 1;
    else
        i = 2;
}

static void InlineIf(bool condition)
{
    int i = condition ? 1 : 2;
}

For the inline if :

.method private hidebysig static void InlineIf(bool condition) cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 i)
    L_0000: nop 
    L_0001: ldarg.0         -- Load argument '0' onto the stack
    L_0002: brtrue.s L_0007 -- Branch to L_0007 if value is non-zero
    L_0004: ldc.i4.2        -- Push 2 onto the stack
    L_0005: br.s L_0008     -- Branch to L_0008
    L_0007: ldc.i4.1        -- Push 1 onto the stack
    L_0008: nop 
    L_0009: stloc.0         -- Pop from stack into local variable 0
    L_000a: ret 
}

And here’s the one for the “normal” if :

.method private hidebysig static void ClassicalIf(bool condition) cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000) -- Additional bool for if
    L_0000: nop 
    L_0001: ldc.i4.0        -- Push 0 onto the stack
    L_0002: stloc.0         -- Pop from stack into local variable '0'
    L_0003: ldarg.0         -- Load argument '0' onto the stack
    L_0004: ldc.i4.0        -- Push 0 onto the stack 
    L_0005: ceq             -- Push 1 if value1 equals value2 (on stack), else push 0.
    L_0007: stloc.1         -- Pop from stack into local variable '1'
    L_0008: ldloc.1         -- Load local variable '1' onto stack.
    L_0009: brtrue.s L_000f -- Branch to L_000f if value is non-zero
    L_000b: ldc.i4.1        -- Push 1 onto the stack 
    L_000c: stloc.0         -- Pop from stack into local variable '0'
    L_000d: br.s L_0011     -- Branch to L_0011
    L_000f: ldc.i4.2        -- Push 2 onto the stack 
    L_0010: stloc.0         -- Pop from stack into local variable '0'
    L_0011: ret 
}

Now same thing with an additional cast :

static void ClassicalIf(bool condition)
{
    object i = 0;
    if (condition)
        i = new object();
    else
        i = 2;
}
    
static void InlineIf(bool condition)
{
    object i = condition ? new object() : 2;
}

For the inline if :

.method private hidebysig static void InlineIf(bool condition) cil managed
{
    .maxstack 1
    .locals init (
        [0] object i)
    L_0000: nop 				
    L_0001: ldarg.0 			-- Load argument '0' onto the stack
    L_0002: brtrue.s L_000c     -- Branch to L_000c if value is non-zero (true)
    L_0004: ldc.i4.2            -- Push 2 onto the stack
    L_0005: box int32           -- Boxing
    L_000a: br.s L_0011         -- Branch to L_0008
    L_000c: newobj instance void-- Create new object  
    L_0011: nop                                                     
    L_0012: stloc.0 			-- Pop from stack into local variable 0
    L_0013: ret 
}

And here’s the one for the “normal” if :

.method private hidebysig static void ClassicalIf(bool condition) cil managed
{
    .maxstack 2
    .locals init (
        [0] object i,
        [1] bool CS$4$0000)
    L_0000: nop 					-- Push 0 onto the stack
    L_0001: ldc.i4.0                -- Pop from stack into local variable '0'
    L_0002: box int32               -- Boxing
    L_0007: stloc.0                 -- Pop from stack into local variable 0
    L_0008: ldarg.0                 -- Load argument '0' onto the stack
    L_0009: ldc.i4.0                -- Push 0 onto the stack 
    L_000a: ceq                     -- Push 1 if value1 equals value2 (on stack), else push 0.
    L_000c: stloc.1                 -- Pop from stack into local variable '1'
    L_000d: ldloc.1                 -- Load local variable '1' onto stack.
    L_000e: brtrue.s L_0018         -- Branch to L_0018 if value is non-zero (true)
    L_0010: newobj instance void [ms-- Create new object
    L_0015: stloc.0                 -- Pop from stack into local variable '0'
    L_0016: br.s L_001f             -- Branch to L_001f
    L_0018: ldc.i4.2                -- Push 2 onto the stack 
    L_0019: box int32               -- Boxing
    L_001e: stloc.0 				-- Pop from stack into local variable '0'
    L_001f: ret 
}

Conclusion

1) So I will also try to use inline if as possible. Less instructions (so CPU) and less memory usage.
2) Also it’s now crystal clear when we see how an inline if is converted to MSIL 🙂

Catch you next time and keep it bug free !

Désassembler, éditer et recompiler une application DotNet

Salut à tous, aujourd’hui un petit article sur les bases du dotnet, le MSIL ou Microsoft Inter médiate Langage de son doux et chaleureux nom. Le MSIL est en fait le code obtenus suite à la compilation d’une application dotnet et qui sera ensuite exécuté dans le CLR. C’est grâce à cela que l’on peut facilement porter d’une architecture à une autre. En effet la compilation d’un application dotnet ne produit pas directement du code binaire directement executable mais un code intermédiaire appel (MS)IL interpreté ensuite par la CLR. Ce pseudo langage peut facilement être lu et éditeé. Pour vous montrer cela je vais prendre comme exemple une application dans lequel nous allons rajouter une méthode qui affichera « Salut » dans la console. Nous utiliserons ILDasm pour dumper et ILAsm pour compiler.

Le premier se trouve dans le windows SDK et, est, normalement fournit avec Visual Studio (sinon suffit d’installer le windows sdk) le second est fournis avec le framework dotnet qui se trouve dans %WINDIR%/Microsoft.Net/Framework/. Pour moi ce sera : %WINDIR%/Microsoft.Net/Framework64/v4.0.30319/ilasm.exe et le premier C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\ILdasm.exe ^^
Le premier outil dispose d’un ihm mais le second non. (Il s’agit d’un compiler donc l’ihm serait un peu superflue).
Notre application console contient ce code :

using System;

namespace DemoDissamble
{
    class Program
    {
        static void Main(string[] args)
        {
            Print1();
            Console.ReadKey();
        }

        static void Print1()
        {
            Console.WriteLine("Je vous dis");
        }

        static void Print2()
        {
            Console.WriteLine("salut");
        }
    }
}

On a donc deux fonctions, mais print2 n’est pas appelé. On va régler ça.
On lance donc ILDasm.exe, on va dans Fichier->Ouvrir et on ouvre notre exe.
On devrait obtenir le résultat suivant :


On voit donc notre namespace qui contient une classe avec notre méthode main, print1 et print2. Pour le moment, on est encore en read-only. On va donc dumper en allant dans Fichier->Dump
On a toute une série de choses que l’on peut exporter, certaines très intéressantes, d’autres moins. Mais ici nous allons juste exporter le code IL, je laisse à votre discrétion l’exploration des meta-inf et cie 😉


Maintenant, j’ouvre le fichier sous visual studio (ne cherchez pas une coloration syntaxique native 😉 ), voici son contenu :

//  Microsoft (R) .NET Framework IL Disassembler.  Version 3.5.30729.1
//  Copyright (c) Microsoft Corporation. Tous droits r�serv�s.

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly DemoDissamble
{
  .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0D 44 65 6D 6F 44 69 73 73 61 6D 62 6C 65   // ...DemoDissamble
                                                                                              00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 09 4D 69 63 72 6F 73 6F 66 74 00 00 )       // ...Microsoft..
  .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0D 44 65 6D 6F 44 69 73 73 61 6D 62 6C 65   // ...DemoDissamble
                                                                                                00 00 )
  .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 1B 43 6F 70 79 72 69 67 68 74 20 C2 A9 20   // ...Copyright ..
                                                                                                  4D 69 63 72 6F 73 6F 66 74 20 32 30 31 31 00 00 ) // Microsoft 2011..
  .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
  .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 31 30 66 62 37 30 65 65 2D 63 34 37 30   // ..$10fb70ee-c470
                                                                                                  2D 34 66 63 62 2D 61 38 37 37 2D 35 65 30 33 66   // -4fcb-a877-5e03f
                                                                                                  36 31 38 37 39 32 34 00 00 )                      // 6187924..
  .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 )             // ...1.0.0.0..
  .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 29 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B   // ..).NETFramework
                                                                                                        2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 2C 50 72   // ,Version=v4.0,Pr
                                                                                                        6F 66 69 6C 65 3D 43 6C 69 65 6E 74 01 00 54 0E   // ofile=Client..T.
                                                                                                        14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61   // .FrameworkDispla
                                                                                                        79 4E 61 6D 65 1F 2E 4E 45 54 20 46 72 61 6D 65   // yName..NET Frame
                                                                                                        77 6F 72 6B 20 34 20 43 6C 69 65 6E 74 20 50 72   // work 4 Client Pr
                                                                                                        6F 66 69 6C 65 )                                  // ofile

  // --- The following custom attribute is added automatically, do not uncomment -------
  //  .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 )

  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
  .hash algorithm 0x00008004
  .ver 1:0:0:0
}
.module DemoDissamble.exe
// MVID: {7CD7C5F3-4B77-4150-8FC1-D694703BE62D}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000003    //  ILONLY 32BITREQUIRED
// Image base: 0x00970000

// =============== CLASS MEMBERS DECLARATION ===================

.class private auto ansi beforefieldinit DemoDissamble.Program
       extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       12 (0xc)
    .maxstack  8
    IL_0000:  call       void DemoDissamble.Program::Print1()
    IL_0005:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    IL_000a:  pop
    IL_000b:  ret
  } // end of method Program::Main

  .method private hidebysig static void  Print1() cil managed
  {
    // Code size       11 (0xb)
    .maxstack  8
    IL_0000:  ldstr      "Je vous dis"
    IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret
  } // end of method Program::Print1

  .method private hidebysig static void  Print2() cil managed
  {
    // Code size       11 (0xb)
    .maxstack  8
    IL_0000:  ldstr      "salut"
    IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000a:  ret
  } // end of method Program::Print2

  .method public hidebysig specialname rtspecialname
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Program::.ctor

} // end of class DemoDissamble.Program

// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************
// AVERTISSEMENT : le fichier de ressources Win32 C:\dump.res a été créé

Dans mon cas il n’est pas très long, 100 lignes.
Notre main va de 58 à 67 suivi de Print1 de 69 à 76 et Print2 de 78 à 85.
Regardons un peu ce qui se fait, pour la suite j’utiliserai les offset qui prennent la forme IL_ pour une question de clarté par rapport aux numéros de lignes.
Dans main, IL_0000 et IL_0005 sont deux call. On comprend aisément qu’il s’agit d’appels de fonctions, ensuite on pop le top la stack et on return. En langage plus « haut niveau », on appelle deux méthodes et on return.
Ensuite nos deux méthodes Print sont sensiblement les mêmes. On crée une référence à un objet string sur le top de la stack, on y stock la valeur entre parenthèse et on appelle Writeline qui se chargera d’afficher dans la console la string sur le top de la stack. Ensuite on return. En langage plus « haut niveau », on crée une variable, que l’on passe en argument à WriteLine et on return. (Rappel de base, ce n’est pas parce que c’est void qu’il n’y a pas de return ;))
Avant de le modifier, on va le tester. Voici la ligne de commande a utiliser :
C:\Windows\Microsoft.NET\Framework64\v4.0.30319>ilasm.exe “C:\dump.il” /exe /out:”C:\run.exe” && C:\run.exe
Donc, en premier argument le chemin du dump à recompiler, ensuite le type d’output (exe ou dll) et enfin le chemin de l’output. Le && C:\run.exe me sert à exécuter le programme à condition que la compile ai réussi (c’est un & conditionnel). Ce qui devrait vous produire quelque chose comme cela :

Microsoft (R) .NET Framework IL Assembler.  Version 4.0.30319.1
Copyright (c) Microsoft Corporation.  All rights reserved.
Assembling 'C:\dump.il'  to EXE --> 'C:\run.exe'
Source file is UTF-8

Assembled method DemoDissamble.Program::Main
Assembled method DemoDissamble.Program::Print1
Assembled method DemoDissamble.Program::Print2
Assembled method DemoDissamble.Program::.ctor
Creating PE file

Emitting classes:
Class 1:        DemoDissamble.Program

Emitting fields and methods:
Global
Class 1 Methods: 4;
Resolving local member refs: 1 -> 1 defs, 0 refs, 0 unresolved

Emitting events and properties:
Global
Class 1
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully
Je vous dis

Comme on le voit il nous manque un output, le salut. On va donc rajouter l’appel à cette fonction après le premier appel. Mais on a un petit « soucis ». En effet, chaque instruction a un offset, il faut donc recalculer les offset afin de pouvoir le compiler. Cela nous donnera :

.method private hidebysig static void  Main(string[] args) cil managed
{
	.entrypoint
	// Code size       12 (0xc)
	.maxstack  8
	IL_0000:  call       void DemoDissamble.Program::Print1()
	IL_0005:  call       void DemoDissamble.Program::Print2()
	IL_000a:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
	IL_000b:  pop
	IL_000c:  ret
} // end of method Program::Main

Et notre output :

Pour aller plus loin voici la liste des OpCodes : http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.pop.aspx
Et la référence de ILAsm : http://msdn.microsoft.com/fr-fr/library/496e4ekx%28v=vs.80%29.aspx

Inform@tiquement
Istace Emmanuel

Linq : Différence de performance entre la syntaxe Linq et la syntaxe de Method

Salut a tous, aujourd’hui un article sur la différence de performance entre deux syntaxe pour écrire des requêtes Linq avec a la fin un spécial winner ^^

Alors, tout d’abord, voici ce qu’est une syntaxe linq :

var x = from t in test
             where t != 0
             select t;

Et voici une syntaxe de méthode :

var x = test.Where(i => i != 0)
                 .Select(i => i);

Pour ma part, du fait que la syntaxe Linq faisait appel a une interprétation a la compilation j’ai toujours pensé qu’elle était plus lente que la syntaxe en méthode. Mais nous allons voir que finalement, pas du tout.

Voici les deux méthodes que nous allons comparer :

        static void MethodSyntax()
        {
            List test = new List();
            test.Add(0);
            var x = test.Where(i => i != 0).Select(i => i);
        }

        static void LinqSyntax()
        {
            List test = new List();
            test.Add(0);
            var x = from t in test
                    where t != 0
                    select t;
        }

Et voici le Code IL généré :

Première Method :

.method private hidebysig static void  MethodSyntax() cil managed
{
  // Code size       90 (0x5a)
  .maxstack  4
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 test,
           [1] class [mscorlib]System.Collections.Generic.IEnumerable`1 x)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.0
  IL_0009:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0)
  IL_000e:  nop
  IL_000f:  ldloc.0
  IL_0010:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
  IL_0015:  brtrue.s   IL_002a
  IL_0017:  ldnull
  IL_0018:  ldftn      bool Linq.Program::'b__0'(int32)
  IL_001e:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                      native int)
  IL_0023:  stsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
  IL_0028:  br.s       IL_002a
  IL_002a:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
  IL_002f:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                                                                       class [mscorlib]System.Func`2)
  IL_0034:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
  IL_0039:  brtrue.s   IL_004e
  IL_003b:  ldnull
  IL_003c:  ldftn      int32 Linq.Program::'b__1'(int32)
  IL_0042:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                       native int)
  IL_0047:  stsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
  IL_004c:  br.s       IL_004e
  IL_004e:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
  IL_0053:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                                                                              class [mscorlib]System.Func`2)
  IL_0058:  stloc.1
  IL_0059:  ret
} // end of method Program::MethodSyntax

Avec Linq :

.method private hidebysig static void  LinqSyntax() cil managed
{
  // Code size       54 (0x36)
  .maxstack  4
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 test,
           [1] class [mscorlib]System.Collections.Generic.IEnumerable`1 x)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.0
  IL_0009:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0)
  IL_000e:  nop
  IL_000f:  ldloc.0
  IL_0010:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
  IL_0015:  brtrue.s   IL_002a
  IL_0017:  ldnull
  IL_0018:  ldftn      bool Linq.Program::'b__4'(int32)
  IL_001e:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                      native int)
  IL_0023:  stsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
  IL_0028:  br.s       IL_002a
  IL_002a:  ldsfld     class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
  IL_002f:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                                                                       class [mscorlib]System.Func`2)
  IL_0034:  stloc.1
  IL_0035:  ret
} // end of method Program::LinqSyntax

A ma plus grand surprise on peut voir que le code IL est plus lourd sur les méthode que sur la syntaxe.  En effet, si l’on observe le code on voit que Linq appel un delegate alors que Method en appel deux. Si l’on s’y penche d’un peu plus près on voit que le delegate appelé par Linq et le premier appelé par Methode est le même bien qu’étant stocké deux fois sous des noms différent.

Que font ces delegate :

.method private hidebysig static bool  'b__4'(int32 t) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
  .maxstack  2
  .locals init ([0] bool CS$1$0000)
  IL_0000:  ldarg.0                     // Load l'arguments 0
  IL_0001:  ldc.i4.0			// Met 0 sur la stack
  IL_0002:  ceq				// Equivalent a un if equal
  IL_0004:  ldc.i4.0			// Met 0 sur la stack
  IL_0005:  ceq				// Equivalent a un if equal
  IL_0007:  stloc.0			// Pop le premier élément stack de la stack et la store dans local 0
  IL_0008:  br.s       IL_000a		// se branche sur l'instruction 000a
  IL_000a:  ldloc.0			// Charge sur l'evaluation stack la variable a l'index 0
  IL_000b:  ret				// retour
}

.method private hidebysig static int32  'b__1'(int32 i) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
  // Code size       6 (0x6)
  .maxstack  1
  .locals init ([0] int32 CS$1$0000)
  IL_0000:  ldarg.0			// Charge l'argument 0
  IL_0001:  stloc.0			// Pop le premier élément stack de la stack et la store dans local 0
  IL_0002:  br.s       IL_0004		// se branche sur l'instruction 0004
  IL_0004:  ldloc.0			// Charge sur l'evaluation stack la variable a l'index 0
  IL_0005:  ret				// Charge
}

On comprend donc facilement qu’il s’agit pour le premier du where.

Et en fait, dans le code MSIL des deux méthodes, tout ce qui est dans la version Linq se trouve dans la version Method. Method exécute juste plus de code, c’est-à-dire de IL_0034 a IL 0053 dans lequel le delegate spécifique a Method est appelé.

Regardons au final les perfs. Sur un tableau de 1 000 000 000 (un milliard) d’integer ou on veut selectionner tous les int différents de 0. L’indice est exprimé en clock thick et se base sur 100 expériences par méthode dans lesquels on a exclus le premier et dernier décile (c’est a dire les extrêmes, considérée comme anomalies). Si vous voulez les nombres obtenus complets les voici : http://pastebin.com/TVy7F4Xq

(Effectué sur un portable monocore 2.8Ghz avec 4Go de ram)

Temp moyen avec Linq en thicks Temp moyen avec Method en thicks
9 214 21 506

On voit donc que, en moyenne, le code généré par une requête en syntaxe Linq sera 2,33 fois plus rapide que le code généré par une syntaxe Method.

Vous pouvez donc meller code lisible et perfs sans trop de soucis a présent ^^
Inform@tiquement
Manu404