How to use conditional in List.ForEach()? - c#

I need to remove items from the HttpSession collection. In the following code, myList contains the same items as Session. If there are items in myList/Session that are not in itemsToRemove, they should be deleted from the session collection.
However, I'm not sure what the lambda syntax should look like. The following isn't correct.
myList.ForEach(x => !itemsToRemove.Contains(x) { Session.Remove(x) });
Any ideas how I can use a lambda expression to put everything on one line to accomplish this task?
Also, is there a way to avoid creating the intermediate list (myList)? I'm only doing that because I can't remove items from Session while iterating through it.

The most naïve way:
myList.Where(x => !itemsToRemove.Contains(x)) // LINQ extension method
.ToList() <----
.ForEach(x => Session.Remove(x)); // List<T> method so this is required |
Also you can use this:
mystList.Except(itemsToRemove)
.ToList()
.ForEach(x => Session.Remove(x));
But to use ForEach the underlying type should be List<T> so you need to call ToList() first. What causes 1 excess enumeration of the whole collection.
I would do this instead:
foreach (var x in mystList.Except(itemsToRemove))
{
Session.Remove(x)
}
This will minimize the number of enumerations.

First off, abatischev's answer is excellent. It's ideal from both a performance perspective and a readability perspective. If, however, you really want to cram all the functionality into one statement (which I don't recommend), you could try the following:
Session.OfType<string>()
.Except(itemsToRemove)
.ToList()
.ForEach(x => Session.Remove(x));
As abatischev metnioned, the ToList() call costs you an extra enumeration through the collection, which could have a non-trivial performance impact if the collection has a large number of elements in it. However, it means the ForEach() call iterates over a newly created List<string>, which fills the role of your myList and lets you remove items from the Session (since you're iterating through that temporary list, rather than the Session).
(Note that I haven't worked with HttpSessionState objects myself, merely looked at their MSDN article. You may need to replace the string generic type with something else if strings aren't what HttpSessionState holds.)

Related

In what scenario we have to use extension method as AsEnumerable() and ToList() in linq Query

here we are using AsEnumerable() extension method.
IEnumerable<string> strings = grdLookupPracticeMultiple.GridView.GetSelectedFieldValues("ID").Select(s => (string)s).AsEnumerable();
and what mean (string)s
The (string)s in this context is simply saying "cast the variable s as type string" - I'm guessing that GetSelectedFieldValues("ID") returns object, and you want a sequence of strings. There's actually a .Cast<string>() that would have done that for you.
As for when you need AsEnumerable() - pretty rarely, actually - and probably not in this case. The key scenario is when it wouldn't be otherwise. Perhaps you explicitly want to treat IQueryable<T> as IEnumerable<T> (to force LINQ-to-Objects instead of query composition). Or maybe you're dealing with something like DataSet where the .Rows etc aren't actually IEnumerable<T> for any T, and need help.
As for when to use .ToList(): when you want to create a snapshot of the data. At the moment, strings is a deferred execution sequence over the data. It will execute when you foreach over it. So: if the list changes between now and then : you'll get the updated contents. Often, you want the data as it exists now.
edit: actually, in this case GetSelectedFieldValues returns a snapshot, so: it won't actually update in this case, but in many others : it might; so: it needs to be considered.
So: summing up, you probably just want:
var strings = grdLookupPracticeMultiple.GridView.GetSelectedFieldValues("ID")
.Cast<string>().ToList();
However, if this is the dev express grid, you already get a List<T> from GetSelectedFieldValues, so maybe this would be more efficient:
var strings = grdLookupPracticeMultiple.GridView.GetSelectedFieldValues("ID")
.ConvertAll(s => (string)s);

Pre-processing every item in an IQueryable<T> in LINQ to objects

Please have a look at the following code:
var list = new List<MyClass>();
...
var selection = list
.Preprocess()
.Where(x => x.MyProperty = 42)
.ToList();
I want to implement Preprocess in such a way that it can process every item selected by the predicate that follows. So, if list contains thousands of object, I don't want Preprocess to process all of them, but only those that are selected by the Where clause.
I know this sounds like it would be better to move Preprocess to the end of the query, but I have my reasons to do it that way.
Is that possible with IQueryable<T>? Or does it behave like IEnumerable, where the whole LINQ-to-objects pipeline is strictly sequential?
For your purposes, IQueryable here is no different from IEnumerable, which is, as you said, purely sequential. Immediately after you invoke Where(), there's effectively no trace of the original "collection".
Now, you could theoretically do what you want, but that would require rolling "your own LINQ". Preprocess() could return some kind of PreprocessedEnumerable, all the custom operators would attach to it and the final ToList() will do the reordering of the calls.

Removing items from collection while iterating IEnumerable

I have the following code:
foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks
|| bar.Key > toTicks))
{
dataFromDataFeed.Remove(bar.Key);
}
Is this safe or do I need to convert the IEnumerable within foreach to a Dictionary<T,U> first?
Thanks.
No, that will either bomb or get you a result that still has bad elements. Just invert the Where expression:
var filtered = dataFromDataFeed.Where(bar => bar.Key >= fromTicks && bar.Key <= toTicks);
dataFromFeed = filtered.ToList(); // optional
It isn't clear whether you actually need the list to be updated, often it is not necessary since you have a perfectly good enumerator, so the last statement is // optional.
Keep in mind that using Remove() like you did in your original code has O(n*m) complexity, very bad. Using ToList() is only O(m) but requires O(m) storage. Trading speed for memory is a common programmer's decision but this is a slamdunk unless m is huge (hundreds of millions and you're fighting OOM) or very small. Neither should apply, given the expression.
It is considered bad practice to modify a collection while it is being enumerated, and in many cases this will cause an InvalidOperationException to be thrown.
You should copy the values into another List or array (e.g. by calling ToList() after your Where() call), and then you won't be modifying the original data.
foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks || bar.Key > toTicks).ToList())
{
dataFromDataFeed.Remove(bar.Key);
}
It will throw an error because you are iterating over the collection. Modification of collection is not supported while you are enumerating over it.
Create new List using ToList()
foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks
|| bar.Key > toTicks).ToList())
{
dataFromDataFeed.Remove(bar.Key);
}
Although Dictionary could almost certainly have included a method which would all a predicate function on each item and remove all items where that predicate returned true, it doesn't. Consequently, if one has a Dictionary that includes some items which match a predicate and others which don't, the only ways to get a dictionary including only the items which don't satisfy the predicate are either to build a list of all the items satisfying the predicate and then remove from the dictionary all the items on the list, or else build a dictionary containing only items that don't satisfy the predicate and abandon the original in favor of the new one. Which approach is better will depend upon the relative numbers of items to be kept and discarded.
As an alternative, one could switch to using a ConcurrentDictionary. Unlike a Dictionary, a ConcurrentDictionary will allow items to be removed without invalidating any enumearation in progress. If one only removes items as they are enumerated, I would expect a ConcurrentDictionary to enumerate exactly as one would expects. If enumerating one item would sometimes cause code to delete a different item, then code must be prepared for the fact that removing an item which has not yet been enumerated might, but is not required to, cause that item to be omitted from the enumeration.
Although Dictionary is apt to generally be faster than ConcurrentDictionary, it may be worthwhile to use the latter if "delete items where..." operations are common and would have to either delete or copy a significant fraction of the items in the collection.

