Проблема с синтаксисом IEnumerable ‹T› метод с использованием yield return

Вот мой метод:

static IEnumerable<DateTime> GetMonths(DateTime from, DateTime to)
{
    // if logs is not uptodate
    TimeSpan logsMissingTimespan = to - from;

    if (logsMissingTimespan != new TimeSpan(0))
    {
        return GetMonthsBetweenTwoDates(from, to);
    }

    return null; // Why this line ?
}

private static IEnumerable<DateTime> GetMonthsBetweenTwoDates(DateTime from, DateTime to)
{

    DateTime date = from;
    DateTime lastDate = DateTime.MaxValue;

    while (date < to)
    {
        if (lastDate.Month != date.Month)
        {
            lastDate = date;
            yield return lastDate;
        }
        date = date.AddDays(1);
    }
}

он работает нормально, но я думаю, что могу написать что-нибудь более чистое вроде этого:

static IEnumerable<DateTime> GetMonths(DateTime from, DateTime to)
{
    TimeSpan logsMissingTimespan = to - from;

    if (logsMissingTimespan == new TimeSpan(0))
    {
        yield break;
    }

    return GetMonthsBetweenTwoDates(from, to);
}

Но у меня появляется сообщение об ошибке:

Невозможно вернуть значение из итератора. Используйте оператор yield return, чтобы вернуть значение, или yield break, чтобы завершить итерацию.

Зачем мне return null и каков правильный синтаксис?

РЕДАКТИРОВАТЬ:

Итак, правильный способ - использовать Enumerable.Empty:

static IEnumerable<DateTime> GetMonths(DateTime from, DateTime to)
{
    // if logs is not uptodate
    TimeSpan logsMissingTimespan = to - from;

    if (logsMissingTimespan != new TimeSpan(0))
    {
        return GetMonthsBetweenTwoDates(from, to);
    }

    return Enumerable.Empty<DateTime>();
}

person Florian    schedule 29.11.2011    source источник
comment
Было бы лучше, если бы он вернул Enumerable.Empty (), потому что в настоящее время вызывающий код должен различать пустые и непустые результаты   -  person Damien_The_Unbeliever    schedule 29.11.2011
comment
возможный дубликат доходности с нулевым значением   -  person nawfal    schedule 19.02.2013


Ответы (4)


Формы ваших первых двух примеров дают разные виды вывода.

Ваш первый пример возвращает IEnumerable<T> напрямую, если условие выполнено, и нулевую ссылку, если это не так. Ваш второй пример always возвращает IEnumerable<T>, но условие определяет, есть ли в нем какие-либо элементы.

Второй пример выполняется с использованием блока итератора. Синтаксис yield используется компилятором C # для преобразования написанной вами функции в пользовательский (скрытый) тип, реализующий IEnumerable<T>, и тип, реализующий IEnumerator<T>. Эти типы реализуют необходимые конечные автоматы для достижения (надеюсь) логики, которую вы поместили в функцию. Из-за этого нельзя смешивать парадигмы; вы должны либо вернуть экземпляр IEnumerable<T> из функции (и вообще нигде не использовать yield), либо все должно быть возвращено через yield.

Если вас беспокоит только тот факт, что вы возвращаете пустую ссылку, вы можете сделать методы семантически одинаковыми, вернув Enumerable.Empty<DateTime>, а не null.

person Adam Robinson    schedule 29.11.2011

Поскольку вы использовали слово yield, теперь ожидается, что метод будет выдавать по одному элементу за раз. Он должен использовать только yeild return или yield break для возврата одного элемента за итерацию.

Вы должны использовать Enumerable.Empty<DateTime>(); вместо yield break.

person DaveShaw    schedule 29.11.2011

Метод либо реализован с блоком итератора, либо нет - так что либо все выражается в терминах yield return и yield break, либо ничего из этого нет.

Однако от вас не требуется делать ничего особенного. Ваш исходный GetMonthsBetweenTwoDates уже работает там, где to == from, потому что он никогда не войдет в цикл while.

С другой стороны, ваше использование lastDate кажется мне подозрительным - в частности, похоже, что он будет делать что-то другое, если from будет в том же месяце, что и DateTime.MaxValue.

person Jon Skeet    schedule 29.11.2011
comment
Пример глупый, он просто для иллюстрации моей цели :-) - person Florian; 29.11.2011
comment
@Florian: Тогда я предлагаю вам выбрать лучший пример в будущем :) Серьезно, это помогает, если он отражает то, что вы на самом деле пытаетесь сделать, так как вполне может быть лучший подход. - person Jon Skeet; 29.11.2011
comment
Хорошо, я приму ваше внимание на будущее. - person Florian; 29.11.2011
comment
Даже если я думаю, что никогда не буду использовать stackoverflow, чтобы задавать вопросы, потому что очень скоро получу очень хорошую книгу [1] :-) [1] manning.com/skeet2 - person Florian; 29.11.2011
comment
@ Флориан: Джон не упоминает, что его книги - это всего лишь результат сложного искусственного интеллекта, который собирает ответы от SO;) - person Adam Robinson; 29.11.2011
comment
@ Адам ... Действительно, очень умно ^^ Подпрограмма, возвращающая IEnumerable ‹SOQuestions›? : D - person Florian; 29.11.2011

вам не нужен разрыв доходности, чтобы завершить итерацию при первой проверке.

if (logsMissingTimespan == new TimeSpan(0))
{
    return null;
}    
return GetMonthsBetweenTwoDates(from, to);
person Turbot    schedule 29.11.2011