Ошибка в итераторах с кодами контрактов?
Следующий код не выполняется при предварительном условии. Это ошибка в коде контрактов?
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(), который фактически просто возвращает новый класс.