c# ienumerable 和 ilist 和 iqueryable 的区别

来源:这里教程网 时间:2026-02-21 17:35:45 作者:

什么时候该用
IList<t></t>
?——需要立刻拿到全部数据并反复操作

当你明确知道后续要多次访问、修改、索引(比如

list[5]
)、统计数量(
Count
)或增删元素时,
IList<t></t>
是最直接的选择。它代表一个「已加载到内存的、可变的列表」。

调用
.ToList()
.ToArray()
后得到的就是
IList<t></t>
(或
List<t></t>
),此时数据库查询已执行完毕,后续所有操作都在内存中进行,不连库
支持
Count
属性、
Insert()
RemoveAt()
[index]
索引器 ——
IEnumerable<t></t>
IQueryable<t></t>
都不支持这些
⚠️ 坑:如果从 EF Core 的
DbSet<t></t>
直接赋值给
IList<t></t>
变量但没调用
ToList()
,编译会报错 —— 因为
DbSet<t></t>
默认是
IQueryable<t></t>
,不能隐式转成
IList<t></t>

IQueryable<t></t>
IEnumerable<t></t>
看起来都能写
.Where()
,到底差在哪?

表面一样,底层完全两回事:一个是「把条件发给数据库执行」,一个是「把全部数据拉到内存再筛」。

IQueryable<t></t>
本质是表达式树(
Expression<func bool>></func>
),EF Core 会把它翻译成 SQL ——
.Skip(10).Take(20)
就是
OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY
IEnumerable<t></t>
是纯内存操作,调用
.AsEnumerable()
后,后续所有 LINQ 方法(包括
Where
OrderBy
Skip
)都在客户端执行 —— 即使你只想要第 11 条,也会先把全表查出来
常见错误:
var posts = context.Posts.AsEnumerable().Where(p => p.Status == "Published").Skip(10).Take(10);
这句实际执行的是「查全表 → 内存过滤 → 内存分页」,性能灾难

为什么不能把
IQueryable<t></t>
当作
IList<t></t>
直接传参?

因为它们生命周期和语义完全不同:一个是「还没执行的查询计划」,一个是「已落地的内存对象」。

如果你写一个方法接收
IList<t></t>
参数,却传入
IQueryable<t></t>
,编译直接失败 —— 它们没有继承关系(
IQueryable<t></t>
继承自
IEnumerable<t></t>
,不是
IList<t></t>
更隐蔽的问题:把
IQueryable<t></t>
存进字段或返回给上层,却在多个地方反复遍历(比如两个
foreach
),会导致同一查询执行两次 —— 数据可能已变,还浪费连接资源
正确做法:如需复用结果,先
.ToList()
落地;如需保持查询能力,就用
IQueryable<t></t>
,但确保只执行一次(比如最后统一
ToListAsync()

选型口诀:查库用
IQueryable<t></t>
,算数用
IEnumerable<t></t>
,干活用
IList<t></t>

这不是教条,而是对应三种真实动作:

IQueryable<t></t>
:还在和数据库「商量怎么查」—— 只做
Where
OrderBy
Join
这类能转 SQL 的操作
IEnumerable<t></t>
:数据已在本地,但你只想「读一遍」—— 比如日志聚合、DTO 映射、简单过滤,且不关心索引或修改
IList<t></t>
:你要「真动手改」—— 插入新项、按位置删、反复查长度、导出 Excel、绑定 WinForms DataGridView

最容易被忽略的一点:

IQueryable<t></t>
的延迟执行是「表达式树级」的,一旦混入
IEnumerable<t></t>
操作(比如
.Select(x => new { x.Id, Name = x.Title.ToUpper() })
中用了非 SQL 友好方法),EF Core 会自动截断翻译,提前把部分数据拉进内存再继续 —— 日志里会出现「Client evaluation」警告,性能拐点往往就在这里。

相关推荐