执行ToList()方法后的Lambda表达式结果与之前不符的技术问询
为什么ToList()前后Lambda表达式的结果有差异?
这个问题的核心原因,其实是LINQ的「延迟执行」特性在搞鬼!我来结合你的代码一步步拆解:
先搞懂没有ToList()的情况
你的原始LINQ链(去掉ToList()的话)逻辑是这样的:
return Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(Assembly.LoadFile) .SelectMany(m => m.GetTypes()) .Select(m => m.GetTypeInfo()) .Where(m => !m.IsAbstract && !m.IsInterface && typeof(IComponent).IsAssignableFrom(m)) .Select(Activator.CreateInstance);
这里所有的操作(找文件、加载dll、取类型、筛选、创建实例)都是延迟执行的——也就是说,这些代码写完之后根本不会立刻跑,只有当你真正去枚举这个返回的IEnumerable<IComponent>时(比如foreach遍历、调用ToList()/Count()之类的方法),才会从头开始一步步执行整个链条。
更关键的是:每次枚举都会重新执行一遍整个链条!比如你第一次foreach遍历结果,会重新找文件、加载dll;第二次遍历,又会再来一遍。这就会导致几个问题:
- 如果目录里的dll在两次遍历之间变了(比如新增/删除),两次的结果会不一样;
- 同一个dll会被重复加载多次,CLR会把它们当成不同的程序集,哪怕类型名完全一样,这些类型实例的
GetType().Assembly也是不同的对象; - 性能差,每次都要走文件系统和反射操作。
加上ToList()之后发生了什么
当你在Where之后加上ToList(),情况就完全变了:
.Where(m => !m.IsAbstract && !m.IsInterface && typeof(IComponent).IsAssignableFrom(m)) .ToList() // 关键!这里触发了立即执行 .Select(Activator.CreateInstance)
ToList()是立即执行的方法,它会立刻把前面整个LINQ链(从Directory.GetFiles到Where)的结果计算出来,并存成一个List<TypeInfo>。后续的Select(Activator.CreateInstance),只是在这个已经缓存好的List上操作——也就是说:
- 前面的找文件、加载dll、筛选类型这些操作,只会执行一次,就是在调用GetComponents()的时候;
- 后续不管你枚举多少次结果,都是从这个List里取TypeInfo创建实例,不会再碰文件系统和重新加载dll;
- 结果是固定的,不受后续目录文件变化的影响;
- 同一个dll只会被加载一次,避免了重复加载导致的类型实例问题。
总结两者的核心差异
- 执行时机:无ToList()时,所有操作在枚举时才执行;有ToList()时,前面的操作在调用GetComponents()时就执行完了。
- 重复执行:无ToList()每次枚举都重新跑完整条链;有ToList()只跑一次前面的步骤,后续只操作缓存的List。
- 结果稳定性:无ToList()的结果会受文件系统变化影响;有ToList()的结果是固定的快照。
- 程序集加载:无ToList()可能重复加载dll,导致类型实例的Assembly不同;有ToList()只会加载一次dll。
内容的提问来源于stack exchange,提问作者汤景行
相关产品推荐
相关产品推荐

