I am new to linq to objects in C#. I have the following query which works adequately. Is there someway to reduce or rewrite this query using joins or something else?
var query =
from container in containers
let items = container.GetItems()
from itemA in items
let tag = CreateTag(itemA)
where IsInteresting(tag)
from itemB in items
let derivedTag = CreateDerivedTag(tag)
where CreateTag(itemB) == derivedTag
select new { first = itemA, second = itemB };
To summarize,
- there are a set of item sequences
for each item sequench
- for each item A that is IsInteresting()
- find an item B in the same sequence that matches based on the criteria derived from the item A
- create a new item containing a reference to matching items A and B
- for each item A that is IsInteresting()
return a collection of all such matching items
-
3\$\begingroup\$ It looks like join might be possible. It might be easier to answer this question if you posted a complete program: e.g. definitions of CreateTag, CreateDerivedTag, IsInteresting, GetItems, together with some sample data in containers. \$\endgroup\$ChrisW– ChrisW2014年02月10日 00:01:09 +00:00Commented Feb 10, 2014 at 0:01
-
\$\begingroup\$ I tried answering this in lambda syntax, but this query is awfully strange. The final select and where are really hard to convert, the way I was doing it. Someone else can probably do a far better job. \$\endgroup\$Magus– Magus2014年02月10日 16:24:23 +00:00Commented Feb 10, 2014 at 16:24
1 Answer 1
After a lot of setup, I arrived at this:
var extensionMethodQuery =
from container in containers
let items = container.GetItems()
select items.GetItemDerivedTagItems
(items.SelectInterestingItemTags()).FirstOrDefault();
Your original query looks pretty optimal, but it took a while to see why. I broke it down into its component parts, and concluded that I'd concentrate on making it more reusable and readable. Personally, I find lambda joins too verbose, so created two extension methods instead:
public static class Filters
{
public static IEnumerable<ItemTag> SelectInterestingItemTags
(this IEnumerable<Item> items)
{
return
from item in items
let tag = Tag.CreateTag(item)
where Tag.IsInteresting(tag)
select new ItemTag(item, tag);
}
public static IEnumerable<ItemDerivedTagItem>
GetItemDerivedTagItems
(this IEnumerable<Item> items, IEnumerable<ItemTag> itemTags)
{
return
from itemTag in itemTags
from item in items
let derivedTag = Tag.CreateDerivedTag(itemTag.tag)
where Tag.CreateTag(item) == derivedTag
select new ItemDerivedTagItem(itemTag.item, item);
}
}
You'll notice I needed to create two extra classes to enable this, ItemTag
and ItemDerivedTagItem
, that replace the anonymous classes. Those names are probably awful, but not knowing the context, I did the best I could. The IsInteresting
and CreateDerivedTag
methods were made static member methods of their classes, too. The results were compared successfully via unit tests, against my dummy classes and dummy data, so hopefully you can verify that with yours.
As ever, the proof is in the profiling, and for me at least, the extension method query is 4 times faster. I can supply the dummy classes and data I used if needed, but as mentioned in the comments, I'd much prefer those supplied next time please!