将枚举与列表传递给视图具有巨大的性能差异

jam*_*art 4 c# asp.net-mvc

简而言之,我有一个接受的观点:

@model IEnumerable<MyModel>
Run Code Online (Sandbox Code Playgroud)

并继续遍历模型,使用以下方法创建表:

    @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>
    }
Run Code Online (Sandbox Code Playgroud)

所以在控制器中我传递x给视图,它是:

var x = new DAL().GetList(); // returns IEnumerable<MyModel>
Run Code Online (Sandbox Code Playgroud)

或者

var x = new DAL().GetList().ToList(); 
Run Code Online (Sandbox Code Playgroud)

通过第一个(IEnumerable)比通过第二个(已经转换为列表)慢

为什么?

我假设它与 Model.Count() 有关系,也许它必须为每个周期将 the 转换IEnumerable为 a List,但即使只有 100 个条目,速度差异也会从几乎立即变为 8 秒。

Jon*_*eet 8

TL;DR:foreach改为使用。请参阅底部的代码。

这主要与 ASP.NET 无关——它是 ASP.NET 的工作方式IEnumerable<T>及其扩展方法,特别是对于延迟创建的序列。

当你调用ToList()一个序列时,它List<T>通过询问原始序列的每个元素来创建一个- 但在那之后,你可以做任何事情(通过索引访问,获取计数等)而根本不咨询序列。它不仅不需要查询序列,而且 LINQ 还针对实现调用它们ElementAt()以及Count()何时调用它们进行了优化IList<T>

如果你调用Count()一个IEnumerable<T>没有实现的IList<T>(或其他一些有帮助的接口),它必须从头到尾遍历整个序列。如果序列被惰性求值(例如使用带有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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这并不意味着您需要调用ToList()- 您可以重写整个循环以避免使用Count()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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在棘手的部分是是否DisplayFor会做正确的事情。有可能它不会 - 我不HtmlHelper<T>知道到底发生了什么。您可能需要稍微更改代码才能使其正常工作。

如果您确实需要通过索引访问元素,我会将模型更改为 aList<T>并使用Count属性和常规索引器:

@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>
}
Run Code Online (Sandbox Code Playgroud)

这样你就没有隐藏的“也许它会很快,也许不会”依赖。