2010-09-20 9 views
9

ho questo C# DLL:F #/.NET nullo esempio stranezza

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

E questo # app F che fa riferimento al DLL:

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

Nessuno avvia la variabile statica Instance. Indovina qual è l'uscita dell'app F #?

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

Dopo alcuni test ho scoperto che a meno che usi this (ad esempio per accedere campo esempio), non otterrà NullReferenceExpcetion.

È un comportamento previsto o uno spazio nella compilazione F #/CLR?

+0

+1: oh wow, è qualcosa che non ho visto prima :) – Juliet

risposta

10

Ho il sospetto che troverete che sta usando call invece di callvirt se si guarda l'IL. Il compilatore C# utilizza sempre callvirt, anche per i metodi non virtuali, poiché impone un controllo di nullità.

È un bug? Beh, non necessariamente. Dipende da cosa specifica la specifica del linguaggio F # sui metodi chiama su riferimenti null. È perfettamente possibile che affermi che il metodo sarà chiamato (non virtualmente) con un riferimento "questo" nullo, che è esattamente quello che è successo.

C# accade per specificare che questo tipo di dereferenziazione genererà un NullReferenceException, ma questa è una scelta di lingua.

Sospetto che l'approccio di F # potrebbe essere un po 'più veloce, a causa della mancanza di controllo di nullità in questione ... e non dimenticare che i riferimenti nulli sono meno "previsti" in F # rispetto a C# ... che potrebbe spiega il diverso approccio adottato qui. O potrebbe essere solo una svista, ovviamente.

EDIT: io non sono un esperto di leggere le specifiche F #, ma la sezione 6.9.6 almeno suggerisce a me che si tratta di un bug:

6.9.6 Valutare metodo di applicazioni

Per le elaborate applicazioni dei metodi, la forma elaborata dell'espressione sarà o expr.M (args) o M (args).

  • L'(opzionale) expr e args sono valutati in ordine da sinistra a destra e il corpo dell'organo viene valutata in un ambiente con parametri formali mappati ai corrispondenti valori degli argomenti.

  • Se expr restituisce null, viene sollevata NullReferenceException.

  • Se il metodo è uno slot di dispacciamento virtuale (ovvero un metodo che è dichiarato astratto), il corpo del membro viene scelto in base alle mappe di invio del valore di expr.

Se questo conta come un programma elaborato o non è un po 'al di là di me, ho paura ... ma spero che questo è stato almeno un po' utile.

+0

Hai ragione e la tua spiegazione sembra abbastanza ragionevole. È stata generata un'istruzione MSIL diversa. Non ero a conoscenza del fatto che sia possibile chiamare metodi in .NET in modo non virtuale. Grazie Jon – Elephantik

+0

@Elephantik: È interessante notare che puoi usare 'call' in IL per chiamare metodi virtuali non virtualmente, rompendo l'incapsulamento. (È come C# chiama a 'base.Foo()' funziona internamente.) –

+1

Infatti. vedere anche http://blogs.msdn.com/b/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx per alcuni comportamenti particolarmente angusti. – kvb

3

Penso che il team F # consideri questo comportamento un bug che probabilmente cambierà in una versione futura.

Occasionalmente una classe può cambiare un metodo da non virtuale (nella versione N) in virtuale (nella versione N + 1). Si tratta di un "cambio di rottura"? Si sta rompendo se il codice è compilato rispetto alla classe originale utilizzata call anziché callvirt. Il concetto di "cambio di rottura" è principalmente una questione di grado anziché tipo; il cambiamento da non virtuale a virtuale è un cambiamento di rottura "minore". In ogni caso, alcune classi in .NET Framework hanno mostrato questo tipo di cambiamento, e quindi si imbattono in un problema analogo alla "fragile base class". Testimoniando questo, il team di F # intende modificare il codegen del compilatore per usare callvirt come C# e VB do. Supponendo che ciò accada, alcuni programmi improbabili, come quello in questa versione, cambierebbero comportamento quando compilati con una versione futura del compilatore F #.

+0

IMHO, trovo un po 'scioccante che il CLR consideri MSIL valido per ignorare un override usando la chiamata invece di callvirt. Quello che mi piacerebbe vedere è il motivo per cui questo è considerato un MSIL valido. –

+0

In quale altro modo le chiamate base.Method() funzionano? – Brian