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

时间:2014-04-15 17:23:47

标签: c# .net linq

我想知道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>,还是抓取所有对象,将该对象强制转换为内存中的列表,然后返回那个新对象?

如果是第二种可能性,如果将足够大的日志列表传递给函数,我是否应该关注性能?

4 个答案:

答案 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();
}

输出:

LINQPad output #1

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

ToList

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

这是一种即时方法,这意味着一旦您拥有该列表,它现在就是与原始集合完全独立的列表。

请注意,列表中的对象仍然可以与原始集合共享,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();
}

输出:

LINQPad output #2

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

您可以将Where方法视为与原始集合相关联,而ToList只会返回包含元素的新列表,而不会链接到原始集合。

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

如果您为<{1}}电话提供大型集合,每隔时间枚举Where电话的结果,您将枚举原来的大集合和过滤它。如果过滤器只允许少数这些元素通过它,那么每次枚举它时它仍然会枚举原始的大集合。

另一方面,对大事做Where也会创建一个大的列表。

这会成为性能问题吗?

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

  1. 首先要知道你有问题
  2. 其次使用适当的(内存,CPU时间等)工具来测量代码,以找出性能问题
  3. 修复
  4. 返回1号
  5. 你常常会看到程序员对一段代码感到烦恼,认为它会引发性能问题,只会让看着屏幕的慢用户想知道接下来要做什么,或者数据的下载时间会相形见绌。 ,或者将数据写入磁盘所需的时间,或者不是。

    首先你知道,然后你解决了。

答案 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;
        }
    }
}

ToListIEnumerable<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时的情况。