【问题标题】:Invalid cast from List<MyType> to IEnumerable<MyType> back to List<MyType>, why?从 List<MyType> 到 IEnumerable<MyType> 到 List<MyType> 的无效转换,为什么?
【发布时间】:2012-07-27 11:39:42
【问题描述】:

所以基本上我有这个方法。

public List<Customer> FilterCustomersByStatus(List<Customer> source, string status)
{
    return (List<Customer>)source.Where(c => c.Status == status);
}

我向我抛出了一个无法转换的错误:

无法将“WhereListIterator`1[AppDataAcces.Customer]”类型的对象转换为“System.Collections.Generic.List`1[AppDataAcces.Customer]”类型。

为什么...?由于基础类型相同,Enumerable.Where 是否会创建 WhereListIterator 的新实例,如果是,为什么会有人这样做,因为这是不必要的性能和功能损失,因为我总是必须创建一个新列表 (.ToList( ))

【问题讨论】:

  • 使用 Linq,它会创建一个可枚举/枚举器,并动态地遍历您的集合而不创建新集合。除非您调用.ToList(),否则它实际上不会返回List 对象。这样做的原因是为了提高性能和限制内存使用;给定一个包含 1000000 个条目的列表,Linq 可以过滤/选择所有项目,而无需每次都复制列表。
  • 您能解释一下为什么每次都需要List 吗?特别是如果它包含 500k 条目,如您所说。
  • 我喜欢将它们保存在一个列表中,以便我可以添加/修改它的一些元素,即使我在显示项目时将它们保存在一个 IEnumerable 中,我仍然需要一些东西来按顺序枚举我的对象为了得到它们,尽管我在寻找这种方法的实现相关性而不是我的工作相关性。
  • filtered列表添加或修改项目有什么意义?您能否向我们展示您的代码中更大的部分,并描述您要解决的场景?
  • 我不是在尝试解决任何特殊情况,只是想了解它为什么以这种方式实现(有什么好处)?这就是我在这里问的原因,仅此而已。

标签: c# .net linq ienumerable


【解决方案1】:

Enumerable.Where 是否创建WhereListIterator 的新实例

是的。

如果是这样,为什么会有人这样做

因为它允许延迟流式传输行为。 Where 如果其消费者只想要第一个或第二个条目,则不必过滤所有列表。这对于 LINQ 来说是正常的。

因为这是不必要的性能和功能损失,因为我总是必须创建一个新列表 (.ToList())

“性能和功能的损失”来自您的设计。过滤后就不需要List&lt;Customer&gt;了,因为对它做任何修改都是没有意义的。

更新:“为什么要这样实现” 因为它实现了IEnumerable,而不是IList。因此它看起来像IEnumerable,它嘎嘎叫像IEnumerable

此外,以这种方式实现它要容易得多。想象一下,您必须在IList 上写Where。必须返回IList。它应该怎么做?返回原始列表的代理?您每次访问都会遭受巨大的性能损失。返回带有过滤项目的新列表?这与Where().ToList() 相同。返回原始列表但删除了所有不匹配的项目?这就是RemoveAll 的用途,为什么要另一种方法。

请记住,LINQ 尝试发挥函数式作用,并尝试将对象视为不可变对象。

【讨论】:

  • 感谢 Serg,您的回答非常明确。但是,如果它在过滤后转储列表,并将其转换为 IEnumerable(仅保存对我的项目的引用,而不是物理项目),我将再次制作一个列表(使用 .ToList 的新列表),我的收获在哪里?请跟我裸露。
  • 它不会转储任何东西。它持有对原始IEnumerable 的内部引用,并在其上构建另一个迭代器。另一个在下一次通话中。等等。您获得的好处是,您不会处理所有项目,直到您需要它们 - 而您几乎从不这样做。
  • 如果您了解一篇关于其工作原理的好文章,我将不胜感激。
  • Skeet's "C# in Depth" 有很好的迭代器部分。
【解决方案2】:

正如其他人指出的,您需要使用ToList 将结果转换为List&lt;T&gt;

原因是 Where 被延迟评估,所以 Where 并没有真的过滤数据。 它的作用是创建一个IEnumerable,根据需要过滤数据。

惰性求值有几个好处。它可能会更快,它允许使用 Where 和无限 IEnumerables 等。

ToList 强制将结果转换为List&lt;T&gt;,这似乎是您想要的。

【讨论】:

  • 好吧,Where() 是懒惰的事实在这里并不重要。不返回 List&lt;T&gt; 的事实是。
  • 我想要一个列表,我知道 ToList 可以做到这一点,我只是想知道它为什么不保留底层类型,所以我每次都需要创建一个新列表?
  • @svick Point is Where 不仅懒惰,而且还流媒体,你不能在返回List 的同时继续流媒体。
  • @svick 我不认为List&lt;T&gt; 可以偷懒。如果 Linq 是 IList-only,它最多可以返回 IList&lt;T&gt;,但事实并非如此。至于为什么,我猜 - 对于长链 LINQ 函数 - 惰性求值通常更有效。
【解决方案3】:

Where 扩展过滤并返回IEnumerable&lt;TSource&gt;,因此您需要调用 .ToList() 将其转换回来

public List<Customer> FilterCustomersByStatus(List<Customer> source, string status)
{
    return source.Where(c => c.Status == status).ToList();//This will return a list of type customer
}

【讨论】:

  • 请您解释一下-1
  • 解释什么?我只想知道为什么每次都生成新列表,因为它的性能很差,尤其是有 500 000 多个条目时。
  • @Freeman 我同意性能很差,因此您应该将返回类型更改为 Customer[] 并在 Where 子句中调用 .ToArray() 而不是 .ToList()。带有返回声明类型字节的 ToArray 你不会从 List 中受益
  • 即使我返回一个数组,我将使用相同的 Where 方法,它不会返回相同的 IEnumerable 类型吗?
  • @Freeman 当您调用 .ToArray() 时,它会创建一个 Ineumerable 数组,即 Customer[]
【解决方案4】:

IEnumerable 和 IList 之间的区别在于,可枚举不包含任何数据,它包含一个 iterator,当您请求新的数据时(例如,使用 foreach 循环),它会遍历数据。另一方面,列表是数据的副本。在您的情况下,要创建 List,ToList() 方法会遍历整个数据并将它们添加到 List 对象中。

根据您计划的用途,两者都有优点和缺点。例如,如果您计划多次使用整个数据,则应使用列表,但如果您计划使用一次或计划使用 linq 再次查询,则应选择 enumerable。

编辑: 为什么返回类型是 WhereListIterator 而不是 List 的问题的答案是,部分原因是 Linq 是如何工作的。例如,如果在第一个之后有另一个 Where 或另一个 Linq 语句,编译器将使用整个方法链创建一个查询,然后返回最终查询的迭代器。另一方面,如果第一个 Where 将返回一个 List,这将导致链中的每个 Linq 方法在数据上单独执行。

【讨论】:

    【解决方案5】:

    试试这个:

    public List<Customer> FilterCustomersByStatus(List<Customer> source, string status)
    {
        return source.Where(c => c.Status == status).ToList();
    }
    

    【讨论】:

    • 主要问题是“为什么?”你甚至都不想回答这个问题。
    猜你喜欢
    • 2012-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多