2010-04-20 10 views
7

estoy teniendo una discusión con el CodeContracts static analysis tool.CodeContracts: Posiblemente llamar a un método en una referencia nula

Mi código:

Screenshot http://i40.tinypic.com/r91zq9.png

(ASCII version)

La herramienta me dice que instance.bar puede haber una referencia nula. Yo creo lo opuesto.

¿Quién tiene razón? ¿Cómo puedo probar que está mal?

+2

De tema, pero me podría decir qué tema de Visual Studio que está utilizando? – Justin

+1

@Justin: simplemente el tema predeterminado vs2010 con mi propio esquema de color de texto personalizado (ver publicación editada). ¿Porque lo preguntas? – dtb

+0

Acabo de estar buscando cambiar mi propio esquema de colores de estudio visual últimamente y realmente me gusta. Me preguntaba si lo habías descargado en alguna parte. – Justin

Respuesta

2

Actualización: Parece que el problema es que invariants are not supported for static fields.

2da actualización: El método descrito a continuación es currently the recommended solution.

Una posible solución es crear una propiedad para instance que Ensure s las invariantes que desea mantener. (Por supuesto, necesita Assume para que se pruebe el Ensure). Una vez que haya hecho esto, puede usar la propiedad y todas las invariantes deben probarse correctamente.

Ésta es tu ejemplo usando este método:

class Foo 
{ 
    private static readonly Foo instance = new Foo(); 
    private readonly string bar; 

    public static Foo Instance 
    // workaround for not being able to put invariants on static fields 
    { 
     get 
     { 
      Contract.Ensures(Contract.Result<Foo>() != null); 
      Contract.Ensures(Contract.Result<Foo>().bar != null); 

      Contract.Assume(instance.bar != null); 
      return instance; 
     } 
    } 

    public Foo() 
    { 
     Contract.Ensures(bar != null); 
     bar = "Hello world!"; 
    } 

    public static int BarLength() 
    { 
     Contract.Assert(Instance != null); 
     Contract.Assert(Instance.bar != null); 
     // both of these are proven ok 

     return Instance.bar.Length; 
    } 
} 
+0

Gracias por investigar esto. No dude en publicarlo en el foro de MSDN si le interesa. Dejé de usar el comprobador estático por ahora porque una revisión de código actualmente produce mejores resultados para mí. – dtb

+0

He encontrado una publicación en el foro que indica que invariantes no son compatibles con campos estáticos. Actualicé mi publicación con una solución alternativa. – porges

+0

Buen hallazgo. Pero 'bar' es un campo de instancia, no estático !? De todos modos, he movido la marca de verificación a esta respuesta porque es la mejor explicación hasta ahora. – dtb

2

CodeContracts es el adecuado. No hay nada que le impida configurar instance.bar = null antes de llamar al método BarLength().

+2

Pero no estoy configurando 'instance.bar = null' en ninguna parte y' instance.bar' es privado, por lo que no puede ser nulo, ¿no? – dtb

+0

Sí, así que supongo que tienes razón si ese es el código completo. Es probable que su análisis estático no sea lo suficientemente inteligente como para determinar si alguna vez establece el valor como nulo o no, por lo que se supone que podría hacerlo. No sé a qué largo va a verificar eso, si es que hay alguno. – EMP

+0

¿Cómo puedo convencerlo de que 'instance.bar' nunca es' nulo'? Hacer 'bar' de solo lectura y agregar' Contract.Ensures (bar! = Null); 'al constructor no funciona. 'Contract.Assume (instance.bar! = Null);' en 'BarLength()' funciona pero parece feo. – dtb

-1

Estoy de acuerdo con usted. instance y bar son ambos privados, por lo que CodeContracts debería poder saber instance.bar nunca se establece en nulo.

0

Si marca el campo 'bar' como de sólo lectura, que debe satisfacer el analizador estático que el campo no se establece en ninguna otra cosa después se ejecuta la ctor.

+0

Marcar 'bar' como de solo lectura no tiene ningún efecto. Por alguna razón, el analizador no parece darse cuenta de que estoy asignando 'bar' a un valor no nulo en el constructor. – dtb

2

El código incluye una instancia inicializado estática privada:

private static Foo instance = new Foo(); 

Tiene pensado que esto significa que la instancia constructor siempre tendrá correr antes de acceder a cualquier método estático, por lo tanto, asegurar bar se ha inicializado?

En el caso de un solo subproceso, creo que tienes razón.

La secuencia de eventos sería:

  1. llamada a Foo.BarLength()
  2. inicialización estática de la clase Foo (si no se ha completado)
  3. inicialización estática del miembro estático privada instance con instancia de Foo
  4. entrada a Foo.BarLength()

Sin embargo, la inicialización estática de una clase es sólo alguna vez desencadena una vez por aplicación de dominio - y IIRC no hay bloqueo para asegurarse de que es completaron antes se llaman otros métodos estáticos.

Por lo tanto, usted podría tener este escenario:

  1. Tema Alfa: Llamada a Foo.BarLength()
  2. Tema Alfa: inicialización estática de la clase Foo (si no se ha completado) comienza
  3. cambio de contexto
  4. Subproceso Beta: llame al Foo.BarLength()
  5. Subproceso Beta: No Llamar de inicialización estática de la clase Foo porque eso es ya en marcha
  6. rosca Beta: Entrada a Foo.BarLength()
  7. rosca Beta: El acceso a null miembro estático instance

No hay manera de que el analizador contratos puede saber que nunca ejecutarías el código de manera multiproceso, por lo que tiene que pecar de cauteloso.

+0

Correcto, pero como dices (paso 7), 'instancia' sería nulo, no' instancia.bar'. 'instance.bar' solo es nulo antes de que se asigne en el constructor de la instancia de Foo, pero la instancia solo se almacena en el campo estrictamente después de que se haya completado el constructor. – dtb

+0

De todos modos, es obviamente una limitación del analizador estático. Ahora estoy buscando la mejor manera de convencerlo de que estoy en lo correcto. Tirar basura a mi código con las declaraciones 'Assume' o con verificaciones que nunca pueden ocurrir se siente simplemente sucio. – dtb

+0

Sí, CLR se asegura de que el constructor estático se complete antes de que se haga referencia a cualquier otra cosa en esa clase, a menos que tenga dos clases con constructores estáticos que se refieran entre sí: http://msdn.microsoft.com/en-us/library/ aa645612.aspx – EMP

Cuestiones relacionadas