13

Assume my objects are in perfect working order (i.e. TDD makes me think they work).

I have a list that I create like this (except indented properly):

var result = from v in vendors
             from p in v.Products
             orderby p.Name
             select p;

This works - I get all products from all vendors.

Now I have a list of conditions, built up at runtime by the user. Let's apply them:

foreach (Attribute a in requiredAttributes)
{
    result = result.Where(p => p.Attributes.Contains(a));
}

This may be primitive, but I thought it'd work. However, after this foreach loop is finished, when you enumerate "result" it will contain all products that has the LAST Attribute of the requiredAttributes collection in it's Attributes property (also a collection).

To me, this smells like "a" is overwritten somewhere with each trip trough the loop, and only the last one applies.

Short of somehow writing an extension method to IEnumerable called ContainsAll(IEnumerable) or something to that effect, how can I achieve what I want, which is basically a logical AND, giving me only those products that has ALL the required attributes?

2
  • Thanks guys - I am giving the "accepted answer" to Jon as it seems it might be more suitable for the "general" case. But I will definately check out Omer's suggestion as well, didn't know you could do that. Votes to all. :) Commented Oct 10, 2008 at 6:30
  • The new way to fix this issue is to just use C#5. See also Has foreach's use of variables been changed in C# 5? Commented Jan 15, 2016 at 14:38

3 Answers 3

20

(Edited for clarity.)

The problem is the foreach loop, and the fact that the "a" variable is being captured and then changed each time. Here's a modification which will work, by effectively introducing a "new" variable for each iteration of the loop, and capturing that new variable.

foreach (Attribute a in requiredAttributes)
{
    Attribute copy = a;
    result = result.Where(p => p.Attributes.Contains(copy));
}

Omer's solution is a cleaner one if you can use it, but this may help if your real code is actually more complicated :)

EDIT: There's more about the issue in this closures article - scroll down to "Comparing capture strategies: complexity vs power".

Sign up to request clarification or add additional context in comments.

1 Comment

I learned this critical nuance from ReSharper's puzzling warnings about it ;)
7
var result = from v in vendors
             from p in v.Products
             where requiredAttributes.All(a => p.Attributes.Contains(a))
             orderby p.Name
             select p;

HTH.

Comments

5

I haven't coded it up, but change

foreach (Attribute a in requiredAttributes){    
    result = result.Where(p => p.Attributes.Contains(a));
}

to

foreach (Attribute a in requiredAttributes){    
    Attribute b = a;
    result = result.Where(p => p.Attributes.Contains(b));
}

should fix it too, I think.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.