访问空的Linq结果非常慢

时间:2010-06-10 23:25:26

标签: .net linq

用Linq弄湿我的脚。我试图确定四个DataColumns中包含的不同值。所以,我从

开始
var c1types = (from DataRow row in dtSource.Select("hasreq")
               where row["m"].ToInt() > 0
               select new { col = row["m"] }).Distinct();
var c2types = (from DataRow row in dtSource.Select("hasreq")
               where row["w"].ToInt() > 0
               select new { col = row["w"] }).Distinct();
var c3types = (from DataRow row in dtSource.Select("hasreq")
               where row["ag"].ToInt() > 0
               select new { col = row["ag"] }).Distinct();
var c4types = (from DataRow row in dtSource.Select("hasreq")
               where row["aq"].ToInt() > 0
               select new { col = row["aq"] }).Distinct();

foreach (var type in c1types.Union(c2types).Union(c3types).Union(c4types).Distinct())
{
  ...
}

这有效,但速度非常慢(4-5秒)。所以,我在foreach之前提出以下内容

MessageBox.Show(c1types.Count().ToString()); // 1 - immediate display
MessageBox.Show(c2types.Count().ToString()); // 1 - immediate display
MessageBox.Show(c3types.Count().ToString()); // 1 - immediate display
MessageBox.Show(c4types.Count().ToString()); // 0 - 4-5 seconds to display

使用我的样本数据,前三个Selects中的每一个都返回一个不同的值(Count()== 1)。第四个没有返回值(Count()== 0)。我不明白为什么它会立即显示前三个计数,但第四个计数需要4-5秒才能显示。看起来空的结果是减速的原因。这里发生了什么,最好的解决方法是什么?

1 个答案:

答案 0 :(得分:1)

从您的代码中,我猜测ToInt()是一种扩展方法。我很确定DataRow上的索引器返回对象,我不记得定义ToInt()的Object。

如果这是真的,ToInt可能正在做一些性能下降。也许它正在做类似

的事情
try { return Int32.Parse(arg); }
catch { return 0; }

如果Int32.Parse无法处理来自row [“aq”]的大多数值,则可能导致速度变慢 - 请检查调试窗口是否有异常。如果这是问题,你可以使用Int32.TryParse来加速它,这不会引发异常。

如果我错了你能提供更多信息吗?什么是ToInt()?

编辑:

我应该知道它将是Convert.ToInt32,因为Int32.Parse为参数获取一个String。我提出的解决方案如下。

批评使用Convert.ToInt32:

你应该从Convert.ToInt32中捕获异常。它会抛出Eric Lippert所谓的'Boneheaded'异常。应该从不发生的例外情况。如果你从Convert.ToInt32得到一个例外,你的程序是错误的。您已尝试将某些内容转换为不代表Int32的Int32。考虑ToInt扩展方法的单元测试结果。您可以调用myPrizeSheep.ToInt()并获得0.将绵羊转换为数字是否有意义?吞噬Convert.Int32中的异常通常会导致麻烦 - 在您的情况下,这是一个性能问题,但通常情况可能更糟 - 正确性问题。

没有Convert.TryConvertInt32(Object)。但是有一个Int32.TryParse(String)。这是因为想要将用户输入的字符串解析为Int32是很常见的。你希望他们可能输入的东西不是Int32 - 如果他们这样做的话也不是特例 - 你可以告诉用户纠正它 - 这是正常程序执行流程的一部分。

如果你有一个对象,你必须知道它代表一个Int32才能尝试转换它。如果您传递给Convert.ToInt32的内容不代表Int32,那么这是一个例外情况。我想不出你想要'尝试'将任何旧Object转换为Int32的单个实例 - 显然BCL也不会开发。

我认为ToInt不是很好地使用扩展方法。我通常使用扩展方法,因此我可以使用漂亮的管道转发样式语法将调用链接在一起。在很少的情况下,您希望将ToInt()链接到其他方法调用。

因为所有ToInt都会调用Convert.ToInt32并错误地吞下任何异常。在示例中使用Convert.ToInt32会好得多。

解决方案:

考虑如何处理Linq查询中不是int的内容。在你的情况下,他们可能是空值或DBNulls。您可能希望排除这些行,在这种情况下,您可以写下以下内容:

var c4types = (from DataRow row in dtSource.Select("hasreq") 
               where row["aq"] != null && row["aq"] != DBNull.Value && row["aq"].ToInt() > 0 
               select new { col = row["aq"] }).Distinct();

该表达式的缺点是您最终得到的是对象列表而不是Int32。您可以在最终选择中进行转换,但之后您将进行两次转换。说实话,我喜欢的方式(如果你想要一个Int32而不是Objects的集合)将是:

var c4types = dtSource.Select("hasreq")
                      .Where(row => row["aq"] != null && row["aq"] != DBNull.Value)
                      .Select(Convert.ToInt32(row["aq"])
                      .Where(i => i > 0)
                      .Distinct();

在上面,我们首先删除具有空值的行。然后我们将我们知道的行转换成int。然后我们摆脱小于1的整数,最后得到一个唯一的整数集合。