C# 列表查询比IQueryable快20倍?

C# 列表查询比IQueryable快20倍?,c#,performance,linq,list,iqueryable,C#,Performance,Linq,List,Iqueryable,这是我今晚设置的一个测试。这是为了证明一些不同的东西,但结果并不像我预期的那样 我在IQueryable上运行了一个10000个随机查询的测试,测试时我发现如果我在列表上执行相同的操作,我的测试速度会快20倍 见下文。我的CarBrandManager.GetList最初返回一个IQueryable,但现在我首先发出一个ToList(),然后它会更快 有谁能告诉我为什么我看到了这么大的差异 var sw = new Stopwatch(); sw.Start(); int queries =

这是我今晚设置的一个测试。这是为了证明一些不同的东西,但结果并不像我预期的那样

我在IQueryable上运行了一个10000个随机查询的测试,测试时我发现如果我在列表上执行相同的操作,我的测试速度会快20倍

见下文。我的CarBrandManager.GetList最初返回一个IQueryable,但现在我首先发出一个ToList(),然后它会更快

有谁能告诉我为什么我看到了这么大的差异

var sw = new Stopwatch();
sw.Start();

int queries = 10000;

//IQueryable<Model.CarBrand> carBrands = CarBrandManager.GetList(context);
List<Model.CarBrand> carBrands = CarBrandManager.GetList(context).ToList();

Random random = new Random();
int randomChar = 65;

for (int i = 0; i < queries; i++)
{
    randomChar = random.Next(65, 90);
    Model.CarBrand carBrand = carBrands.Where(x => x.Name.StartsWith(((char)randomChar).ToString())).FirstOrDefault();
}

sw.Stop();
lblStopWatch.Text = String.Format("Queries: {0} Elapsed ticks: {1}", queries, sw.ElapsedTicks);
var sw=新秒表();
sw.Start();
int=10000;
//IQueryable carBrands=CarBrandManager.GetList(上下文);
List carBrands=CarBrandManager.GetList(context.ToList();
随机=新随机();
int-randomChar=65;
for(int i=0;ix.Name.StartsWith(((char)randomChar.ToString()).FirstOrDefault();
}
sw.Stop();
lblStopWatch.Text=String.Format(“查询:{0}已用记号:{1}”,查询,sw.ElapsedTicks);

这里可能存在两个问题。第一:除了知道它实现了
IQueryable
之外,还不清楚从
GetList(context)
返回了什么类型的集合。这意味着当您评估结果时,很可能会创建一个SQL查询,将该查询发送到数据库,并将结果具体化为对象。或者它可能正在解析XML文件。或者下载RSS提要或调用internet上的OData端点。这显然比简单地过滤内存中的短列表要花更多的时间。(毕竟,到底有多少汽车品牌?)

但是我们假设它返回的实现实际上是一个
列表
,因此您测试的唯一区别是它是转换为
IEnumerable
还是转换为
IQueryable
。将类的扩展方法上的方法签名与上的方法签名进行比较。将列表视为IQueryable时,传递的是需要求值的
表达式
s,而不仅仅是可以直接运行的
Func
s

当您使用自定义LINQ提供程序(如实体框架)时,这使框架能够评估实际的表达式树,并从中生成SQL查询和物化计划。但是,LINQ to对象只想计算内存中的lambda表达式,因此它必须使用反射或将表达式编译成
Func
s,这两者都会对性能造成很大影响

您可能会试图对结果集调用
.ToList()
.AsEnumerable()
,以强制其使用
Func
s,但从某种角度看,这将是一个错误。假设您知道从
GetList(context)
方法返回的数据是某种内存对象。目前可能是这样,也可能不是这样。无论如何,它不是为
GetList(context)
方法定义的契约的一部分,因此您不能假设它总是这样。您必须假设返回的类型很可能是您可以查询的类型。尽管目前可能只有十几个汽车品牌可供搜索,但总有一天可能会有数千个(我在这里谈论的是编程实践,不一定说汽车行业就是这样)。因此,你不应该假设下载整个汽车列表并在内存中过滤它们总是会更快,即使现在恰好是这样

如果
CarBrandManager.GetList(context)
可能返回由自定义LINQ提供程序(如实体框架集合)支持的对象,那么您可能希望将数据强制转换保留为IQueryable:即使您的基准测试显示使用列表的速度快20倍,这种差异是如此之小,以至于没有用户能够分辨出这种差异。有朝一日,通过调用
.Where().Take().Skip()
并仅从数据存储中加载您真正需要的数据,您可能会看到性能提高了几个数量级,而如果您立即调用
.ToList()
,则最终会将整个表加载到系统内存中


但是,如果您知道
CarBrandManager.GetList(context)
将始终返回内存中的列表(顾名思义),则应将其更改为返回
IEnumerable
,而不是
IQueryable
。或者,如果您使用的是.NET 4.5,可能是
IReadOnlyList
IReadOnlyCollection
,这取决于您愿意强制您的
CarManager
遵守的合同。

在我的示例中,您能告诉我在使用IQueryable时应该做些什么来提高性能吗?@TysHTTP:我添加了几段。这充分回答了你的问题吗?谢谢你的帮助!此汽车品牌列表主要用作主数据列表,在运行另一个查询时需要在内存中查找此列表中的记录(有时每次运行超过100.000次)。根据您的说法,我想我应该切换到IEnumerable?@TysHTTP:是的,如果您知道您不打算在某个时候让LINQ提供程序支持此列表,那么让它返回一个
IEnumerable
,就更有意义了。请记住,他还将对生成的查询进行10000次迭代!如果它是从IQueryable执行的数据库查询,那么它就是10000个数据库查询,而不是仅仅迭代内存中的列表10000次。如果只迭代查询一次,那么调用
ToList
可能比直接迭代要昂贵。