C# 带有where子句的LINQ语句使执行变慢
我有一个关于LINQ和where语句的问题。我有以下代码示例(这是我在应用程序中使用的代码的简化版本):C# 带有where子句的LINQ语句使执行变慢,c#,linq,C#,Linq,我有一个关于LINQ和where语句的问题。我有以下代码示例(这是我在应用程序中使用的代码的简化版本): //从数据源获取图像。 var images=GetImages();//返回IEnumerable //我将继续处理图像,直到所有内容都已处理完毕。 while(images.Any()) { //我要确定它是什么样的图像,并用它做一些动作。 var image=images.First(); //突然,在我的过程中,我将向我的图像集合添加一个where语句,以获取所有符合指定条件的图像。
//从数据源获取图像。
var images=GetImages();//返回IEnumerable
//我将继续处理图像,直到所有内容都已处理完毕。
while(images.Any())
{
//我要确定它是什么样的图像,并用它做一些动作。
var image=images.First();
//突然,在我的过程中,我将向我的图像集合添加一个where语句,以获取所有符合指定条件的图像。
//如果我的图像集合不是空的,则可能会再次对图像集合执行相同的where语句。
//这也是问题所在,当我不添加ToList()扩展方法时,我的linq语句变得非常慢。
//当我添加ToList()扩展方法时,为什么我的linq语句又运行得很快?
var saveImages=images.Where();/.ToList()这是使我的LINQ查询再次运行所必需的。
//我将对这些保存图像执行一些操作,然后我将从当前图像集合中删除这些保存图像,因为我不再需要使用以下语句执行这些操作。
图像=图像。除了(保存图像);
}
正如代码示例所解释的,当我添加ToList()扩展方法时,为什么我的LINQ语句再次变快。为什么不能只因为返回IEnumerable集合就使用Where语句
我真的很困惑,我希望有人能给我解释:)。当你通过循环时,你的
图像首先变成了这个
images.Except(firstSetOfExclusions)
那么这个
images.Except(firstSetOfExclusions).Except(secondSetOfExclusions)
images.Except(firstSetOfExclusions).Except(secondSetOfExclusions).Except(thirdSetOfExclusions)
那么这个
images.Except(firstSetOfExclusions).Except(secondSetOfExclusions)
images.Except(firstSetOfExclusions).Except(secondSetOfExclusions).Except(thirdSetOfExclusions)
等等。慢的原因是,除非调用ToList
,否则每个排除集都必须执行新的查询。随着循环的每次迭代,这会变得越来越慢,因为它会一次又一次地执行相同的查询ToList
通过在内存中“具体化”查询来修复此问题
请注意,此问题的另一个解决方案是“具体化”新的图像子集,如下所示:
images = images.Except(saveImages).ToList();
这将避免链接“except”,因此您不必在saveImages
上调用ToList
,如果我们将LINQ重新实现到对象以显示方法,可能会更有意义;这是我们的主要内容
:
static void Main()
{
Log();
IEnumerable<int> data = GetData();
while (data.Any())
{
var value = data.First();
Console.WriteLine("\t\tFound:{0}", value);
var found = data.Where(i => i == value);
data = data.Except(found);
}
}
static IEnumerable<int> GetData()
{
Log();
return new[] { 1, 2, 3, 4, 5 };
}
注意到每个项目之间的复杂性是如何增长的吗
要获得额外积分,请将GetData
设为迭代器块-查看执行了多少次GetData
static IEnumerable<int> GetData()
{
Log();
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
}
静态IEnumerable GetData()
{
Log();
收益率1;
收益率2;
收益率3;
收益率4;
收益率5;
}
我做了94次(而不是原来版本中的一次)。很有趣吧
这不是LINQ的错——这是因为您使用LINQ非常奇怪。对于您正在做的事情,最好使用平面集合(List
),根据需要添加和删除项目
这是LINQ:
public static bool Any<T>(this IEnumerable<T> data)
{
Log();
using (var iter = data.GetEnumerator())
{
return iter.MoveNext();
}
}
static void Log([CallerMemberName] string name = null)
{
Console.WriteLine(name);
}
public static T First<T>(this IEnumerable<T> data)
{
Log();
using (var iter = data.GetEnumerator())
{
if (iter.MoveNext()) return iter.Current;
throw new InvalidOperationException();
}
}
public static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T,bool> predicate)
{
Log();
foreach (var item in data) if (predicate(item)) yield return item;
}
public static IEnumerable<T> Except<T>(this IEnumerable<T> data, IEnumerable<T> except)
{
Log();
var exclude = new HashSet<T>(except);
foreach (var item in data)
{
if (!exclude.Contains(item)) yield return item;
}
}
公共静态bool Any(此IEnumerable数据)
{
Log();
使用(var iter=data.GetEnumerator())
{
返回iter.MoveNext();
}
}
静态无效日志([CallerMemberName]字符串名称=null)
{
Console.WriteLine(名称);
}
公共静态T优先(此IEnumerable数据)
{
Log();
使用(var iter=data.GetEnumerator())
{
if(iter.MoveNext())返回iter.Current;
抛出新的InvalidOperationException();
}
}
公共静态IEnumerable,其中(此IEnumerable数据,Func谓词)
{
Log();
foreach(数据中的var项)if(谓词(项))产生返回项;
}
公共静态IEnumerable Exception(此IEnumerable数据,IEnumerable Exception)
{
Log();
var exclude=新哈希集(除外);
foreach(数据中的var项)
{
如果(!exclude.Contains(item))生成退货项目;
}
}
您正在逐渐使图像
投影变得越来越复杂。我只想循环一次。。。另外:每次您迭代它时,它可能会迭代任何由GetImages
组成的内容。。。这可能是一项非常重要的工作,ToList
只是将可枚举项解析为一个列表。如果对可枚举项进行多次枚举,如果可枚举项本身具有昂贵的迭代(例如数据库查询),这可能是一个好处。GetImages
中的底层可枚举项是什么?您做了一件非常奇怪的事情,似乎忘记了LINQ使用后期绑定。@MarcGravel我很确定GetImages方法只返回集合一次,因为图像是通过API下载的。所以一切都已经在记忆中了。在GetImages方法中,我准备了一个新的图像列表,并将下载的图像添加到其中(以及我在过程中需要的任何附加数据)。你能给我解释一下为什么我让图像投影变得越来越复杂吗?@MichielPeeters多次迭代序列(而不是数组/列表)已经很“奇怪”,因为它甚至不能保证工作。使用while(Any)
和First
而不是foreach
是很奇怪的。使用连续嵌套的多次调整该查询。除了
/。其中
很奇怪。这里有多个奇怪的级别。是否有可能查看LINQ语句中实际发生的情况?我已经查看/调试了集合,但我找不到在哪里可以真正看到表达式树。@MichielPeeters我不知道它是否可行-我认为很多都是实现LINQ扩展方法的类的私有,和/或嵌入在方法内部本地创建的lambdas中,这使得很难找到。真遗憾。如果我能看到表达式树,也许我会更了解LINQ实际上是如何处理的