IEnumerable Where()和ToList() - 他们真正做了什么?

Ale*_*iak 11 .net c# linq

我想知道究竟是什么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)

输出:

LINQPad输出#1

如您所见,向原始集合中添加更多元素以满足第二个集合的过滤条件,这些元素也将显示在过滤集合中.

ToList

这将创建一个新的列表对象,用该集合填充它,并返回该集合.

这是一个直接的方法,这意味着一旦您拥有该列表,它现在是与原始集合完全独立的列表.

需要注意的是对象该列表中可能仍然与原来的收集共享,该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)

输出:

LINQPad输出#2

在这里,您可以看到,一旦我们创建了该列表,如果原始集合发生更改,它就不会更改.

您可以将该Where方法视为链接到原始集合,而ToList只是返回包含元素的新列表,而不是链接到原始集合.

现在,让我们来看看你的最后一个问题.你应该担心表现吗?嗯,这是一个相当大的主题,但是,是的,你应该担心性能,但不要在某种程度上让你一直这样做.

如果您为呼叫提供大型集合Where,则每次枚举Where呼叫结果时,您将枚举原始大型集合并对其进行过滤.如果过滤器只允许少数这些元素通过它,那么每次枚举它时它仍将枚举原始大型集合.

另一方面,做一个ToList大的东西也会创建一个大的列表.

这会成为性能问题吗?

谁能说出来,但对于所有表演,这是我的第一个答案:

  1. 首先要知道你有问题
  2. 使用适当的(内存,CPU时间等)工具来找出第二衡量你的代码,其中的性能问题
  3. 修理它
  4. 返回1号

很多时候你会看到程序员对一段代码感到烦恼,认为它会引发一个性能问题,只是看着屏幕上的慢速用户想知道下一步该做什么,或者数据的下载时间,或者是将数据写入磁盘所需的时间,或者不是.

首先你知道,然后你修复.


Ed *_* S. 6

Where()返回一个新的 IEnumerable.它是原始序列的过滤版本(投影),原始保持不变. ToList()使用投影返回新列表.

同样重要的是要注意,调用.Where()不会评估投影,这是在枚举枚举时完成的.例如,在foreach循环中使用时,或者在这种情况下,在调用时使用ToList().


Tim*_*lds 6

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>.