C# 将枚举与列表传递给视图会产生巨大的性能差异

C# 将枚举与列表传递给视图会产生巨大的性能差异,c#,asp.net-mvc,C#,Asp.net Mvc,简言之,我有一个观点: @model IEnumerable<MyModel> 或 通过第一个(IEnumerable)比通过第二个(已转换为列表)慢 为什么? 我假设这与Model.Count()有关,并且可能它必须为每个周期将IEnumerable转换为列表,但即使只有100个条目,速度差也会从几乎立即变为8秒;DR:改用foreach。请参阅底部的代码 这基本上与ASP.NET无关——这是ASP.NET的工作方式和扩展方法,特别是对于延迟创建的序列 当你调用一个序列时,它会通

简言之,我有一个观点:

@model IEnumerable<MyModel>

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

为什么?


我假设这与Model.Count()有关,并且可能它必须为每个周期将
IEnumerable
转换为
列表
,但即使只有100个条目,速度差也会从几乎立即变为8秒;DR:改用
foreach
。请参阅底部的代码

这基本上与ASP.NET无关——这是ASP.NET的工作方式和扩展方法,特别是对于延迟创建的序列

当你调用一个序列时,它会通过询问原始序列的每个元素来创建一个新的序列,但是在这之后,你可以做任何事情(通过索引访问、获取计数等),而根本不需要咨询序列。它不仅不需要参考序列,而且LINQ还为实现和何时调用它们进行了优化

如果在未实现IList的
IEnumerable
上调用
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;
        }
    }
}
现在棘手的部分是
DisplayFor
是否会做正确的事情。可能不会-我对
HtmlHelper
了解得不够,无法确切了解那里发生了什么。您可能需要对代码进行更多的更改,以使其正常工作

如果确实需要通过索引访问元素,我会将模型更改为
列表
,并使用
计数
属性和常规索引器:

@for(int i=0;im[i].LoggedInUser)
@DisplayFor(m=>m[i].DateCreated)
}

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

我看到的问题是使用IEnumerable-Count()和ElementAt(I)的两个方法。如果它不是没有随机访问优化的集合(例如SQL查询或类似流的枚举器)每次调用这些方法时,您都将执行查询。当您执行ToList时,您将执行enumerable直到最后,并将结果存储在内存中。您作为模型传递的具体内容是什么?作为序列公开的列表可能与延迟执行序列(如开放数据库查询或网络查询)的行为非常不同。IEnumerable的本质是它可以表达这两种情况。谢谢。我最初有一个
foreach
循环,但去了几家公司,试图让DropDownLists将它们的值传递回控制器,最后得到了这个
int
循环。今天早上我发现我的初始模型设置不正确而且DropDownList的填充比最初想象的要容易得多,所以我将进行重构,希望最终回到
foreach
var x = new DAL().GetList(); // returns IEnumerable<MyModel>
var x = new DAL().GetList().ToList(); 
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;
        }
    }
}