我想知道Where()
和ToList()
方法到底在做什么。具体来说,我想知道Where()
是否会在内存中创建一个新对象或返回一个新对象。
好的,看下面的代码,说我有一个骨架日志类。
public class Log()
{
public string Log {get;set;}
public string CreatedByUserId {get;set;}
public string ModifiedUserId {get;set;}
}
在我的业务逻辑中,假设我只想要由某个用户创建或修改的日志。这将通过以下方法完成: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();
}
在这种情况下,Where()
是通过删除与条件不匹配的所有对象来修改IEnumerable<Log>
,还是抓取所有对象,将该对象强制转换为内存中的列表,然后返回那个新对象?
如果是第二种可能性,如果将足够大的日志列表传递给函数,我是否应该关注性能?
答案 0 :(得分: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();
}
输出:
如您所见,向原始集合添加更多元素以满足第二个集合的过滤条件,这些元素也会在过滤集合中显示。
这将创建一个新的列表对象,用集合填充它,然后返回该集合。
这是一种即时方法,这意味着一旦您拥有该列表,它现在就是与原始集合完全独立的列表。
请注意,列表中的对象仍然可以与原始集合共享,ToList
方法不会创建所有这些对象的新副本,但集合< / em>是一个新的。
这是一个简单的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();
}
输出:
在这里,您可以看到,一旦我们创建了该列表,如果原始集合发生更改,它就不会更改。
您可以将Where
方法视为与原始集合相关联,而ToList
只会返回包含元素的新列表,而不会链接到原始集合。
现在,让我们来看看你的最后一个问题。你应该担心表现吗?嗯,这是一个相当大的主题,但是是,你应该担心性能,但不要到达你一直这样做的程度。
如果您为<{1}}电话提供大型集合,每隔时间枚举Where
电话的结果,您将枚举原来的大集合和过滤它。如果过滤器只允许少数这些元素通过它,那么每次枚举它时它仍然会枚举原始的大集合。
另一方面,对大事做Where
也会创建一个大的列表。
这会成为性能问题吗?
谁能说出来,但对于所有表演,这是我的第一回答:
你常常会看到程序员对一段代码感到烦恼,认为它会引发性能问题,只会让看着屏幕的慢用户想知道接下来要做什么,或者数据的下载时间会相形见绌。 ,或者将数据写入磁盘所需的时间,或者不是。
首先你知道,然后你解决了。
答案 1 :(得分:6)
Where()
会返回新 IEnumerable
。它是原始序列的过滤版本(投影),原始保持不变。 ToList()
使用投影返回新列表。
同样重要的是要注意调用.Where()
不会评估投影,这是在枚举枚举时完成的。例如,在foreach
循环中使用时,或者在这种情况下,在调用ToList()
时使用。
答案 2 :(得分:3)
Where
过滤IEnumerable<T>
只保留那些满足谓词的元素,维护排序。这 not 强制枚举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;
}
}
}
ToList
将IEnumerable<T>
转换为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;
}
在这种情况下,.WHERE通过删除与条件不匹配的所有对象来修改IEnumerable日志,或者它是否从日志中获取所有对象,将该对象强制转换为内存中的列表,然后返回新对象?
您对logs.Where(...).ToList()
表单的查询会将您的日志项目流式传输到Where
部分,然后只将满足谓词的那些项目放入最终的List<Log>
。
答案 3 :(得分:0)
Where
将创建一个迭代器,它将枚举您的集合并仅返回与您的谓词匹配的项目。这里的关键是,在您实际尝试访问它之前(例如,在foreach
)循环中,不会执行此迭代。
ToList
会将对可枚举集合中每个项目的引用复制到一个新列表(而不是复制引用而不是对象本身)。如果您在ToList
的末尾添加Where
,那么您将导致Where
必须遍历该集合。
简而言之,如果您使用Where
,则不会创建任何新对象(迭代器本身除外),并且您不会更改原始集合中的任何内容。如果您使用ToList
,那么您将引用复制到与Where
子句匹配的对象到新的List
(原始列表当然是不变 - 除非你将它分配回同一个变量。)
因此,如果您实际上不需要创建新列表,请不要使用ToList
。如果你需要做的只是遍历你的集合,只需跳过ToList
部分。但是,如果你这样做,这里有一个微妙的要点:
var filtered = logs.Where(x => x.CreatedByUserId.Equals(user) ||
x.ModifiedByUserId.Equals(user));
然后更改您的收藏,然后执行此操作:
foreach (var f in filtered)
{
//....
}
您将遍历原始集合(logs
),因为它现在是 ,而不是您声明filtered
时的情况。