Ошибка в итераторах с кодами контрактов?

Следующий код не выполняется при предварительном условии. Это ошибка в коде контрактов?

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

5 ответов

Я думаю, это связано с задержкой итераторов. Помните, что обработка контракта будет происходить в конечном выданном IL, а не в коде C#. Это означает, что вы должны учитывать сгенерированный код для таких функций, как итераторы и лямбда-выражения.

Если вы декомпилируете этот код, вы обнаружите, что "i" на самом деле не является параметром. Это будет переменная в классе, которая используется для реализации итератора. Таким образом, код на самом деле выглядит следующим образом

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

Я не очень знаком с контрактным API, но думаю, что сгенерированный код гораздо сложнее проверить.

Вот сообщение в блоге, относящееся к этой теме, касающееся модульного тестирования, итераторов, отложенного выполнения и вас.

Задержка исполнения является проблемой здесь.

Это могло быть проблемой в переписывающем устройстве CodeContract в прошлом. Но текущая версия, кажется, хорошо работает на вашем примере. Здесь нет проблем с итераторами / отложенной оценкой и т. Д. Параметр i фиксируется по значению и не изменится во время итерации. Контракты должны проверять это только в начале вызова Test, а не во время каждой итерации.

Этот код будет работать с окончательной версией.NET 4.0 (только что попробовал), где поддерживаются контракты кода в интеграторах, но, как я недавно выяснил, он не всегда работает должным образом (подробнее здесь).

Помните, что итераторы не запускаются до тех пор, пока они не перечислены, и скомпилированы в какой-то особый соус в конце. Общий шаблон, которому вы должны следовать, если хотите проверить параметры, и это, вероятно, справедливо для контрактов, состоит в том, чтобы иметь функцию-обертку:

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

Таким образом, Test() будет проверять параметры при вызове, а затем возвращает _Test(), который фактически просто возвращает новый класс.

Другие вопросы по тегам