Подтвердить что ты не робот

Является ли разрыв доход эквивалентным возврату Перечислимый <T>.Empty из метода, возвращающего IEnumerable <T>

Кажется, что эти два метода ведут себя одинаково для меня

public IEnumerable<string> GetNothing()
{
    return Enumerable.Empty<string>();
}

public IEnumerable<string> GetLessThanNothing()
{
    yield break;
}

Я профилировал каждый в тестовых сценариях, и я не вижу значимой разницы в скорости, но версия yield break немного быстрее.

Есть ли причины использовать один над другим? Легче читать, чем другой? Есть ли разница в поведении, которая имеет значение для вызывающего?

4b9b3361

Ответ 1

Если вы намереваетесь всегда возвращать пустое перечисление, то использование синтаксиса Enumerable.Empty<string>() является более декларативным IMHO.

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

Ответ 2

IEnumerable<T> методы с yield break или yield return в их телах трансформируются в конечные машины. В таких методах вы не можете смешивать доходность доходности с традиционными доходами. Я имею в виду, что если вы что-то уступите в какой-то части метода, вы не сможете вернуть ICollection в другой.

С другой стороны, предположим, что вы реализуете метод с типом возвращаемого значения IEnumerable<T>, добавляя элементы в коллекцию, а затем возвращаете исходную копию коллекции. Если по какой-то причине вы хотите просто вернуть пустую коллекцию, вы не можете сделать yield break. Все, что вы можете сделать, это просто вернуть Enumerable.Empty<T>().

Если вы профилировали оба пути, и нет существенных изменений, вы можете просто забыть об этом:)

Ответ 3

Забавно, я прочитал этот пост этим утром, и несколько часов спустя меня поразил этот пример - нашел разницу, когда у вас больше кода:

public static IEnumerable<T> CoalesceEmpty<T>(IEnumerable<T> coll)
{
    if (coll == null)
        return Enumerable.Empty<T>();
    else
         return coll;
}

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

Ответ 4

Я тестировал каждый в тестовых сценариях, и я не вижу значимой разницы в скорости, но версия выхода на выход немного быстрее.

Я собираюсь предположить, что ваши профилирующие тесты не включали скорость запуска программы. Конструкция yield работает, создавая для вас класс. Этот дополнительный код велик, когда он обеспечивает необходимую логику, но если нет, он просто добавляет к дисковым вводам-выводам, размеру рабочего набора и времени JIT.

Если вы откроете программу, содержащую ваши методы тестирования, в ILSpy и отключите декомпиляцию перечислителя, вы найдете класс с именем <GetLessThanNothing>d__0 с дюжиной членов. Его метод MoveNext выглядит следующим образом:

bool IEnumerator.MoveNext()
{
    int num = this.<>1__state;
    if (num == 0)
    {
        this.<>1__state = -1;
    }
    return false;
}

EmptyEnumerable работает при ленивом создании статического пустого массива. Возможно, проверка того, нужен ли массив для создания, - это причина, по которой EmptyEnumerable медленнее, чем yield break, в изолированном бенчмаркинге, но, вероятно, потребуется много итераций для преодоления штрафа за запуск, и в любом случае вряд ли будет заметным в целом, даже в сценарии "смерти на тысячу перфарпуц".

Ответ 5

Казалось бы, yield break создаст экземпляр хотя бы одного меньшего объекта, чем это сделает return Enumerable.Empty<string>(). Кроме того, могут быть некоторые проверки, которые вы бы закорачивали с помощью yield break. И если ничего другого, это еще одна функция-обертка, через которую проходит ваш стек, который будет обнаруживаться, хотя и не заметен.

Однако я согласен с тем, что другой ответ опубликовал, что .Empty - это "предпочтительный" способ сделать это.