2010-09-20 5 views
9

J'ai cette C# DLL:F #/instance null .NET bizarrerie

namespace TestCSProject 
{ 
    public class TestClass 
    { 
     public static TestClass Instance = null; 

     public int Add(int a, int b) 
     { 
      if (this == null) 
       Console.WriteLine("this is null"); 
      return a + b; 
     } 
    } 
} 

Et cette application F # qui fait référence à la DLL:

open TestCSProject 
printfn "%d" (TestClass.Instance.Add(10,20)) 

Personne n'initie la variable statique Instance. Devinez quelle est la sortie de l'application F #?

 
this is null 
30 
Press any key to continue . . . 

Après quelques tests, je trouve que si je ne l'utilise this (par exemple pour accéder à champ d'instance), je ne vais pas NullReferenceExpcetion.

Est-ce un comportement prévu ou une lacune dans F # compilation/CLR?

+0

+1: oh wow, c'est quelque chose que je n'avais pas vu auparavant :) – Juliet

Répondre

10

Je suppose que vous trouverez qu'il utilise call au lieu de callvirt si vous regardez l'IL. Le compilateur C# utilise toujours callvirt, même pour les méthodes non virtuelles, car il force une vérification de nullité.

Est-ce un bug? Eh bien, pas nécessairement. Cela dépend de ce que la spécification de langage F # indique à propos des appels de méthodes sur des références nulles. Il est parfaitement possible qu'il indique que la méthode sera appelée (non-virtuelle) avec une référence "this" nulle, ce qui est exactement ce qui s'est passé. C# arrive à spécifier que ce type de déréférencement va lancer un NullReferenceException, mais c'est un choix de langue.

Je soupçonne que l'approche F # peut être un peu plus rapide, en raison de l'absence de vérification de nullité impliquée ... et n'oubliez pas que les références nulles sont moins "attendues" dans F # que dans C# ... expliquer l'approche différente adoptée ici. Ou il pourrait juste être un oubli, bien sûr.

EDIT: Je ne suis pas un expert à la lecture de la spécification F #, mais la section 6.9.6 au moins me suggère que c'est un bug:

6.9.6 Évaluation de l'application des méthodes

Pour les applications élaborées de méthodes, la forme élaborée de l'expression sera soit expr.M (args) ou M (args).

  • Le (optionnel) expr et args sont évalués dans l'ordre de gauche à droite et le corps de l'élément est évalué dans un environnement avec des paramètres formels qui sont associés à des valeurs d'argument correspondante.

  • Si expr est évalué à null, NullReferenceException est déclenché.

  • Si la méthode est un slot d'envoi virtuel (c'est-à-dire une méthode déclarée abstraite), alors le corps du membre est choisi en fonction des cartes d'envoi de la valeur de expr.

Que cela compte comme une application élaborée ou non est un peu au-delà de moi, j'ai peur ... mais j'espère que cela a été au moins un peu utile.

+0

Vous avez raison et votre explication semble assez raisonnable. Une instruction MSIL différente a été générée. Je n'étais pas au courant qu'il est possible d'appeler des méthodes dans .NET de manière non virtuelle. Merci Jon – Elephantik

+0

@Elephantik: Il est intéressant de noter que vous pouvez utiliser 'call' dans IL pour appeler des méthodes virtuelles de manière non-virtuelle, en rompant l'encapsulation. (C'est comme ça que C# appelle 'base.Foo()' fonctionne en interne.) –

+1

En effet. voir aussi http://blogs.msdn.com/b/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx pour un comportement en coin particulièrement horrible. – kvb

3

Je pense que l'équipe F # considère ce comportement comme un bogue susceptible de changer dans une future version.

Occasionnellement, une classe peut changer une méthode de non-virtuelle (dans la version N) à virtuelle (dans la version N + 1). Est-ce un "changement de rupture"? Il se casse si le code compilé par rapport à la classe d'origine est utilisé call plutôt que callvirt. Le concept de "rupture de changement" est principalement une question de degré plutôt que genre; le changement non virtuel à virtuel est un changement de rupture «mineur». Dans tous les cas, certaines classes du .NET Framework ont ​​montré ce genre de changement, et vous rencontrez ainsi un problème analogue à la "classe de base fragile". En témoigne, l'équipe F # a l'intention de changer le codegen du compilateur pour utiliser callvirt comme C# et VB do. En supposant que cela se produise, quelques programmes improbables, comme celui de ce repro, changeraient de comportement une fois compilés avec une future version du compilateur F #.

+0

IMHO, je trouve un peu choquant que le CLR considère MSIL valide pour contourner une dérogation en utilisant l'appel au lieu de callvirt. Ce que j'aimerais voir, c'est la raison pour laquelle il faut considérer le MSIL valide. –

+0

Comment les appels bas.Method() pourraient-ils fonctionner? – Brian