2010-09-20 11 views
9

que tiene este C# DLL:F #/.NET instancia nula rareza

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

Y esto # app F, que hace referencia a la DLL:

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

Nadie inicia la variable estática Instance. ¿Adivina cuál es el resultado de la aplicación F #?

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

Después de algunas pruebas descubrí que a menos que utilice this (por ejemplo, para acceder a un campo de instancia), no voy a entrar NullReferenceExpcetion.

¿Es eso un comportamiento previsto o una brecha en la compilación F #/CLR?

+0

+1: oh wow, eso es algo que no he visto antes :) – Juliet

Respuesta

10

Sospecho que encontrará que está usando call en lugar de callvirt si mira IL. El compilador C# siempre usa callvirt, incluso para métodos no virtuales, porque obliga a una comprobación de nulidad.

¿Esto es un error? Bueno, no necesariamente. Depende de lo que indique la especificación de lenguaje F # sobre métodos llama a referencias nulas. Es perfectamente posible que establezca que el método se llamará (no virtualmente) con una referencia nula "this", que es exactamente lo que ha sucedido.

C# pasa a especificar que este tipo de desreferencia arrojará un NullReferenceException, pero esa es una opción de idioma.

Sospecho que el enfoque F # puede ser un poco más rápido, debido a la falta de comprobación de nulidad involucrada ... y no olvide que las referencias nulas son menos "esperadas" en F # que en C# ... que explica el enfoque diferente tomado aquí. O podría ser un descuido, por supuesto.

EDIT: No soy un experto en la lectura de la especificación # F, pero la sección 6.9.6 al menos sugiere a mí que es un error:

6.9.6 Evaluación de aplicaciones Método

Para aplicaciones elaboradas de métodos, la forma elaborada de la expresión será ya sea expr.M (args) o M (args).

  • El (opcional) expr y args se evalúan en orden de izquierda a derecha y el cuerpo del miembro se evalúa en un ambiente con parámetros formales que se asignan a valores de argumento correspondiente.

  • Si expr se evalúa como nulo, se genera NullReferenceException.

  • Si el método es una ranura de despacho virtual (es decir, un método que se declara abstracto), el cuerpo del miembro se elige según los mapas de distribución del valor de expr.

si esto cuenta como una solicitud elaborada o no es un poco más allá de mí, me temo ... pero espero que esto ha sido al menos algo útil.

+0

Tienes razón y tu explicación parece bastante razonable. Se generó diferente instrucción MSIL. No sabía que es posible llamar a los métodos en .NET de forma no virtual. Gracias Jon – Elephantik

+0

@Elephantik: Curiosamente, puede usar 'call' en IL para llamar a métodos virtuales de forma no virtual, rompiendo la encapsulación. (Así es como C# llama a 'base.Foo()' funciona internamente). –

+1

De hecho. ver también http://blogs.msdn.com/b/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx para algunos comportamientos de esquina particularmente feos. – kvb

3

Creo que el equipo F # considera que este comportamiento es un error que probablemente cambie en una versión futura.

Ocasionalmente, una clase puede cambiar un método de no virtual (en la versión N) a virtual (en la versión N + 1). ¿Es esto un "cambio de ruptura"? Se está rompiendo si el código compilado contra la clase original usó call en lugar de callvirt. El concepto de "cambio de ruptura" es principalmente una cuestión de grado en lugar de tipo; el cambio no virtual a virtual es un cambio de ruptura "menor". En cualquier caso, algunas clases en el .NET Framework han exhibido este tipo de cambio, y por lo tanto te encuentras con un problema análogo a la "clase base frágil". Presenciando esto, el equipo F # intenta cambiar el codegen del compilador para usar callvirt como C# y VB do. Suponiendo que eso ocurra, algunos programas poco probables, como el de esta reproducción, cambiarían el comportamiento cuando se compila con una versión futura del compilador F #.

+0

En mi humilde opinión, me resulta un poco chocante que el CLR considere que es válido MSIL eludir una anulación mediante el uso de llamada en lugar de callvirt. Lo que me gustaría ver es la razón por la cual se considera MSIL válido. –

+0

¿De qué otro modo funcionarían las llamadas base.Method()? – Brian