2010-09-20 13 views
9

Ich habe diesen C# DLL:F #/.NET Instanz null Kuriosität

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; 
     } 
    } 
} 

Und diesen Fis-App, die die DLL verweist:

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

Niemand initiiert die Instance statische Variable. Rate was die Ausgabe der F # App ist?

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

Nach einigen Tests, dass ich herausgefunden habe, wenn ich this verwenden (beispielsweise Feld zuzugreifen), werde ich NullReferenceExpcetion nicht bekommen.

Ist das ein beabsichtigtes Verhalten oder eine Lücke in F # Compilation/CLR?

+0

+1: oh wow, das ist etwas, was ich noch nie zuvor gesehen habe :) – Juliet

Antwort

10

Ich vermute, Sie finden es mit call anstelle von callvirt, wenn Sie sich die IL ansehen. Der C# -Compiler verwendet immer callvirt, auch für nicht-virtuelle Methoden, weil er eine Nichtigkeitsprüfung erzwingt.

Ist das ein Fehler? Nun, nicht unbedingt. Es hängt davon ab, was die F # -Sprachspezifikation über Methodenaufrufe für Nullverweise angibt. Es ist durchaus möglich, dass es heißt, dass die Methode (nicht-virtuell) mit einer Null "dieser" Referenz aufgerufen wird, was genau passiert ist.

C# gibt an, dass diese Art der Dereferenzierung eine NullReferenceException auslösen wird, aber das ist eine Sprachwahl.

Ich vermute, dass der F # -Ansatz ein wenig schneller sein kann, wegen der fehlenden Nichtigkeitsüberprüfung ... und vergiss nicht, dass Nullreferenzen in F # weniger "erwartet" sind als in C# ... dass kann erklären die verschiedenen Ansatz hier genommen. Oder es könnte natürlich nur ein Versehen sein.

EDIT: Ich bin kein Experte in der Spezifikation F # zu lesen, aber Abschnitt 6.9.6 mindestens schlägt mir, dass es ein Fehler ist: Anwendungen Methode

6.9.6 Auswertung

Für ausgearbeitete Anwendungen von Methoden wird die ausgearbeitete Form des Ausdrucks entweder expr.M (args) oder M (args) sein.

  • Die (optional) und expr args in links-nach-rechts um, und der Körper des Elements ausgewertet werden, ist in einer Umgebung mit formalen Parametern ausgewertet, die auf entsprechende Argumentwerte abgebildet werden.

  • Wenn Ausdruck zu null auswertet, wird NullReferenceException ausgelöst.

  • Wenn die Methode ein virtueller Dispatch-Slot ist (dh eine Methode, die als abstrakt deklariert wird), wird der Rumpf des Members gemäß den Dispatch-Maps des Werts von expr ausgewählt.

Ob dies gilt als eine elaborierte Anwendung oder nicht, ist ein wenig über mich, ich habe Angst ... aber ich hoffe, dass dies zumindest etwas hilfreich war.

+0

Sie haben Recht und Ihre Erklärung klingt ziemlich vernünftig. Es wurde eine andere MSIL-Anweisung generiert. Mir war nicht bewusst, dass es möglich ist, Methoden in .NET auf nicht virtuelle Weise aufzurufen. Danke Jon – Elephantik

+0

@Elephantik: Interessanterweise können Sie Call in IL verwenden, um virtuelle Methoden nicht virtuell aufzurufen und die Kapselung zu unterbrechen. (So ​​ruft C# zu 'base.Foo()' intern auf.) –

+1

In der Tat. Siehe auch http://blogs.msdn.com/b/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx für ein besonders hässliches Verhalten bei Ecken. – kvb

3

Ich denke, dass das F # -Team dieses Verhalten als Fehler betrachtet, der sich wahrscheinlich in einer zukünftigen Version ändern wird.

Gelegentlich kann eine Klasse eine Methode von nicht virtuell (in Version N) in virtuell (in Version N + 1) ändern. Ist das eine "brechende Veränderung"? Es bricht, wenn Code kompiliert gegen die ursprüngliche Klasse call anstatt callvirt kompiliert wird. Das Konzept des "Brechens der Veränderung" ist meistens eine Angelegenheit von Grad eher als Art; Die nicht virtuelle bis virtuelle Veränderung ist eine "geringfügige" Bruchänderung. Auf jeden Fall haben einige Klassen in .NET Framework diese Art von Änderung gezeigt, und Sie stoßen also auf ein Problem, das der "zerbrechlichen Basisklasse" analog ist. Dies beweisend, beabsichtigt das F # -Team, den Compilercodegen zu ändern, um callvirt wie C# und VB do zu verwenden. Angenommen, dies geschieht, würden einige unwahrscheinliche Programme, wie das in dieser Wiederholung, das Verhalten ändern, wenn sie mit einer zukünftigen Version des F # -Compilers kompiliert werden.

+0

IMHO finde ich es ein wenig schockierend, dass die CLR es als gültige MSIL ansieht, einen Override zu umgehen, indem sie Call statt benutzt Rufvirt. Was ich gerne sehen würde, ist der Grund dafür, dass dies eine gültige MSIL ist. –

+0

Wie sonst würden base.Method() -Aufrufe funktionieren? – Brian