Find a set number of values from collection

I have some code that filters through a collection of sorted objects according to a filter value. For instance, I want to find the objects where Name=="searchquery". Then I want to take the top X values from that collection.
My questions:
My collection is a List<T>. Does this collection guarantee the sort order?
If so, is there a built-in way to find the the top X objects that satisfy the condition? I'm looking for something like
collection.FindAll(o=>o.Name=="searchquery",100);
That would give me the top 100 objects that satisfy the condition. The reason is performance, once I've found my 100 objects, I don't want to keep checking the entire collection.
If i write:
collection.FindAll(o=>o.Name=="searchquery").Take(100);
will the runtime be intelligent enough to stop checking once it hits 100?
I can of course implement this myself, but if there is a built-in way (like a LInQ method) I'd prefer to use it.
collection.Where(o=>o.Name=="searchquery").Take(100)
The order should be in the same order as the original list, and it will stop checking once it takes 100 elements (Where returns an enumeration which is only evaluated as you take elements). From the documentation:
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.
If you need a different sort order, you will have to specify it (this of course means you have no choice but to examine all elements though).
Ok,
My collection is a List<T>. Does this collection guarantee the sort order?
No, but it will preserve the order of insertion.
If so, is there a built-in way to find the the top X objects that satisfy the condition?
someEnumerable.Where(r => r.Name == "searchquery").Take(100)
If i write:
// Some linq that works
will the runtime be intelligent enough to stop checking once it hits 100?
Yes, probably
Now, if you have a IList that has been sorted and you want to quickly iterate the top 100 items do this.
var list = sourceEnumerable.OrderBy(r => r.Name).ToList();
foreach(var r in list.Where(r => r.Name == "searchquery").Take(100))
{
// Do something
}
collection.Where(o=>o.Name=="searchquery").Take(100)
Is the most correct answer, because behind the scene Where is deferred execution, below is how Where method is implemented:
Where(this IEnumerable<T>, Func<T, bool> func)
{
foreach (var item in collection)
{
if (func(item))
{
yield return item;
}
}
}
So when calling Take(100), the loop just finds first 100 items which satisfy the criteria.
If you know for sure that the objects in your collection are not repeated (e.g.like a primary key), then you can use SortedList instead of List<T>. This will guarantee, that your list will be sorted when you filter it using a certain criteria. Have a look here for sorted list example:
http://msdn.microsoft.com/en-us/library/system.collections.sortedlist(v=vs.100).aspx

