2009-07-02 17 views
7

El siguiente código falla en la condición previa. ¿Es esto un error en los contratos de código?Error en iteradores con contratos de código?

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

Respuesta

2

Supongo que esto tiene que ver con la naturaleza retrasada de los iteradores. Recuerde, el procesamiento del contrato ocurrirá en la IL final emitida, no en el código C#. Esto significa que debe considerar el código generado para características como iteradores y expresiones lambda.

Si descompila ese código, encontrará que "i" no es realmente un parámetro. Será una variable en la clase que se usa para implementar el iterador. Así que el código en realidad se parece más a la siguiente

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

No estoy terriblemente familiar con la API de contrato, pero yo creo que es el código generado es mucho más difícil de verificar.

+0

¿Por qué debería estar el Requires on the MoveNext en lugar del contructor de IteratorImpl? –

+0

@pn, así es como el equipo C# eligió implementar iteradores. Cualquier código que aparezca en el cuerpo de un iterador terminará en el método MoveNext del código generado. – JaredPar

+0

Mi pregunta es si esto es un error en los contratos de código o no. Parece que el escritor de código de contrato re no entiende los iteradores. –

0

Recuerde que los iteradores no se ejecutan hasta que se enumeran, y se compilan en una salsa especial en el back-end. El patrón general que debe seguir si desea validar los parámetros, y esto probablemente es verdad para los contratos, es tener una función de contenedor:

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

que prueban direcciones() va a hacer la comprobación de los parámetros cuando está llamado luego return _Test(), que en realidad solo devuelve una nueva clase.

+0

Esto es una solución. Sin embargo, ¿debería cambiar mi código o es un error que se solucionará? –

+0

Esta es la forma en que funcionan los iteradores: C# no va a cambiar este comportamiento. Si necesita verificar los parámetros del método y desea hacerlo en la invocación, no en la enumeración, debe envolverlo en un segundo método que haga la verificación. No sé acerca de los contratos o si se arreglarán para trabajar mejor con los iteradores. – Talljoe

0

Aquí hay un blog post relacionado con este mismo tema relacionado con las pruebas unitarias, los iteradores, la ejecución diferida y usted.

La ejecución retrasada es el problema aquí.

0

Este código funcionará con la versión final del .NET 4.0 (solo probado), donde Code Contracts en interators son compatibles, pero como descubrí hace poco no siempre funciona correctamente (leer más here).

0

Esto puede haber sido un problema en el reescritor de CodeContract en el pasado. Pero la versión actual parece funcionar bien en tu ejemplo. No hay problema aquí con los iteradores/evaluación retrasada, etc. El parámetro i se captura por valor y no cambiará durante la iteración. Los contratos deben verificar esto solo al comienzo de la llamada a Prueba, no durante cada iteración.

Cuestiones relacionadas