2010-11-27 30 views
2

Salutations! Je suis un peu confus sur la façon dont le compilateur C# effectue ses optimisations.
j'ai écrit le getter suivant pour compenser l'initialisation « paresseux », et la valeur par défaut en cas de null:Optimisations de compilation C#: opérateur de coalescence nulle - MISE À JOUR - Bogue du réflecteur?

Classe statique Helper:

private static string host; 
public static string Host 
{   
    get 
    { 
     return host ?? (host= (ConfigurationManager.AppSettings["Host"] ?? "host.ru")); 
    } 
} 

Voici le résultat de désassemblage par réflecteur :

public static string Host 
{ 
    get 
    { 
     if (Helper.host == null) 
     { 
      string host = Helper.host; 
     } 
     return (Helper.host = ConfigurationManager.AppSettings["Host"] ?? "host.ru"); 
    } 
} 

on dirait qu'il travaillerait d'une autre manière que prévu ...

MISE À JOUR

private static string host; 
    public static string Host 
    { 
     get 
     { 
      return host ?? (host = (GetVal() ?? "default")); 
     } 
    } 
    static void Main(string[] args) 
    { 

     Console.WriteLine(Host); 
     host = "overwritten"; 
     Console.WriteLine(Host); 
    } 
    static string GetVal() 
    { 
     return "From config"; 
    } 

fonctionne correctement (De config, écrasé), mais réflecteur montre la même:

public static string Host 
{ 
    get 
    { 
     if (Program.host == null) 
     { 
      string host = Program.host; 
     } 
     return (Program.host = GetVal() ?? "default"); 
    } 
} 
+0

Si vous utilisez C# 4.0 Je regarde en utilisant la classe Lazy pour Lazy instanciation. –

+0

Cela me semble assez étrange. Presque ressemble à un bug dans le réflecteur ou C#. Mais un bug C# dans un code aussi simple semble plutôt improbable. – CodesInChaos

+0

Peut-être que quelqu'un qui comprend IL peut vérifier si c'est un bug dans le réflecteur – CodesInChaos

Répondre

1

Cela ressemble à un bug dans le désassemblage C# de Reflector.

A partir de ce code:

public static string _test; 
public static string _setting; 

public static string Test_1 
{ 
    get { return _test ?? (_setting ?? "default"); } 
} 

réflecteur montre ce C# démontage:

public static string Test_1 
{ 
    get 
    { 
     return (_test ?? (_setting ?? "default")); 
    } 
} 

et correspondant IL:

.method public hidebysig specialname static string get_Test_1() cil managed 
{ 
    .maxstack 8 
    L_0000: ldsfld string ConsoleApplication1.Program::_test 
    L_0005: dup 
    L_0006: brtrue.s L_0017 
    L_0008: pop 
    L_0009: ldsfld string ConsoleApplication1.Program::_setting 
    L_000e: dup 
    L_000f: brtrue.s L_0017 
    L_0011: pop 
    L_0012: ldstr "default" 
    L_0017: ret 
} 

Je ne suis pas un expert IL, mais est mon point de vue:

  • L_0000:ldsfld pousse _test sur la pile d'évaluation
  • L_0005:dup copie la valeur (_test) qui est le plus haut sur la pile d'évaluation et pousse que sur la pile.
  • L_0006:brtrue.s pops la valeur créée par dup la pile et saute à L_0017 si ce ne null.
  • L_0008:pop à ce stade, _test est null, sortez cette valeur de la pile.

et il continue d'évaluer _setting de façon similaire, enfin de retour "default" si _setting est également null.

Maintenant, si l'on ajoute une affectation dans le code comme ceci:

public static string Test_2 
{ 
    get { return _test ?? (_test = (_setting ?? "default")); } 
} 

réflecteur montre ce C# démontage:

public static string Test_2 
{ 
    get 
    { 
     if (_test == null) 
     { 
      string text1 = _test; 
     } 
     return (_test = _setting ?? "default"); 
    } 
} 

qui n'est pas correct (si _test n'est pas null, au lieu de renvoyant _test, il attribue _setting ou "default" à _test, puis retourne).

Cependant, le désassemblage IL ressemble à IL pour Test_1, avec quelques instructions supplémentaires à L_0017 et L_0018 pour effectuer l'affectation.

.method public hidebysig specialname static string get_Test_2() cil managed 
{ 
    .maxstack 8 
    L_0000: ldsfld string ConsoleApplication1.Program::_test 
    L_0005: dup 
    L_0006: brtrue.s L_001d 
    L_0008: pop 
    L_0009: ldsfld string ConsoleApplication1.Program::_setting 
    L_000e: dup 
    L_000f: brtrue.s L_0017 
    L_0011: pop 
    L_0012: ldstr "default" 
    L_0017: dup 
    L_0018: stsfld string ConsoleApplication1.Program::_test 
    L_001d: ret 
} 

Enfin, si vous copiez C# le réflecteur de désassemblage et d'exécuter contre l'original, vous verrez qu'il produit des résultats différents.

using System; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      _test = "Test"; 
      Console.WriteLine(Test_2); 
      Console.WriteLine(Reflector_Test_2); 
      Console.ReadLine(); 
     } 

     public static string _test; 
     public static string _setting; 

     public static string Test_1 
     { 
      get { return _test ?? (_setting ?? "default"); } 
     } 

     public static string Test_2 
     { 
      get { return _test ?? (_test = (_setting ?? "default")); } 
     } 

     public static string Reflector_Test_2 
     { 
      get 
      { 
       if (_test == null) 
       { 
        string text1 = _test; 
       } 
       return (_test = _setting ?? "default"); 
      } 
     } 
    } 
} 

Sorties

Test 
default 
+0

Merci! J'ai posté ce problème sur le forum RedGate: http://www.red-gate.com/MessageBoard/viewtopic.php?p=44297 – Cheerkin

0

Je suppose que je ne comprends pas - les deux exemples de code sont synonymes . N'oubliez pas que Reflector ne peut pas reproduire votre syntaxe exacte à partir de l'IL généré par le compilateur. Parfois, la syntaxe sera différente mais la sémantique et la signification du code seront toujours les mêmes.

+0

Je ne vois pas comment ils sont aussi. Le premier appelle (en dehors des problèmes de thread-safty) l'expression juste une fois, le second l'appelle chaque fois que le getter est exécuté. – CodesInChaos

+0

Oui, c'est ce qui me rend confus, en fait. Pas de "charge paresseuse" du tout. – Cheerkin

+0

@ Callum Rogers: Je ne vois pas comment le second est paresseux. Il appelle 'ConfigurationManager.AppSettings [" Host "]' à chaque fois. Alors que le premier l'appelle seulement si 'host == null'. Et le code 'if' dans la seconde ne fait rien du tout. Il assigne à une variable locale, mais ne la lit jamais. – CodesInChaos