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



简而言之,我有一个接受的视图

@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(); 

通过第一个(IEnumerable(比通过第二个(已转换为List(慢

为什么

我假设这与Model.Count((有关,也许它必须在每个循环中将IEnumerable转换为List,但即使只有100个条目,速度差也会从几乎立即变为8秒。

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

这基本上与ASP.NET无关——这是IEnumerable<T>及其扩展方法的工作方式,尤其是对于延迟创建的序列。

当您在序列上调用ToList()时,它会通过询问原始序列的每个元素来创建List<T>,但在那之后,您可以在根本不咨询序列的情况下执行所有操作(通过索引访问、获取计数等(。LINQ不仅不需要查阅序列,而且在IList<T>实现上调用ElementAt()Count()时,它们都进行了优化。

如果在不实现IList<T>IEnumerable<T>(或其他一些有帮助的接口(上调用Count(),它必须从开始到结束循环整个序列。如果对序列进行延迟求值(例如,使用带有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()——你的整个循环可以重写,以避免完全使用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>
}

这样你就不会有隐藏的"也许会很快,也许不会"依赖。

最新更新