我想知道究竟是什么Where()
和ToList()
方法在做什么.具体来说,我想知道是否Where()
会在内存中创建一个新对象或返回一个新对象.
好的,看下面的代码,说我有一个骨架日志类.
public class Log()
{
public string Log {get;set;}
public string CreatedByUserId {get;set;}
public string ModifiedUserId {get;set;}
}
Run Code Online (Sandbox Code Playgroud)
在我的业务逻辑中,假设我只想要由某个用户创建或修改的日志.这将通过一种方法完成:FilterLogsAccordingToUserId()
.
public IEnumerable<Log> FilterLogsAccordingToUserId(IEnumerable<Log> logs, string userId)
{
int user = int.Parse(userId);
return logs.Where(x => x.CreatedByUserId.Equals(user) ||
x.ModifiedByUserId.Equals(user)).ToList();
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,是通过删除与条件不匹配的所有对象Where()
来修改IEnumerable<Log>
,还是抓取所有对象,将该对象强制转换为内存中的列表,然后返回该新对象?
如果这是第二种可能性,如果将足够大的日志列表传递给函数,我是否应该关注性能?
ang*_*son 26
我们分别采用两种方法.
这个将返回一个新对象,枚举时将通过谓词过滤原始集合对象.
它绝不会改变原始集合,但会链接到它.
它也是一个延迟执行集合,这意味着在您实际枚举它之前,每次枚举它时,它都将使用原始集合并对其进行过滤.
这意味着如果您更改原始集合,其过滤结果将相应更改.
这是一个简单的LINQPad程序,演示:
void Main()
{
var original = new List<int>(new[] { 1, 2, 3, 4 });
var filtered = original.Where(i => i > 2);
original.Add(5);
filtered.Dump();
original.Add(6);
filtered.Dump();
}
Run Code Online (Sandbox Code Playgroud)
输出:
如您所见,向原始集合中添加更多元素以满足第二个集合的过滤条件,这些元素也将显示在过滤集合中.
这将创建一个新的列表对象,用该集合填充它,并返回该集合.
这是一个直接的方法,这意味着一旦您拥有该列表,它现在是与原始集合完全独立的列表.
需要注意的是对象在该列表中可能仍然与原来的收集共享,该ToList
方法不会使所有这些新的副本,但集合是一个新的.
这是一个简单的LINQPad程序,演示:
void Main()
{
var original = new List<int>(new[] { 1, 2, 3, 4 });
var filtered = original.Where(i => i > 2).ToList();
original.Add(5);
original.Dump();
filtered.Dump();
}
Run Code Online (Sandbox Code Playgroud)
输出:
在这里,您可以看到,一旦我们创建了该列表,如果原始集合发生更改,它就不会更改.
您可以将该Where
方法视为链接到原始集合,而ToList
只是返回包含元素的新列表,而不是链接到原始集合.
现在,让我们来看看你的最后一个问题.你应该担心表现吗?嗯,这是一个相当大的主题,但是,是的,你应该担心性能,但不要在某种程度上让你一直这样做.
如果您为呼叫提供大型集合Where
,则每次枚举Where
呼叫结果时,您将枚举原始大型集合并对其进行过滤.如果过滤器只允许少数这些元素通过它,那么每次枚举它时它仍将枚举原始大型集合.
另一方面,做一个ToList
大的东西也会创建一个大的列表.
这会成为性能问题吗?
谁能说出来,但对于所有表演,这是我的第一个答案:
很多时候你会看到程序员对一段代码感到烦恼,认为它会引发一个性能问题,只是看着屏幕上的慢速用户想知道下一步该做什么,或者数据的下载时间,或者是将数据写入磁盘所需的时间,或者不是.
首先你知道,然后你修复.
Where()
返回一个新的 IEnumerable
.它是原始序列的过滤版本(投影),原始保持不变. ToList()
使用投影返回新列表.
同样重要的是要注意,调用.Where()
不会评估投影,这是在枚举枚举时完成的.例如,在foreach
循环中使用时,或者在这种情况下,在调用时使用ToList()
.
Where
过滤 anIEnumerable<T>
仅保留那些满足谓词的元素,从而保持排序。这不会强制枚举源IEnumerable<T>
,因此它本质上是声明性的。
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
Run Code Online (Sandbox Code Playgroud)
ToList
将 an 转换IEnumerable<T>
为 a List<T>
,保持排序。这会强制枚举整个IEnumerable<T>
源。
public static List<TSource> ToList<TSource>(IEnumerable<TSource> source)
{
var list = new List<TSource>();
foreach (var item in source)
{
list.Add(item);
}
return list;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下, .WHERE 是通过删除所有不符合条件的对象来修改 IEnumerable 日志,还是从 ienumerable 日志中获取所有对象,将该对象转换为内存中的列表,然后返回该新对象?
您对表单的查询logs.Where(...).ToList()
将通过该部分流式传输您的日志项Where
,然后仅将满足谓词的日志项放入最终的List<Log>
.