如何在LINQ中将匿名类型转换为强类型?

18

我有一个ListViewItems数组(ListViewItem[]),其中我每个ListViewItem.Tag中存储一个SalesOrderMaster对象以供稍后引用。

我有一些代码,它现在可以安全地遍历每个ListViewItem,将.Tag属性安全强制转换为SalesOrderMaster对象,然后仅在检查订单在该集合中不存在后,将该对象添加到SalesOrders集合中。

比较销售订单的过程很耗费时间,因此我希望将其转换为LINQ表达式以提高清晰度和性能。(我还安装了.NET Framework 3.5的并行扩展,以便进一步提高LINQ的性能)

所以,话不多说:这是我有的内容,以及我想要的内容(我想要的内容无法编译,所以我知道我做错了什么,但我希望它可以说明问题)

我目前的代码:(慢)

foreach (ListViewItem item in e.Argument as ListViewItem[])
            {
                SalesOrderMaster order = item.Tag as SalesOrderMaster;
                if ( order == null )
                {
                    return;
                }
                if (!All_SalesOrders.Contains(order))
                {
                    All_SalesOrders.Add(order);
                }
            }

我的需求:(理论)

    List<SalesOrderMaster> orders = 
(from item in (e.Argument as ListViewItem[]).AsParallel() 
select new { ((SalesOrderMaster)item.Tag) }).Distinct();

编辑:我知道转换很便宜,我的意思是“比较”,在这种情况下相当于.Contains(order)操作。

编辑:每个人的答案都很棒!我希望我可以选择多个答案,但最终我必须选择一个。

编辑:这就是我最终采用的方法:

List<SalesOrderMaster> orders = 
(from item in (e.Argument as ListViewItem[]) select (SalesOrderMaster) item.Tag).GroupBy(item => item.Number).Select(x => x.First()).ToList();
3个回答

18

我看到没有人解决您需要显式地将匿名类型转换为命名类型的问题,所以我来试一下…… 通过使用 "select new { }",你创建了一个匿名类型,但其实并不需要。你可以这样编写查询:

List<SalesOrderMaster> orders = 
    (from item in (e.Argument as ListViewItem[]).AsParallel() 
    select (SalesOrderMaster)item.Tag)
    .Distinct()
    .ToList();

注意查询选择了(SalesOrderMaster)item.Tag而没有使用new { },因此它不会创建匿名类型。另外请注意,我添加了ToList(),因为你想要一个List<SalesOrderMaster>

这解决了你的匿名类型问题。然而,我同意Mark和Guffa的看法,在这里使用并行查询不是你最好的选择。如果要像Guffa建议的那样使用HashSet<SalesOrderMaster>,可以这样做:

IEnumerable<SalesOrderMaster> query = 
    from item in (ListViewItem[])e.Argument
    select (SalesOrderMaster)item.Tag;

HashSet<SalesOrderMaster> orders = new HashSet<SalesOrderMaster>(query);

(我避免使用var,以便在示例中明确返回类型。)


@[Lucas] 但是我们如何使用这种方法选择多个字段呢?有没有使用匿名类型的替代方法? - Zesty
1
啊,没事了,我懂了。选择新的Person(e.First_name, e.Last_name)).ToList<Person>()。 - Zesty

3

正如Marc Gravell所说,您不应该从不同的线程访问Tag属性,而且类型转换非常便宜,因此您可以使用以下代码:

var items = (e.Argument as ListViewItem[]).Select(x=>x.Tag)
         .OfType<SalesOrderMaster>().ToList();

但是,如果您想查找不同的项-在这里,您可以尝试使用AsParallel:

var orders = items.AsParallel().Distinct();

3

这段代码中耗时的部分是对列表调用Contains方法。由于它是一个O(n)操作,所以随着向列表中添加更多对象,速度会变慢。

只需使用HashSet<SalesOrderMaster>来代替List<SalesOrderMaster>存储对象。 HashSetContains方法是一个O(1)操作,因此您的循环将成为一个O(n)操作,而不是一个O(n*n)操作。


1
但是向哈希集合中添加项是一个O(n)操作,每添加一个O(log n),因此您会得到O(n*log n)。 - configurator
是的,有时将项添加到哈希集合中是一个O(n)操作,但将项添加到列表中也是如此,因此相对结果大致相同。 - Guffa
那么明确一点,对于添加项目来说,两者所需的时间大致相同,而在HashSet中确定它是否已包含该项目要快得多。就是这样吗? - Lucas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接