I'm trying to find a good way to cumulatively apply up to 5 Func's to the same IEnumerable. Here is what I came up with:
private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters)
{
Func<SurveyUserView, bool> invokeList = delegate(SurveyUserView surveyUserView)
{
return surveyUserView.deleted != "deleted";
};
if (filters.ContainsKey("RegionFilter"))
{
invokeList += delegate(SurveyUserView surveyUserView)
{
return surveyUserView.Region == filters["RegionFilter"];
};
}
if (filters.ContainsKey("LanguageFilter"))
{
invokeList += delegate(SurveyUserView surveyUserView)
{
return surveyUserView.Locale == filters["LanguageFilter"];
};
}
if (filters.ContainsKey("StatusFilter"))
{
invokeList += delegate(SurveyUserView surveyUserView)
{
return surveyUserView.Status == filters["StatusFilter"];
};
}
if (filters.ContainsKey("DepartmentFilter"))
{
invokeList += delegate(SurveyUserView surveyUserView)
{
return surveyUserView.department == filters["DepartmentFilter"];
};
}
return invokeList;
}
I thought that it would apply these in a cumulative fashion, however, I can see from the results that it's actually just applying the last one (DepartmentFilter).
There are 2^4 possible combinations so brute-force if/elses are not going to work. (I want to AND using a particular lambda only when the corresponding key is present in the Dictionary.)
EDIT: Here is the solution that I accepted, but it causes a StackOverflowException when it is evaluated. Anybody see why?
private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters )
{
Func<SurveyUserView, bool> resultFilter = (suv) => suv.deleted != "deleted";
if (filters.ContainsKey("RegionFilter"))
{
Func<SurveyUserView, bool> newFilter =
(suv) => resultFilter(suv) && suv.Region == filters["RegionFilter"];
resultFilter = newFilter;
}
if (filters.ContainsKey("LanguageFilter"))
{
Func<SurveyUserView, bool> newFilter =
(suv) => resultFilter(suv) && suv.Locale == filters["LanguageFilter"];
resultFilter = newFilter;
}
if (filters.ContainsKey("StatusFilter"))
{
Func<SurveyUserView, bool> newFilter =
(suv) => resultFilter(suv) && suv.Status == filters["StatusFilter"];
resultFilter = newFilter;
}
if (filters.ContainsKey("DepartmentFilter"))
{
Func<SurveyUserView, bool> newFilter =
(suv) => resultFilter(suv) && suv.department == filters["DepartmentFilter"];
resultFilter = newFilter;
}
return resultFilter;
}
EDIT: Here is the very nice explanation of why this resulted in a StackOverflowException from friend and mentor Chris Flather-
The important thing to understanding why the infinite recursion occurs is understanding when the symbols in a lambda are resolved (i.e. at runtime and not at definition).
Take this simplified example:
Func<int, int> demo = (x) => x * 2;
Func<int, int> demo2 = (y) => demo(y) + 1;
demo = demo2;
int count = demo(1);
If it were resolved statically at definition this would work and be the same as:
Func<int, int> demo2 = (y) => (y * 2) + 1;
Int count = demo2(1);
But it doesn’t actually attempt to figure out what the demo embedded in demo2 does until runtime – at which time demo2 has been redefined to demo. Essentially the code now reads:
Func<int, int> demo2 = (y) => demo2(y) + 1;
Int count = demo2(1);
Use "need to" to express that something is important for you to do. This form is often used for something that is important one time, rather than referring to a responsibility or duty.
“Need” occurs when it is stated for more than one people whereas “Needs” is used when the statement is formed for a single person. But the verb “need to” is written as “I need to”, “You need to”. The use of need can be alternatively “She needs/He needs/It needs” and the examples of needs are “We need/They need”.
Semi-modal need and main verb need We can use main verb need as an alternative to semi-modal need. Main verb need is followed by to and it changes with person, number and tense (I, you, we, they need to; she, he, it needs to; I, you, she, he, it, we, they needed to).
We use 'need' to describe a necessity. It has a very similar meaning to obligation and can therefore be a more polite way of saying 'must' or 'have to'. Unusually, 'need' is both a normal verb and also a modal verb.
Instead of trying to combine the delegates this way, you could build new delegates that use the existing one with your AND condition:
Func<SurveyUserView, bool> resultFilter = (suv) => true;
if (filters.ContainsKey("RegionFilter"))
{
var tmpFilter = resultFilter;
// Create a new Func based on the old + new condition
resultFilter = (suv) => tmpFilter(suv) && suv.Region == filters["RegionFilter"];
}
if (filters.ContainsKey("LanguageFilter"))
{
// Same as above...
//... Continue, then:
return resultFilter;
That being said, it may be easier to pass your original IQueryable<SurveyUserView>
or IEnumerable<SurveyUserView>
into this method, and just add .Where
clauses directly to filter. You could then return the final query without executing it, with the filters added on.
I would think that using the Where(...)
extension on what is, presumably, an IQueryable<SurveyUserView>
and return a IQueryable<SurveyUserView>
instead of a Func<...>
:
// Assuming `q` is a `IQueryable<SurveyUserView>`
if(filters.ContainsKeys["Whatever"])
{
q = q.Where(suv => suv.Status == filters["Whatever"];
}
The And
ing is implicit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With