Delete inside foreach with linq where

I can see why this is not allowed:
foreach (Thing t in myCollection) {
if (shouldDelete(t) {
myCollection.Delete(t);
}
}
but how about this?
foreach (Thing t in myCollection.Where(o=>shouldDelete(o)) {
myCollection.Delete(t);
}
I don't understand why this fails. The "Where()" method obviously isn't returning the original collection, so I am not enumerating round the original collection when I try to remove something from it.
I don't understand why this fails.
I assume your question then is "why does this fail?" (You forgot to actually ask a question in your question.)
The "Where()" method obviously isn't returning the original collection
Correct. "Where" returns an IEnumerable<T> which represents the collection with a filter put on top of it.
so I am not enumerating round the original collection when I try to remove something from it.
Incorrect. You are enumerating the original collection. You're enumerating the original collection with a filter put on top of it.
When you call "Where" it does not eagerly evaluate the filter and produce a brand new copy of the original collection with the filter applied to it. Rather, it gives you an object which enumerates the original collection, but skips over the items that do not match the filter.
When you're at a store and you say "show me everything", the guy showing you everything shows you everything. When you say "now just show me the apples that are between $1 and $5 a kilogram", you are not constructing an entirely new store that has only apples in it. You're looking at exactly the same collection of stuff as before, just with a filter on it.
Try use this code
myCollection.RemoveAll(x => x.shouldDelete(x));
You can do:
myCollection.RemoveAll(shouldDelete);
The second statement returns a IEnumerable<> operating on your list. This one should be okay:
foreach (Thing t in myCollection.Where(o=>shouldDelete(o).ToList()) {
myCollection.Delete(t);
}
It is because collection should not be modified withing the foreach loop. It attempts to delete it before entire foreach loop is executed. Hence it will fail.
Where extension method filter the collection values based on the passed predicate and returns IEnumerable. Hence can't modify the collection while iteration.
You can use RemoveAll() for your purpose.

Categories

Resources