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

Разница между доходностью и List.AsEnumerable

Доходность - это то, что мне трудно понять до сих пор. Но теперь я держусь за это. Теперь, в проекте, если я верну List, анализ кода Microsoft даст предупреждение об этом. Поэтому, как правило, я буду делать все необходимые логические части и вернуть список как IEnumerable. Я хочу знать разницу между ними. Означает ли я, что я возвращаю доход или иначе.

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

private static IEnumerable<int> getIntFromList(List<int> inputList)
{
    var outputlist = new List<int>();
    foreach (var i in inputList)
    {
        if (i %2 ==0)
        {
            outputlist.Add(i);
        }
    }

    return outputlist.AsEnumerable();
}

private static IEnumerable<int> getIntFromYeild(List<int> inputList)
{
    foreach (var i in inputList)
    {
        if (i%2 == 0)
        {
            yield return i;
        }
    }
}

Одно существенное преимущество, которое я вижу, - это меньше строк. Но есть ли другая польза? Должен ли я изменять и обновлять мои функции, которые возвращают IEnumearble для использования yield вместо List? Каков наилучший способ или лучший способ сделать что-то?

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

4b9b3361

Ответ 1

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

return outputlist;

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

Самый простой способ показать разницу - это, вероятно, поставить вызов Console.WriteLine внутри оператора if (i % 2 == 0):

Console.WriteLine("Got a value to return: " + i);

Затем, если вы также поместите вызов Console.WriteLine в код клиента, например

foreach (int value in getIntFromList(list))
{
    Console.WriteLine("Received value: " + value);
}

... вы увидите, что с первым кодом вы сначала видите все строки "Получили значение", а затем все строки "Полученное значение". С помощью блока итератора вы увидите, что они чередуются.

Теперь представьте, что ваш код действительно делает что-то дорогое, и ваш список очень длинный, и клиенту нужны только первые 3 значения... с вашим первым кодом вы будете загружать ненужную работу. С ленивым подходом вы выполняете столько же работы, сколько вам нужно, в режиме "как раз вовремя". Второй подход также не требует буферизации всех результатов в памяти - опять же, если список ввода очень велик, вы также получите большой список результатов, даже если вы хотите использовать только одно значение в время.

Ответ 2

Ключевой момент yield return заключается в том, что он не буферизирован; блок итератора является конечным автоматом, который возобновляется по мере повторения данных. Это делает его удобным для очень больших источников данных (или даже бесконечных списков), поскольку вы можете избежать наличия огромного списка в памяти.

Ниже приведен полностью корректный итератор-блок, который можно успешно повторить:

Random rand = new Random();
while(true) yield return rand.Next();

и мы можем делать такие вещи, как:

for(int i in TheAbove().Take(20))
    Console.WriteLine(i);

Хотя очевидно, что все, что итеративно до конца (например, Count() и т.д.), будет работать вечно без конца - не отличная идея.

В вашем примере код, вероятно, слишком сложный. Версия List<int> может быть просто:

return new List<int>(inputList);

Тип yield return зависит от того, что вы хотите сделать: в самом простом, это может быть просто:

foreach(var item in inputList) yield return item;

хотя очевидно, что все равно будет смотреть исходные данные: изменения в inputList могут привести к поломке итератора. Если вы думаете, что "это хорошо", то, честно говоря, вы могли бы просто:

return inputList;

Если это не так, в этом случае блок итератора немного перебор, а:

return new List<int>(inputList);

должно быть достаточно.

Для полноты: AsEnumerable просто возвращает исходный источник, тип cast; это:

return inputList;

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

return someList.AsEnumerable(); // so they can only iterate it, not Add

то это будет не; злой вызывающий может все еще просто сделать:

var list = (IList<int>) theAbove;
int mwahaahahaha = 42;
list.Add(mwahaahahaha);

Ответ 3

Большая разница: второй (выход) создает меньше мусора памяти. Первые в основном создают копию списка в памяти.

Большая разница: если вызывающий пользователь манипулирует исходным списком в примере 2, он сломается, в примере 1 он не будет (из-за повторения копии).

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

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

Ответ 4

Разница во времени выполнения.

В первом примере код в вашей функции выполняется до выхода функции. Весь список создается, а затем возвращается как IEnumerable.

Во втором примере код в функции фактически не запускается, когда функция завершается. Вместо этого, когда функция выходит из нее, она возвращает IEnumerable, и когда вы позже выполняете итерацию этого IEnumerable, это происходит при выполнении кода.

В частности, если вы только перебираете первые 3 элемента IEnumerable во втором примере, цикл for будет только повторять достаточно времени, чтобы получить три элемента и не более.

Ответ 5

Когда вы используете yield, компилятор генерирует код шаблона итератора, который будет работать быстрее, чем предварительно сгенерированный список. Это примерно так:

namespace Yield
{
    class UserCollection
    {
        public static IEnumerable Power()
        {
            return new ClassPower(-2);
        }

        private sealed class ClassPower : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable
        {

            private int state;
            private object current;
            private int initialThreadId;

        public ClassPower(int state)
        {
            this.state = state;
            this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        bool IEnumerator.MoveNext()
        {
            switch (this.state)
            {
                case 0:
                    this.state = -1;
                    this.current = "Hello world!";
                    this.state = 1;
                    return true;

                case 1:
                    this.state = -1;
                    break;
            }
            return false;
        }

        IEnumerator<object> IEnumerable<object>.GetEnumerator()
        {
            if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) && (this.state == -2))
            {
                this.state = 0;
                return this;
            }
            return new UserCollection.ClassPower(0);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {       
            return (this as IEnumerable<object>).GetEnumerator();
        }

        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

        void IDisposable.Dispose()
        {
        }

        object IEnumerator<object>.Current
        {
            get
            {
                return this.current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.current;
            }
        }
    }
}

}