2009-07-02 7 views
7

Il seguente codice non funziona nella pre condizione. Si tratta di un bug nei contratti di codice?Bug in iteratori con contratti di codice?

static class Program 
{ 
    static void Main() 
    { 
     foreach (var s in Test(3)) 
     { 
      Console.WriteLine(s); 
     } 
    } 

    static IEnumerable<int>Test (int i) 
    { 
     Contract.Requires(i > 0); 
     for (int j = 0; j < i; j++) 
      yield return j; 
    } 
} 

risposta

2

La mia ipotesi è che questo abbia a che fare con la natura ritardata degli iteratori. Ricorda che l'elaborazione del contratto avverrà sull'IL finale emesso, non sul codice C#. Ciò significa che devi considerare il codice generato per funzioni come iteratori e espressioni lambda.

Se decompilate quel codice, troverete che "i" non è in realtà un parametro. Sarà una variabile nella classe che viene utilizzata per implementare l'iteratore. Quindi il codice in realtà sembra più simile al seguente

class IteratorImpl { 
    private int i; 
    public bool MoveNext() { 
    Contract.Require(i >0); 
    .. 
    } 
} 

Io non sono terribilmente familiarità con l'API contratto, ma la mia ipotesi è il codice generato è molto più difficile da verificare.

+0

Perché le richieste devono essere su MoveNext anziché sul contructor di IteratorImpl? –

+0

@pn, questo è il modo in cui il team C# ha scelto di implementare gli iteratori. Qualsiasi codice che appare nel corpo di un iteratore finirà nel metodo MoveNext del codice generato. – JaredPar

+0

La mia domanda è se si tratta di un bug nei contratti di codice o meno. Sembra che il redattore del contratto di codice non comprenda gli iteratori. –

0

Ricorda che gli iteratori non vengono eseguiti finché non vengono enumerati e vengono compilati in una salsa speciale nel back-end. Lo schema generale si dovrebbe seguire se si desidera convalidare i parametri, e questo probabilmente vale per i contratti, è quello di avere una funzione wrapper:

static IEnumerable<int> Test (int i) 
{ 
    Contract.Requires(i > 0); 
    return _Test(i); 
} 

private static IEnumerable<int> _Test (int i) 
{ 
    for (int j = 0; j < i; j++) 
     yield return j; 
} 

quel test vie() farà il controllo dei parametri di quando è chiamato quindi return _Test(), che in realtà restituisce solo una nuova classe.

+0

Questo è un rimedio. Tuttavia, dovrei cambiare il mio codice o si tratta di un bug che verrà risolto? –

+0

Questo è il modo in cui gli iteratori funzionano: C# non cambierà questo comportamento. Se è necessario verificare i parametri del metodo e si desidera farlo invocando non all'enumerazione, è necessario avvolgerlo in un secondo metodo che esegue il controllo. Non conosco i Contratti o se si sistemeranno per lavorare meglio con gli iteratori. – Talljoe

0

Ecco uno blog post relativo a questo argomento molto riguardante test delle unità, iteratori, esecuzione ritardata e voi.

L'esecuzione ritardata è il problema qui.

0

Questo codice funzionerà con la versione finale di .NET 4.0 (solo provato) dove Code Contracts in interators sono supportati, ma come ho scoperto di recente che non sempre funziona correttamente (leggi tutto here).

0

Questo potrebbe essere stato un problema nel redattore CodeContract in passato. Ma la versione attuale sembra fare bene sul tuo esempio. Qui non ci sono problemi con iteratori/valutazione ritardata ecc. Il parametro i viene catturato dal valore e non cambierà durante l'iterazione. I contratti devono essere verificati solo all'inizio della chiamata a Test, non durante ogni iterazione.