Linq, using ToLookup to project values to different named variables - c#

var tmpProjection = myCollection.ToLookup(t => t.SomeBoolValue);
var listOneFinal = tmpProjection[true];
var listTwo = tmpProjection[false];
First question, is there a way to assign it to listOne and listTwo in some shorter way, I know I'm being pedantic here, ... just asking.
Now,
var listThree = listTwo.ToLookup(t => t.SomeOtherBoolValue);
var listFourFinal = listThree[false];
var listFiveFinal = listThree[true];
So in thise case, I just need (ultimately) listOneFinal, listFourFinal and listFiveFinal -- but i'm creating this temp stuff in between ... is there a way to reduce this.
i'm only talk code-wise not performance or code criticality.

bool is kind of weak for communicating intent. Int is a little better and enum would be best.
Lookup<int, T> myLookup = myCollection
.ToLookup(t =>
t.someBoolValue ? 1 :
t.someOtherBoolValue ? 4 :
5
);
var listOne = myLookup[1];
var listFour = myLookup[4];
var listFive = myLookup[5];

You can do it in fewer statements, but since you need to end op with 3 values, you need at least 3 assignments. Your code is very readable, don't sacrifice readability for "being smart" and reducing to fewer statements. That being said, here is a 3 - statement version; that will work well if collections are small (your own version will perform better with larger collections, since this version iterates multiple times through the collection):
var listOneFinal = myCollection.Where(t => t.SomeBoolValue);
var listFourFinal = myCollection.Where(t => !t.SomeBoolValue && !t.SomeOtherBoolValue);
var listFiveFinal = myCollection.Where(t => !t.SomeBoolValue && t.SomeOtherBoolValue);
Depending on your real usage scenario, the above might be more readable.

I think what you've got there is pretty good already, to be honest.
If you're simply looking to minimize the number of statements, I doubt you could do better than:
var listOneFinal = myCollection.Where(t => t.SomeBoolValue);
var listFourFinal = myCollection.Where(t => !t.SomeBoolValue && !t.SomeOtherBoolValue);
var listFiveFinal = myCollection.Where(t => !t.SomeBoolValue && t.SomeOtherBoolValue);
Or perhaps:
var predicates = new Func<MyClass,bool>[]{ t => t.SomeBoolValue, t => t.SomeOtherBoolValue};
var listOneFinal = myCollection.Where(predicates.First());
var listFourFinal = myCollection.Where(t => !predicates.Any(p => p(t)));
var listFiveFinal = myCollection.Where(t => !predicates[0](t) && predicates[1](t));
(Call ToList() on each query if desired)
But really, I prefer your technique much better, the code I have provided is not particularly more readable or efficient.
You might want to consider just storing the 2 lookups instead of each list and inline each 'final lookup' where necessary since it's cheap to call Lookup[key]. So whenever you need listFourFinal, just call listThree[false]. Better variable names would help, obviously.

If you find yourself doing this often, you can write a function to do it. For a boolean ToLookup, we can use C#'s out parameters to return multiple values.
public static void Dichotomize<T>(this IEnumerable<T> source,
Func<T,bool> keySelector,
out IEnumerable<T> affirmative,
out IEnumerable<T> negative) {
if (source == null) throw new ArgumentNullException("source");
if (keySelector == null) throw new ArgumentNullException("keySelector");
var affirmativeList = new List<T>();
var negativeList = new List<T>();
foreach (var element in source) {
(keySelector(element) ? affirmativeList : negativeList).Add(element);
}
affirmative = affirmativeList.AsReadOnly();
negative = negativeList.AsReadOnly();
}
Now we can do:
IEnumerable<T> listOneFinal, listTwo, listFourFinal, listFiveFinal;
myCollection.Dichotomize(t => t.SomeBoolValue, out listOneFinal, out listTwo);
listTwo.Dichotomize(t => t.SomeOtherBoolValue, out listFiveFinal, out listFourFinal);

Related

Is there best practice to obtain elements/variables from collection based on different conditions

