Передача Enumerables vs. Lists в View имеет огромную разницу в производительности. - PullRequest
4 голосов
/ 20 апреля 2020

В двух словах, у меня есть представление, которое принимает:

@model IEnumerable<MyModel>

и перебирает модель, создавая таблицу, используя:

    @for (int i = 0; i < Model.Count(); i++)
    {
        <tr id="tblRow_@(Model.ElementAt(i).Id)">
            <td width="1">
                @Html.DisplayFor(m => m.ElementAt(i).LoggedInUser)
            </td>
            <td width="1" class="date">
                @Html.DisplayFor(m => m.ElementAt(i).DateCreated)
            </td>
        </tr>
    }

Так в контроллере Я передаю x представлению, которое является либо:

var x = new DAL().GetList(); // returns IEnumerable<MyModel>

, либо

var x = new DAL().GetList().ToList(); 

Передача 1-го (IEnumerable) происходит в ожидании медленнее, чем передача 2-го (уже преобразованного в список) )

Почему?

Я предполагаю, что это как-то связано с Model.Count () и что, возможно, ему нужно преобразовать IEnumerable в List для каждого цикла, но даже с всего лишь 100 записями разница в скорости изменяется от почти мгновенной до примерно 8 секунд.

1 Ответ

8 голосов
/ 20 апреля 2020

TL; DR: используйте foreach вместо. См. Код внизу.

Это вообще не совсем ASP. NET - это способ, которым IEnumerable<T> и методы расширения на нем работают, в частности в отношении лениво созданных последовательностей.

Когда вы вызываете ToList() для последовательности, это создает List<T>, запрашивая каждый элемент исходной последовательности - но после этого вы можете делать все (доступ по индексу, подсчет и т. д. c), не обращаясь к последовательности вообще. Мало того, что не нужно обращаться к последовательности, но LINQ имеет оптимизации для ElementAt() и Count(), когда они вызываются на IList<T> реализации.

Если вы вызываете Count() на IEnumerable<T>, который не реализует IList<T> (или любой из нескольких других полезных интерфейсов), он должен выполнить l oop через всю последовательность из начало, пока не дойдет до конца. Если последовательность вычисляется лениво (например, с использованием блока итератора с операторами yield, это означает, что нужно снова выполнять работу.

ElementAt() аналогично, за исключением того, что не нужно добираться до самого конца - оно просто нужно добраться до указанного элемента.

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

using System;
using System.Collections.Generic;
using System.Linq;

class Test
{
    static void Main()
    {
        var sequence = CreateSequence();
        ConsumeList(sequence);
        ConsumeSequence(sequence);
   }

    static void ConsumeList(IEnumerable<int> sequence)
    {
        Console.WriteLine("Start of ConsumeList");
        var list = sequence.ToList();
        Console.WriteLine("ToList has completed - iterating");
        for (int i = 0; i < list.Count(); i++)
        {
            var element = list.ElementAt(i);
            Console.WriteLine($"Element {i} is {element}");
        }
        Console.WriteLine("End of ConsumeList");
        Console.WriteLine();
    }

    static void ConsumeSequence(IEnumerable<int> sequence)
    {
        Console.WriteLine("Start of ConsumeSequence");
        var list = sequence.ToList();
        for (int i = 0; i < sequence.Count(); i++)
        {
            var element = sequence.ElementAt(i);
            Console.WriteLine($"Element {i} is {element}");
        }
        Console.WriteLine("End of ConsumeSequence");
    }

    static IEnumerable<int> CreateSequence()
    {
        for (int i = 0; i < 5; i++)
        {
            var value = i * 2;
            Console.WriteLine($"Yielding {value}");
            yield return value;
        }
    }
}

Это не означает, что вам нужно звонить ToList(), хотя - весь ваш l oop можно переписать, чтобы избежать использования Count() и ElementAt полностью:

@foreach (var element in Model)
{
    <tr id="tblRow_@(element.Id)">
        <td width="1">
            @Html.DisplayFor(m => element.LoggedInUser)
        </td>
        <td width="1" class="date">
            @Html.DisplayFor(m => element.DateCreated)
        </td>
    </tr>
}

Теперь есть сложная часть DisplayFor поступит правильно. возможно , что не получится - я не знаю достаточно о HtmlHelper<T>, чтобы точно знать, что там происходит. Возможно, вам придется изменить код немного больше, чтобы это работало.

Если вам нужно нужно получить доступ к элементу по индексу, я бы изменил модель на List<T> и использовал бы свойство Count и обычный индексатор вместо:

@for (int i = 0; i < Model.Count; i++)
{
    <tr id="tblRow_@(Model[i].Id)">
        <td width="1">
            @Html.DisplayFor(m => m[i].LoggedInUser)
        </td>
        <td width="1" class="date">
            @Html.DisplayFor(m => m[i].DateCreated)
        </td>
    </tr>
}
* 10 57 * Таким образом, у вас не будет скрытой зависимости «может быть, она будет быстрой, а может и не будет».
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...