Предварительные условия для перечислимых - PullRequest
3 голосов
/ 18 октября 2011

Учитывая

void ProcessSchedules(IEnumerable<Schedule> schedules)
{
    Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today))
    foreach( var schedule in schedules )
    {
       // Do something with schedule
    }
}

В этом случае расписания будут перечислены дважды.(Resharper дает мне предупреждение). Альтернативой, которую я делаю для большинства сценариев, является принудительное создание коллекции в списке в начале метода, но, учитывая характер контрактов кода, никакой другой код не должен предшествовать предварительному условию.Какой лучший способ справиться с этим?

Ответы [ 3 ]

2 голосов
/ 18 октября 2011

У вас есть как минимум две возможности:

  1. Не обращайте внимания на предупреждение ReSharper, если вы уверены, что двойное перечисление перечислимого не укусит вас, возможно, потому, что это внутренний метод, и вы знаете и когда-нибудь узнаете всех вызывающих.

  2. Создать перегрузку ProcessSchedules, которая принимает List<Schedule>:

    void ProcessSchedules(IEnumerable<Schedule> schedules)
    {
        Contract.Requires(schedules != null);
        ProcessSchedules(schedules.ToList());
    }
    
    void ProcessSchedules(List<Schedule> schedules)
    {
        Contract.Requires<ArgumentException>(Contract.ForAll(schedules,
                                                             x => x.Date <= 
                                                             DateTime.Today));
        foreach( var schedule in schedules )
        {
            // Do something with schedule
        }
    }
    

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

1 голос
/ 20 октября 2011

Вот варианты, которые я могу представить в порядке убывания моего личного предпочтения.

  • Используйте статическую проверку, а не проверку во время выполнения.Таким образом, для проверки не потребуется вообще перечислять список.Этот вариант имеет ограничение, что он чрезвычайно дорогой.У меня нет доступа к статическому контролеру, так как я бы с удовольствием.Таким образом, в качестве альтернативы, вы могли бы ...

  • Изменить определение метода, чтобы оно работало ОК в присутствии будущих дат, так что проверка не требуется.

Примерно так:

void ProcessSchedules(IEnumerable<Schedule> schedules) 
{ 
    Contract.Requires<ArgumentNullException>(schedules != null);
    foreach(var schedule in schedules ) 
    {
        if (schedule.Date <= DateTime.Today)
        { 
           // Do something with schedule 
        }
    } 
} 

Если это не имеет смысла для вашей программы, тогда вы могли бы ...

  • Изменить определение метода для принятиясписок в первую очередь.

Вот так:

void ProcessSchedules(List<Schedule> schedules) 
{ 
    Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today)) 
    foreach(var schedule in schedules ) 
    { 
       // Do something with schedule 
    } 
} 

Вы уже сказали, что принудительное включение его в список будет вариантом, поэтому ясно, что это конечная последовательностьприемлемого размера.Существует высокая вероятность того, что вызывающая сторона использует List<Schedule> в первую очередь или что есть другие места, где эта реализация будет полезна.

Если вы действительно хотите сохранить IEnumerable в своемподпись вы могли бы ...

  • Просто проигнорируйте предупреждение.

С вашим исходным кодом:

void ProcessSchedules(IEnumerable<Schedule> schedules) 
{ 
    Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today)) 
    foreach( var schedule in schedules ) 
    { 
       // Do something with schedule 
    } 
} 

Я серьезно не вижупроблема в перечислении списка дважды, и я даже не уверен, почему Resharper выдаст предупреждение за это.

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

Если перечисление расписаний считывает данные из базы данных или что-то в этом роде, то контракт в любом случае недействителен, поскольку данные могут изменяться между проверкой иобработка.В этом случае было бы правильнее, если бы метод принимал список или другую кэшированную копию данных, которые не будут видоизменяться.

1 голос
/ 18 октября 2011

Кто-то убьет меня, я знаю: -)

void ProcessSchedules(IEnumerable<Schedule> schedules)
{
    Contract.Requires<ArgumentException>(Contract.ForAll((schedules = schedules.ToList()),x => x.Date <= DateTime.Today))

    // now schedules is a List<Schedule> :-) Note that the reference is still a 
    // IEnumerable<Schedule>, but you can cast it, like:

    List<Schedule> schedules2 = (List<Schedule>)schedules;

    foreach( var schedule in schedules )
    {
       // Do something with schedule
    }
}

Это работает , потому что :

Результатом простого выражения присваивания являетсязначение, присвоенное левому операнду.Результат имеет тот же тип, что и левый операнд, и всегда классифицируется как значение.

Я добавлю, что я создал бы метод расширения, который проверяет, является ли переданный параметр уже List<T>или Array<T>, чтобы не создавать его вторую копию.Что-то вроде:

public static IList<T> Materialize<T>(this IEnumerable<T> enu)
{
    if (enu is IList<T>)
    {
        return (IList<T>)enu;
    }
    else
    {
        return enu.ToList();
    }
}

Очевидно, что вы можете расширить, чтобы проверить, является ли enu уже IList<T> вместо "чистого" IEnumerable<T>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...