Or in general how to filter some elements from collection based on different and complex conditions in single pass
Let's say we have collection of elements
var cats = new List<Cat>{ new Cat("Fluffy"), new Cat("Meowista"), new Cat("Scratchy")};
And somewhere we use this collection
public CatFightResult MarchBoxing(List<Cat> cats, string redCatName, string blueCatName)
{
var redCat = cats.First(cat => cat.Name == redCatName);
var blueCat = cats.First(cat => cat.Name == blueCatName);
var redValue = redCat.FightValue();
var blueValue = blueCat.FightValue();
if (Cat.FightValuesEqualWithEpsilon(redValue, blueValue))
return new CatFightResult{IsDraw: true};
return new CatFightResult{Winner: redValue > blueValue ? redCat : blueCat};
}
Question: Is there a nice way to obtain multiple variables from collection based on some condition(s)?
The question probably requires some sort of uniqueness in collection, let's first assume there is some (i.e. HashSet/Dictionary)
AND preferably:
SINGLE pass/cycle on collection (the most important reason of question, as you can see there are 2 filter operations in above method)
oneliner or like that, with readability, and the shorter the better
generic way (IEnumerable<T> I think, or ICollection<T>)
typos error-prone and changes/additions safe (minimal use of actual conditions in code, preferably checked
null/exception check, because my intention that null is valid result for obtained variable
Would be also cool to have ability to provide custom conditions, which probably could be done via Func parameters, but I didn't tested yet.
There are my attempts, which I've posted in my repo https://github.com/phomm/TreeBalancer/blob/master/TreeTraverse/Program.cs
Here is the adaptation to example with Cats:
public CatFightResult MarchBoxing(List<Cat> cats, string redCatName, string blueCatName)
{
var redCat = null;
var blueCat = null;
//1 kinda oneliner, but hard to read and not errorprone
foreach (var c in cats) _ = c.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null;
//2 very good try, because errorprone and easy to read (and find mistake in assignment), but not oneliner and not elegant (but fast) redundant fetching and not single pass at all, up to O(N*N) with FirstOrDefault
var filter = new [] { redCatName, blueCatName }.ToDictionary(x => x.Key, x => cats.FirstOrDefault(n => n.Name == x.Key));
redCat = filter[redCatName];
blueCat = filter[blueCatName];
//3 with readability and ckecks for mistakenly written searching keys (dictionary internal dupe key check) , but not oneliner and not actualy single pass
var dic = new Dictionary<int, Func<Cat, Cat>> { { redCatName, n => redCat = n }, { blueCatName, n => blueCat = n } };
cats.All(n => dic.TryGetValue(n.Name, out var func) ? func(n) is null : true);
//4 best approach, BUT not generic (ofc one can write simple generic IEnumerable<T> ForEach extension method, and it would be strong candidate to win)
cats.ForEach(n => _ = n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null);
//5 nice approach, but not single pass, enumerating collection twice
cats.Zip(cats, (n, s) => n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null);
//6 the one I prefer best, however it's arguable due to breaking functional approach of Linq, causing side effects
cats.All(n => (n.Name == redCatName ? redCat = n : n.Name == blueCatName ? blueCat = n : null) is null);
}
All the options with ternary op are not extensible easily and relatively error-prone, but are quite short and Linq-ish, they also rely (some trade-off with confusion) on not returning/using actual results of ternary (with discard "_" or "is null" as bool). I think the approach with Dictionary of Funcs is a good candidate to implement custom conditions, just bake-in them with variables.
Thank you, looking forward your solutions ! :)
I'm not sure if it's possible with Linq out of the box but if writing a custom extension once is an option for you, retrieving some values from a collection with arbitrary number of conditions may later be put in pretty concise manner.
For example, you may write something like
var (redCat, blueCat) = cats.FindFirsts(
x => x.Name == redCatName,
x => x.Name == blueCatName);
If you introduce the FindFirsts() extension as follows:
public static class FindExtensions
{
public static T[] FindFirsts<T>(this IEnumerable<T> collection,
params Func<T, bool>[] conditions)
{
if (conditions.Length == 0)
return new T[] { };
var unmatchedConditions = conditions.Length;
var lookupWork = conditions
.Select(c => (
value: default(T),
found: false,
cond: c
))
.ToArray();
foreach (var item in collection)
{
for (var i = 0; i < lookupWork.Length; i++)
{
if (!lookupWork[i].found && lookupWork[i].cond(item))
{
lookupWork[i].found = true;
lookupWork[i].value = item;
unmatchedConditions--;
}
}
if (unmatchedConditions <= 0)
break;
}
return lookupWork.Select(x => x.value).ToArray();
}
}
The full demo can be hound here: https://dotnetfiddle.net/QdVJUd
Note: In order to deconstruct the result array (i.e. use var (redCat, blueCat) = ...), you have to define a deconstruction extension. I borrowed some code from this thread to do so.

