tags:

views:

2531

answers:

5

I have an array of ListViewItems ( ListViewItem[] ), where I store a SalesOrderMaster object in each ListViewItem.Tag for later reference.

I have some code that right now, goes through each ListViewItem safely casts the .Tag property into a SalesOrderMaster object, then adds that object to a collection of SalesOrders, only after checking to make sure the order doesn't already exist in that collection.

The process to compare sales orders is expensive, and I would like to convert this to a LINQ expression for clarity and performance. ( I also have the Parallel Extensions to .NET Framework 3.5 installed so I can use that to further improve LINQ performance)

So without further ado: This is what I have, and then what I want. ( what I want won't compile, so I know I am doing something wrong, but I hope it illustrates the point )

What I have: ( Slow )

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);
                }
            }

What I want: ( Theory )

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

EDIT: I know the cast is cheap, I said the "Compare", which in this case translates to the .Contains(order) operation

EDIT: Everyone's answer was awesome! I wish I could mark more than one answer, but in the end I have to pick one.

EDIT : This is what I ended up with:

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

The part in that code that is expensive is calling the Contains method on the list. As it's an O(n) operation it gets slower the more objects you add to the list.

Just use a HashSet<SalesOrderMaster> for the objects instead of a List<SalesOrderMaster>. The Contains method of the HashSet is an O(1) operation, so your loop will be an O(n) operation instead of an O(n*n) operation.

Guffa
But adding items to a hash set is an O(n) operation every O(log n) adds, so you get O(n*log n)
configurator
Yes, adding items to a hash set is an O(n) operation sometimes, but so is adding items to a list, so the relative result is about the same.
Guffa
So to be clear, adding the item takes about the same time for both, it's determining whether it already contains the item which is much faster in HashSet. Is that it?
Lucas
+1  A: 

Like Marc Gravell said, you shouldn't access the Tag property from different threads, and the cast is quite cheap, so you have:

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

but then, you want to find distinct items - here you can try using AsParallel:

var orders = items.AsParallel().Distinct();
configurator
A: 

Is there some reason you must have your SalesOrderMaster objects in the Tag of a control? Surely you have some central list that's not tightly bound to your UI, right? Why not use that and skip trying pull everything out of your Tag?

It seems to me you could benefit from a refactoring.

Randolpho
Please refrain from commenting on the overall design of an application when you are presented with a pin-hole view of one method, inside of one user control, inside of a major application.
Russ
Wow, touchy much? You have a heavy query and you're tightly coupled to your UI. You need to refactor.
Randolpho
+4  A: 

I see nobody has addressed your need to convert an anonymous type to a named type explicitly, so here goes... By using "select new { }" you are creating an anonymous type, but you don't need to. You can write your query like this:

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

Notice that the query selects (SalesOrderMaster)item.Tag without new { }, so it doesn't create an anonymous type. Also note I added ToList() since you want a List<SalesOrderMaster>.

This solves your anonymous type problem. However, I agree with Mark and Guffa that using a parallel query here isn't you best option. To use HashSet<SalesOrderMaster> as Guffa suggested, you can do this:

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

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

(I avoided using var so the returned types are clear in the examples.)

Lucas
A: 

This is a simple solution: http://www.codeproject.com/KB/cs/AnonymousTypeTransform.aspx