8
\$\begingroup\$

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
  • return a collection of all such matching items

\$\endgroup\$
  • 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 Feb 10 '14 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 Feb 10 '14 at 16:24
4
\$\begingroup\$

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!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.