I want to use more elegant construction

I am not senior at C#, so probably I should learn more basic, but here is my code:
var out1 = obj1.F1();
var out2 = obj2.F2(out1);
var out3 = obj3.F3(out2);
var out4 = obj4.F4(out3);
Other way is to do the same in one line:
var out4 = obj4.F4( obj3.F3( obj2.F2( obj1.F1() ) ) );
But it's difficult to read, more hierarchically, reminds xml tags
So I want to make it more functional. It should look like 'pipeline', 'conveyor' where out of one function is input of other. My solution (with crutches):
var obj = new[]{0}; //first looking bad thing, without that you are operating with each element of F1() result
var something = obj
.Select(x => obj1.F1() ) //second: x=> x doesn't take part in right side
.Select(x => obj2.F2(x) )
.Select(x => obj3.F3(x) )
.Select(x => obj4.F4(x) )
.ToArray(); //third: without that it doesn't do anything
How should you code this 'pattern'?
Rather than trying to use Enumerable.Select, which is designed for sequences (which is not what you have) you can simply create an operator that performs the same operation on individual items:
public static TResult Map<TSource, TResult>(
this TSource source, Func<TSource, TResult> selector)
{
return selector(source);
}
You can then use that without the awkward transformations to/from a sequence:
var result = obj1.F1()
.Map(x => obj2.F2(x))
.Map(x => obj3.F3(x))
.Map(x => obj4.F4(x));
You could do something like this:
var result = obj1.F1().Pipe(obj2.F2).Pipe(obj3.F3).Pipe(obj4.F4);
(using the following extension method)
static class Extensions
{
public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> func)
{
return func(input);
}
}
Not sure it's really more readable, though...
Well, your example is a little poor because I can't tell what exactly these methods are doing. They aren't named anything that infers their purpose, so we can't really give you much advice on how to proceed.
For instance, lets say that this was an image processing library and you were doing something like:
(this is psuedocode)
var img = new Image();
var trimmedImage = TrimWhitespace(img);
var rotatedImage = RotateImage(trimmedImage);
var zoomedImage = ZoomImage(rotatedImage);
I would still never chain them, but if I needed to reuse it in a presentation layer format, I'd setup an object that orchestrates it, ie:
var formattedImage = ImageFormatter.Format(new Image());
and ImageFormatter would call them in the order outlined in the psuedocode.

Change variable in linq

I have a query something like this
function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
if(listDataPoints.Count == 0)
return;
var startPoint = new CustomObject();
startPoint = listDataPoint.First();
List<CustomObject2> cObjList = from r in listDataPoints
where r != null && r.GetDistance(startPoint) > 100
select new CustomObject2
{
Var1 = r.Var1
}.ToList()
}
The problem here is that, in the beginning the startPoint is set to the first object in listDataPoint. However, after the comparison in the query (GetDistance) I want to reassign startPoint to the value of "r" if the Distance is greater than 100.
Is there any way to do so?
Thanks in advance
No, there is no clean way to do that.
LINQ is essentially a piece of functional programming that has been brought into C#. In functional programming values are immutable (they cannot be changed). Thanks to being functional and using immutality, LINQ queries can be lazily evaluated. It is not uncommon for a LINQ query to be only partly run, or for some parts of the sequence to be evaluated several times. That is safe to do thanks to immutability.
As soon as you want to change a value, you are working against LINQ. In this case you are much better off with a for loop.
Of course there are ways to solve this in a functional manner, as it is possible to solve this in a purely functional language. But in C# it is much cleaner to use a for loop.
You can use a fold:
var cObjList = listDataPoints.Where(r => r != null)
.Aggregate(Tuple.Create(startPoint, new List<CustomObject2>()), (acc, r) => {
if(r.GetDistance(acc.Item1)) {
acc.Item2.Add(new CustomObject2 { Var1 = r.Var1 });
return Tuple.Create(r, acc.Item2);
}
else return acc;
}).Item2;
Since you were not-null checking the elements from listDataPoints, so I assume it may contain null objects. In this case, your code may be vulnerable when the First() element from the list is empty.
//there is no function or procedure in c#;
//function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
var dataPoints = listDataPoints.Where(r => r != null);
if (dataPoints.Empty())
//return; you cant not return anything in a function
return null; //or return an empty list
//return new List<CustomObject2>();
var cObjList = dataPoints.Aggregate(
new Stack<CustomObject>(),
(results, r) =>
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(r);
return results;
})
.Select(r => new CustomObject2(){ Var1 = r.Var1 })
.ToList();
//return directly the line above or do more work with cObjList...
}
Yet, this is still messy and not easily maintained. Like Anders Abel suggests, you are best to go with the for loop for this case :
var cObjList= new List<CustomObject2>();
foreach(var r in dataPoints)
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(new CustomObject2(){ Var1 = r.Var1 });
}
//...
return cObjList;

