2010-04-20 12 views
7

Ho una discussione con lo CodeContracts static analysis tool.CodeContracts: possibile chiamare un metodo su un riferimento null

Il mio codice:

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

(ASCII version)

Lo strumento mi dice che instance.bar può essere un riferimento null. Credo il contrario.

Chi ha ragione? Come posso dimostrare che è sbagliato?

+2

fuori tema, ma potreste dirmi cosa Visual Studio tema che si sta utilizzando? – Justin

+1

@Justin: solo il tema predefinito vs2010 con la mia combinazione di colori di testo personalizzata (vedi post modificato). Perché stai chiedendo? – dtb

+0

Ho appena cercato di cambiare i miei schemi di colori dello studio visivo ultimamente e mi piace molto. Mi stavo chiedendo se lo avessi scaricato da qualche parte. – Justin

risposta

2

Aggiornamento: Sembra che il problema sia invariants are not supported for static fields.

Secondo aggiornamento: Il metodo descritto di seguito è currently the recommended solution.

Una possibile soluzione è creare una proprietà per instance che Ensure s gli invarianti che si desidera conservare. (Naturalmente, è necessario effettuare il Assume per il Ensure da provare.) Una volta fatto ciò, si può semplicemente usare la proprietà e tutti gli invarianti dovrebbero essere provati correttamente.

Ecco il vostro esempio di utilizzo di questo metodo:

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

Grazie per aver indagato su questo. Sentiti libero di postarlo sul forum MSDN se ti interessa. Ho smesso di usare il controllo statico per ora perché una revisione del codice attualmente produce risultati migliori per me. – dtb

+0

Ho trovato un post sul forum che afferma che gli invarianti non sono supportati su campi statici. Ho aggiornato il mio post con una soluzione alternativa. – porges

+0

Bella scoperta. Ma 'bar' è un campo istanza, non statico !? Ad ogni modo, ho spostato il segno di spunta su questa risposta perché è la migliore spiegazione finora. – dtb

2

CodeContracts ha ragione. Non c'è nulla che ti impedisca di impostare instance.bar = null prima di chiamare il metodo BarLength().

+2

Ma non sto impostando 'instance.bar = null' ovunque e' instance.bar' è privato, quindi non può essere nullo, giusto? – dtb

+0

Sì, quindi suppongo che tu abbia ragione se questo è l'intero codice. La sua analisi statica probabilmente non è abbastanza intelligente da capire se si imposta il valore su null o no, quindi si presume che si possa. Non so a quali lunghezze si debba verificare, se ce ne sono. – EMP

+0

Come faccio a convincerlo che 'instance.bar' non è mai' null'? Rendendo 'bar' di sola lettura e aggiungendo' Contract.Ensures (bar! = Null); 'al costruttore non fa il trucco. 'Contract.Assume (instance.bar! = Null);' in 'BarLength()' funziona ma sembra brutto. – dtb

-1

Sono d'accordo con te. instance e bar sono entrambi privati, quindi CodeContracts dovrebbe essere in grado di sapere che instance.bar non è mai impostato su null.

0

Se si contrassegna il campo 'bar' come readonly, questo dovrebbe soddisfare l'analizzatore statico che il campo non sarà mai impostato su qualcos'altro dopo l'esecuzione del ctor.

+0

Contrassegnare 'bar' come di sola lettura non ha alcun effetto. Per qualche ragione, l'analizzatore non sembra rendersi conto che sto assegnando 'bar' a un valore non nullo nel costruttore. – dtb

2

Il codice include un'istanza privata statica inizializzata:

private static Foo instance = new Foo(); 

Stai assumendo che questo significa l'istanza costruttore avrà sempre eseguito prima che l'accesso a qualsiasi metodo statico, quindi, garantendo bar è stato inizializzato?

Nel caso a thread singolo, penso che tu abbia ragione.

La sequenza degli eventi sarebbe:

  1. Chiama per Foo.BarLength()
  2. inizializzazione statico della classe Foo (se non è già completato)
  3. inizializzazione statico di membro statico privato instance con istanza di Foo
  4. Immissione a Foo.BarLength()

Tuttavia, l'inizializzazione statica di una classe viene attivata solo una volta per dominio App e IIRC non prevede alcun blocco per garantire che sia completato prima di chiamare qualsiasi altro metodo statico.

Quindi, si potrebbe avere questo scenario:

  1. Alpha Discussione: Call to Foo.BarLength()
  2. Discussione Alpha: l'inizializzazione statico della classe Foo (se non è già completato) inizia
  3. Context switch
  4. Thread Beta: Chiama a Foo.BarLength()
  5. Beta: No Call di inizializzazione statico della classe Foo perché è già in corso
  6. Discussione Beta: Entrata al Foo.BarLength()
  7. Beta Discussione: L'accesso a null membro statico instance

Non c'è modo l'analizzatore contratti possono sapere che non avresti mai eseguito il codice in un modo multithread, quindi deve errare sul lato della cautela.

+0

Giusto, ma come dici tu (passaggio 7), 'instance' sarebbe nullo, non' instance.bar'. 'instance.bar' è null solo prima di essere assegnato nel costruttore dell'istanza di Foo, ma l'istanza viene memorizzata nel campo solo dopo che il costruttore è stato completato. – dtb

+0

In ogni caso, è ovviamente una limitazione dell'analizzatore statico. Ora sto cercando il modo migliore per convincerlo che ho ragione. Sporcare il mio codice con le affermazioni "Assume" o con assegni che non possono mai accadere sembra solo sporca. – dtb

+0

Sì, il CLR garantisce che il costruttore statico venga completato prima che venga fatto riferimento a qualsiasi altro elemento di quella classe, a meno che non si abbiano due classi con costruttori statici che fanno riferimento l'un l'altro: http://msdn.microsoft.com/en-us/library/ aa645612.aspx – EMP

Problemi correlati