Creating a composite condition using anonymous filter method

I am trying to edit a search tool using linq,
What I like a filter in where clause is (ItemNumber == X AND ( StatementStatus == SatusA Or StatementStatus == StatusB ) )
But right now, it is like:
What I like a filter in where clause is (ItemNumber == X AND StatementStatus == SatusA Or StatementStatus == StatusB )
as AND has higher operational priority over OR the result is not what I want. :)
Could you please help?
using (var ctx = new MyContext()) {
Func<Statement, bool> filter = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filter = new Func<Statement, bool>(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
if (request.StatusA)
filter = filter == null ? new Func<Statement, bool>(s => s.StatementStatus == StatementStatusType.StatusA) :
filter.And(s => s.StatementStatus == StatementStatusType.StatusA);
if (request.StatusB)
filter = filter == null ? new Func<Statement, bool>(s => s.StatementStatus == StatementStatusType.StatusB) :
filter.Or(s => s.StatementStatus == StatementStatusType.StatusB);
var results = ctx.Statements
.Include("StatementDetails")
.Include("StatementDetails.Entry")
.Where(filter)
.Take(100)
.Select(s => new StatementSearchResultDTO{ ....
}
}
That's happens not because AND have higher priority than OR. What happens in reality:
var firstFilter = ...; // itemNumber
var secondFilter = ...; // statusA
var firstAndSecondFilter = firstFilter.And(secondFilter); // itemNumber && statusA
var thirdFilter = ...; // statusB
var endFilter = firstAndSecondFilter.Or(thirdFilter) // (itemNumber && statusA) || statusB.
The problem - wrong control flow. You must to do something like that:
var filterByA = ...;
var filterByB = ...;
var filterByAorB = filterByA.Or(filterByB);
var filterByNumber = ...;
var endFiler = filterByNumber.And(filterByAorB);
And your code is bad, not just because it works wrong, but because it's hard to write code in such style. Reasons:
This code doesn't follow DRY principle. You have two same lambdas that checks for StatusA (look in your ternary operator) and two same lambdas that checks for StatusB
You have too long ternary operator with null checks. That's bad because you don't see general picture, your eyes focused on syntax problems. You may write and extension method AndNullable for funcs. Like this:
static Func<T1, TOut> AndNullable<T1, TOut>(this Func<T1, TOut> firstFunc, Func<T1, TOut> secondFunc) {
if (firstFunc != null) {
if (secondFunc != null)
return firstFunc.And(secondFunc);
else
return firstFunc;
}
else {
if (secondFunc != null)
return secondFunc;
else
return null;
}
}
And that same for Or. Now your code can be wroted like this:
Func<Statement, bool> filter = null;
if (request.StatusA)
filter = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filter = filter.OrNullable(s => s.StatementStatus == StatementStatusType.StatusB);
if (!string.IsNullOrEmpty(request.ItemNumber))
filter = filter.AndNullable(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
Reads more better.
Your filter is global filter. Writing of global filter is simpler for few filter conditions and number of lines is small, but it's more complicated to understand your filter. Rewrite it in this way:
Func<Statement, bool> filterByStatusA = null;
Func<Statement, bool> filterByStatusB = null;
if (request.StatusA)
filterByStatusA = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filterByStatusB = s => s.StatementStatus == StatementStatusType.StatusB;
Func<Statement, bool> filterByStatuses = filterByStatusA.OrNullable(filterByStatusB);
Func<Statement, bool> filterByItemNumber = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filterByItemNumber = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
Func<Statement, bool> endFilter = filterByItemNumber.And(filterByStatuses);
Okay, we have outthinked how we can write filters by combining them as Func<..> but we still have problems.
What problems we will got, if result filter is null? Answer: ArgumentNullException due to documentation. We must to think about this case.
What another problems we can got with using of simple Func<...>? Well, you must to know difference between IEnumerable<T> and IQueryable<T> interfaces. In simple words, all operations on IEnumerable causes simple iteratation over all elements (well, it's lazy, IEnumerable really slower than IQueryable). So, for example, combining of Where(filter), Take(100), ToList() on collection that have 10000 elements that are bad for this filter and 400 elements that are good will cause iterating over 10100 elements. If you wrote similar code for IQueryable the request of filtering will send on database server and this server will iterate only ~400 (or 1000, but not 10100), if you have configured indexes on database. So what happens in your code.
var results = ctx.Statements // you are getting DbSet<Statement> that implements interface IQueryable<Statement> (and IQueryable<T> implements IEnumerable<T>)
.Include("StatementDetails") // still IQueryable<Statement>
.Include("StatementDetails.Entry") // still IQueryable<Statement>
.Where(filter) // Cuz your filter is Func<..> and there are no extension methods on IQueryable that accepts Func<...> as parameter, your IQueryable<Statement> casted automatically to IEnumerable<Statement>. Full collection will be loaded in your memory and only then filtered. That's bad
.Take(100) // IEnumerable<Statement>
.Select(s => new StatementSearchResultDTO { .... // IEnumerable<Statement> -> IEnumerable<StatementSearchResultDTO>
}
Okay. Now you understand the problem. So, simple right code for you can be writed in this way:
using (var ctx = new MyContext()) {
results = ctx.Statements
.Include("StatementDetails")
.Include("StatementDetails.Entry")
.AsQueryable();
if (!string.IsNullOrEmpty(request.ItemNumber))
results = results.Where(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
if (request.StatusA) {
if (request.StatusB)
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusA ||
s.StatementStatus == StatementStatusType.StatusA);
else
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusA);
}
else {
if (request.StatusB) {
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusB);
}
else {
// do nothing
}
}
results = .Take(100)
.Select(s => new StatementSearchResultDTO{ ....
};
// .. now you can you results.
}
Yeah, totally ugly, but now your database solves how to find Statements that satisfy the filter. Therefore, this request is quickly as possible. Now we must understand what magic happens in code I written upper. Let's compare two examples of code:
results = results.Where(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
And this:
Func<Statement, bool> filter = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
results = results.Where(filter);
What the difference? Why first is more faster? Answer: when compiler sees first code, it examines that type of results is IQueryable<T> and IEnumerable<T> so that condition inside of brackets can have type Func<Statement, bool> (compiled function) or Expression<Func<Statement, bool>> (data, that can be compiled in function). And compiler chooses Expression (why - really dunno, just chooses). After request of first object query compiled not in C# statement, but in SQL statement and sends to server. Your SQL server can optimize request, because of indexes existing.
Well, the more better way - to write your own expressions. There are different ways to write your own expression, but there is a way to write it with not ugly syntax. The problem that you can't just invoke one expression from another - that doesn't supported by Entity Framework and can be not supported by another ORM's. So, we can use PredicateBuilder by Pete Montgomery: link. And then write two simple extensions on expressions suitable for us.
public static Expression<Func<T, bool>> OrNullable<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
if (first != null && second != null)
return first.Compose(second, Expression.OrElse);
if (first != null)
return second;
if (second != null)
}
And that same for And. And now we can write our filter:
{
Expression<Func<Statement, bool>> filterByStatusA = null;
Expression<Func<Statement, bool>> filterByStatusB = null;
if (request.StatusA)
filterByStatusA = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filterByStatusB = s => s.StatementStatus == StatementStatusType.StatusB;
Expression<Func<Statement, bool>> filterByStatuses = filterByStatusA.OrNullable(filterByStatusB);
Expression<Func<Statement, bool>> filterByItemNumber = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filterByItemNumber = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
Expression<Func<Statement, bool>> endFilter = filterByItemNumber.And(filterByStatuses);
requests = ...;
if (endFilter != null)
requests = requests.Where(endFilter);
}
You can got a problem, because class ExpressionVisitor in PredicateBuilder in .NET < 4.0 is sealed. You can get write your own ExpressionVisitor or just copy it from this article.
OK, here is the way I have solved it:
filter.And(s => (request.StatusA && s.StatementStatus == StatementStatusType.StatusA) ||
(request.StatusB && s.StatementStatus == StatementStatusType.StautsB) ||
!(request.StatusA || request.StatusB)); //None selected = All selected
Any comments?

What am I missing in this chain of predicates?

NOTE: Right before posting this question it occurred to me there's a better way of doing what I was trying to accomplish (and I feel pretty stupid about it):
IEnumerable<string> checkedItems = ProductTypesList.CheckedItems.Cast<string>();
filter = p => checkedItems.Contains(p.ProductType);
So OK, yes, I already realize this. However, I'm posting the question anyway, because I still don't quite get why what I was (stupidly) trying to do wasn't working.
I thought this would be extremely easy. Turns out it is giving me quite a headache.
The basic idea: display all the items whose ProductType property value is checked in a CheckedListBox.
The implementation:
private Func<Product, bool> GetProductTypeFilter() {
// if nothing is checked, display nothing
Func<Product, bool> filter = p => false;
foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
Func<Product, bool> prevFilter = filter;
filter = p => (prevFilter(p) || p.ProductType == pt);
}
return filter;
}
However, say the items "Equity" and "ETF" are both checked in ProductTypesList (a CheckedListBox). Then for some reason, the following code only returns products of type "ETF":
var filter = GetProductTypeFilter();
IEnumerable<Product> filteredProducts = allProducts.Where(filter);
I guessed it might have had something to do with some self-referencing messiness where filter is set to, essentially, itself or something else. And I thought that maybe using ...
filter = new Func<Product, bool>(p => (prevFilter(p) || p.ProductType == pt));
...would do the trick, but no such luck. Can anybody see what I am missing here?
I believe you have a modified closure problem here. The pt parameter is bound into the lambda expression but changes as the loop progresses. It's important to realize the when a variable is referenced in a lambda it is the variable that is captured, not the value of the variable.
In loops this has a very significant ramification - because the loop variable is changing, not being redefined. By creating a variable inside the loop, you are creating a new variable for each iteration - which then alows the lambda to capture each independently.
The desired implementation would be:
foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
string ptCheck = pt;
Func<Product, bool> prevFilter = filter;
filter = p => (prevFilter(p) || p.ProductType == ptCheck);
}
Eric Lippert has written about this specific situation:
http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
http://blogs.msdn.com/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx
Also, see the question Access to Modified Closure (2) for a good explanation of what happens with closure variables. There's also an series of articles on the blog The Old New Thing that has an interesting perspective on this:
http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
It has to do with closures. The variable pt will always refer to the last value of the for loop.
Consider the following example where the output is the one expected because it's using a variable that is scoped inside the for loop.
public static void Main(string[] args)
{
var countries = new List<string>() { "pt", "en", "sp" };
var filter = GetFilter();
Console.WriteLine(String.Join(", ", countries.Where(filter).ToArray()));
}
private static Func<string, bool> GetFilter()
{
Func<string, bool> filter = p => false;
foreach (string pt in new string[] { "pt", "en" })
{
Func<string, bool> prevFilter = filter;
string name = pt;
filter = p => (prevFilter(p) || p == name);
}
return filter;
}
Since you're looping and setting the filter type to itself, you're setting the product type to the last pt in each case. It's a modified closure and since it's delay bound, you need to copy it on each loop, like this:
foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
var mypt = pt;
Func<Product, bool> prevFilter = filter;
filter = p => (prevFilter(p) || p.ProductType == mypt);
}
This should result in the right result, otherwise the last pt is used for all equality checks.

Categories

Resources