Adding an element during enumeration - c#

When the first condition doesn't satisfy because product is not already in the collection, it's then added via the else statement. However, when it is added - the foreach loop continues until all objects have been iterated - yet it never enters p._id == product._id and will continue to the else statement, adding another identical object.
May this be because I'm adding an element to a collection during enumeration? It's not actually added until enumeration is completed.
public void AddProduct(Product product)
{
foreach(Product p in products)
{
if(p._id == product._id)
{
product._qty = product._qty + p._qty;
}
else
{
inventory.Add(p);
}
}
}

Use While loop or for loop , it does not throw CollectionChangedException .

Related

Reassign object in foreach loop c#

Not sure I understand why I can do this with a for loop and not a foreach loop?
This is the code that works. Looping through a BindingList Products, finding a match and then assigning that product at index i to the new product that's passed in.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
for (int i = 0; i < Products.Count; i++)
{
if (Products[i].ProductID == productToUpdateID)
{
Products[i] = productToUpdate;
}
}
}
If I try to do this with a foreach loop I get an error that I cannot assign to the iterator variable. What is the reasoning for this and is there a way to get around it or is using a for loop for this kind of problem the best solution?
This is essentially what I'm trying to do.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product = productToUpdate;
}
}
}
I can do something like this and reassign all the properties explicitly but want to see if there is another way to do it.
foreach(Product product in Products)
{
if (product.ProductID == productToUpdateID)
{
product.Name = productToUpdate.Name;
}
}
Thanks!
The foreach construct is for when you want to do something with each item in the list. That does not seem to be what you are doing. You are modifying the list itself, by removing an item and replacing it.
Personally I would not use a loop at all, I'd just remove the old item and add the new one.
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
Products.RemoveAll( x => x.ProductID == productToUpdateID );
Products.Add( productToUpdate );
}
Or if you wish to preserve order:
public static void UpdateProduct(int productToUpdateID, Product productToUpdate)
{
var index = Products.FindIndex( x => x.ProductID == productToUpdateID );
Products[index] = productToUpdate;
}
The reasons have already been given, but as a minor detail: this is sometimes possible; there is an alternative syntax in recent C# that uses a ref-local for the iterator value:
foreach (ref [readonly] SomeType value in source)
which is only available for some scenarios - naked arrays, spans, or custom iterator types with a ref-return Current - and as long as the optional readonly modifier is not used, you can assign directly via the value variable, since this is a direct reference to the underlying source. The uses for this are rare and niche. If Products is a List<T>, you could combine this with CollectionMarshal.AsSpan(...) to achieve what you want, but frankly I'd consider that hacky (apart from other things, it would bypass the list's internal change protections). Basically: don't do this, but : it isn't entirely impossible.
The foreach loop iterates over the elements of a collection, and the iteration variable is simply a reference to the current element in the collection.
The reason you cannot modify the iteration variable itself is that it is a read-only reference to the element in the collection. Modifying the iteration variable would not change the element in the collection; it would only change the reference.
Alternative ways are already mentioned in the above answers.
Just for the record. IMHO the best way is to use a foreach loop with a modified code like this. It only makes one iteration
int i=-1;
foreach (var product in products )
{
i++;
if (product.ProductID == productToUpdate.ProductID)
{
products[i]=productToUpdate;
break;
}
}
But if you want to use linq for some reason, you can do it in one line
products = products.Select(x => x = x.ProductID == productToUpdate.ProductID?productToUpdate:x).ToList();

adding or removing from an observable collection within ForEach

I don't think I can modify the observable collection 'varieties' in the foreach loop below - whenever the conditions in the 'if' are met the 'Remove' happens then the foreach exists when it still has the bulk of the collection to go through.
I changed it so that if the condition was met rather than remove from varieties it adds the variety to a new list, but again as soon as the condition is met for the first time the foreach exist.
Am I missing something?
foreach (Variety variety in varieties)
{
if (variety.Type != main.Type && variety.Type != null)
{
varieties.Remove(variety);
}
}
changed code:
foreach (Variety variety in varieties)
{
if (variety.Type == main.Type || variety.Type == null)
{
FilteredVarieties.Add(variety);
}
}
You cannot modify the source collection inside the foreach. If you really want to modify ,try having a copy to loop in foreach and modify it inside.
var sourcecopy=varieties;
foreach (Variety variety in sourcecopy)
{
if (variety.Type != main.Type && variety.Type != null)
{
varieties.Remove(variety);
}
}
You can't remove an item from your collection in a foreach loop. What is going on when you say foreach is it is really creating a for loop that goes from 0 to the original count of the collection. So once you remove something and the count of the collection changes, the range for the loop does not get updated.
If you switch to a normal for loop, you should be good though.
As others (and the compiler) have probably said, you can't change a collection that you are enumerating through in that way. However, if you .ToList() your collection in the foreach definition, you are actually enumerating a copy of your list, so are free to remove or add to your original list.
foreach (Variety variety in varieties.ToList())
{
if (variety.Type != main.Type && variety.Type != null)
{
varieties.Remove(variety);
}
}

Querying a chain of list of lists with LINQ

I am working with an XML standard called SDMX. It's fairly complicated but I'll make it as short as possible. I am receiving an object called CategoryScheme. This object can contain a number of Category, and each Category can contain more Category, and so on, the chain can be infinite. Every Category has an unique ID.
Usually each Category contains a lot of Categories. Together with this object I am receiving an Array, that contains the list of IDs that indicates where a specific Category is nested, and then I am receiving the ID of that category.
What I need to do is to create an object that maintains the hierarchy of the Category objects, but each Category must have only one child and that child has to be the one of the tree that leads to the specific Category.
So I had an idea, but in order to do this I should generate LINQ queries inside a cycle, and I have no clue how to do this. More information of what I wanted to try is commented inside the code
Let's go to the code:
public void RemoveCategory(ArtefactIdentity ArtIdentity, string CategoryID, string CategoryTree)
{
try
{
WSModel wsModel = new WSModel();
// Prepare Art Identity and Array
ArtIdentity.Version = ArtIdentity.Version.Replace("_", ".");
var CatTree = JArray.Parse(CategoryTree).Reverse();
// Get Category Scheme
ISdmxObjects SdmxObj = wsModel.GetCategoryScheme(ArtIdentity, false, false);
ICategorySchemeMutableObject CatSchemeObj = SdmxObj.CategorySchemes.FirstOrDefault().MutableInstance;
foreach (var Cat in CatTree)
{
// The cycle should work like this.
// At every iteration it must delete all the elements except the correct one
// and on the next iteration it must delete all the elements of the previously selected element
// At the end, I need to have the CatSchemeObj full of the all chains of categories.
// Iteration 1...
//CatSchemeObj.Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 2...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 3...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Etc...
}
}
catch (Exception ex)
{
throw ex;
}
}
Thank you for your help.
So, as i already said in my comment, building a recursive function should fix the issue. If you're new to it, you can find some basic information about recursion in C# here.
The method could look something like this:
private void DeleteRecursively(int currentRecursionLevel, string[] catTree, ICategorySchemeMutableObject catSchemeObj)
{
catSchemeObj.Items.ToList().RemoveAll(x => x.Id != catTree[currentRecursionLevel].ToString());
var leftoverObject = catSchemeObj.Items.ToList().SingleOrDefault();
if(leftoverObject != null) DeleteRecursively(++currentRecursionLevel, catTree, leftoverObject);
}
Afterwards you can call this method in your main method, instead of the loop:
DeleteRecursively(0, CatTree, CatSchemeObject);
But as i also said, keep in mind, that calling the method in the loop, seems senseless to me, because you already cleared the tree, besides the one leftover path, so calling the method with the same tree, but another category, will result in an empty tree (in CatSchemeObject).
CAUTION! Another thing to mention i noticed right now: Calling to list on your Items property and afterwards deleting entries, will NOT affect your source object, as ToList is generating a new object. It IS keeping the referenced original objects, but a deletion only affects the list. So you must write back the resulting list to your Items property, or find a way to directly delete in the Items object. (Assuming it's an IEnumerable and not a concrete collection type you should write it back).
Just try it out with this simple example, and you will see that the original list is not modified.
IEnumerable<int> test = new List<int>() { 1, 2, 3, 4 , 1 };
test.ToList().RemoveAll(a => a != 1);
Edited:
So here is another possible way of going after the discussion below.
Not sure what do you really need so just try it out.
int counter = 0;
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
var temp = list.Where(x => CatTree[counter++] == x.Id); // or != ? play with it .
list = temp.Items.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
I just translated you problem to 2 solutions, but I am not sure if you won't lose data because of the SingleOrDefault call. It means 'Grab the first item regardless of everything'. I know you said you have only 1 Item that is ok, but still... :)
Let me know in comment if this worked for you or not.
//solution 1
// inside of this loop check each child list if empty or not
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
list.RemoveAll(x => x.Id != Cat.ToString());
list = list.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
}
//solution 2
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
CleanTheCat(cat, list);
}
}
//use this recursive function outside of loop because it will cat itself
void CleanTheCat(string cat, List<typeof(ICategorySchemeMutableObject.Items) /*Place here whatever type you have*/> CatSchemeObj)
{
CatSchemeObj.RemoveAll(x => x.Id != cat);
var catObj = CatSchemeObj.Items.ToList().SingleOrDefault();
if (!catObj.Equals(default(catObj)){
CleanTheCat(cat, catObj);
}
}
Thank you to whoever tried to help but I solved it by myself in a much easier way.
I just sent the full CategoryScheme object to the method that converted it in the XML format, then just one line did the trick:
XmlDocument.Descendants("Category").Where(x => !CatList.Contains(x.Attribute("id").Value)).RemoveIfExists();

Any benefit to loop with nested condition in the ReSharper way?

When using a foreach loop with a nested condition inside, I ever write in the following way:
foreach (RadioButton item in listOfRadioButtons)
{
if (item.IsChecked == true)
{
// sometging
}
}
But I've installed ReSharper and it suggests to change this loop to the following form (removing the if and using a lambda):
foreach (RadioButton item in listOfRadioButtons.Where(item => item.IsChecked == true))
{
// something
}
In my experience, the ReSharper way will loop two times: one to generate the filtered IEnumerable, and after to loop the results of the .Where query again.
I am correct? If so, why is ReSharper suggesting this? Because in my opinion, the first is also more reliable.
Note: The default IsChecked property of the WPF RadioButton is a Nullable bool, so it's need a == true, a .Value, or a cast to bool inside a condition to return bool.
In my experience, the ReSharper way will loop two times: one to
generate the filtered IEnumerable, and after to loop the results of
the .Where query again.
Nope, it will loop only once. Where does not loop your collection - it only creates iterator which will be used to enumerate your collection. Here is how LINQ solution looks like:
using(var iterator = listOfRadioButtons.Where(rb => rb.IsChecked == true))
{
while(iterator.MoveNext())
{
RadioButton item = iterator.Current;
// something
}
}
Your original code is better for performance - you will avoid creating delegate and passing it to instance of WhereEnumerableIterator, and then executing delegate for each item in source sequence. But you should note, as #dcastro pointed, difference will be really small and does not worth noting until you will have to optimize this particular loop.
Solution suggested by ReSharper is (maybe) better for readability. I personally like simple if condition in a loop.
UPDATE: Where iterator can be simplified to (also some interfaces are omitted)
public class WhereEnumerableIterator<T> : IEnumerable<T>, IDisposable
{
private IEnumerator<T> _enumerator;
private Func<T,bool> _predicate;
public WhereEnumerableIterator(IEnumerable<T> source, Func<T,bool> predicate)
{
_predicate = predicate;
_enumerator = source.GetEnumerator();
}
public bool MoveNext()
{
while (_enumerator.MoveNext())
{
if (_predicate(_enumerator.Current))
{
Current = _enumerator.Current;
return true;
}
}
return false;
}
public T Current { get; private set; }
public void Dispose()
{
if (_enumerator != null)
_enumerator.Dispose();
}
}
Main idea here - it enumerates original source only when you ask it to move to next item. Then iterator goes to next item in original source and checks if it matches predicate. If match found, then it returns current item and puts enumerating source on hold.
So, until you will not ask items from this iterator, it will not enumerate source. If you will call ToList() on this iterator, it will enumerate source sequence and return all matched items, which will be saved to new list.

Can I rewrite this do-while loop as a foreach loop?

Is there a way to write this code more elegantly with a foreach loop? The "create a new entry" logic is thwarting me, because it needs to execute even if pendingEntries contains no items.
ItemDto itemToAdd; // an input parameter to the method
IEnumerator<Item> pendingEntries = existingPendingItems.GetEnumerator();
pendingEntries.MoveNext();
do // foreach entry
{
Item entry = pendingEntries.Current;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
}
else // create a new entry
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entry);
} while (pendingEntries.MoveNext());
This should be rewritten. I don't know what kind of collection you're using, but Current is undefined in your case since MoveNext could have returned false. As stated in the documentation:
Current is undefined under any of the following conditions:
The last call to MoveNext returned false, which indicates the end of the
collection.
Here is how I would rewrite it:
bool isEmpty = true;
foreach (Item entry in existingPendingItems)
{
ProcessEntry(entry, itemToAdd);
isEmpty = false;
}
if (isEmpty)
{
ProcessEntry(null, itemToAdd);
}
ProcessEntry contains the logic for a single entry, and is easily unit testable.
The algorithm is cleared to read.
The enumerable is still only enumerated once.
foreach (Item entry in existingPendingItems.DefaultIfEmpty())
{
Item entryToSave;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
entryToSave = entry;
}
else // create a new entry
{
entryToSave = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entryToSave);
}
The key is the Enumerable.DefaultIfEmpty() call — this will return a sequence with a default (Item) item if the sequence is empty. This will be null for a reference type.
Edit: fixed bug mentioned by neotapir.
Personally I'd suggest something like this:
ItemDto itemToAdd; // an input parameter to the method
if (existingPendingItems.Any())
{
foreach(Item entry in existingPendingItems)
{
entry.Quantity += itemToAdd.Quantity
Save(entry);
}
}
else
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry);
}
I think this makes the intent of the code much clearer.
EDIT: Changed count to any as per suggestion. Also fixed the add quantity logic
I'd rewrite it as more standard while method. And you've forgot that IEnumerator<T> implements IDisposable, so you should dispose it.
foreach( Item entry in pendingEntries.Current)
{
if( entry != null)
entry.Quantity += itemToAdd.Quantity;
else
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry)
}
cant exactly test it without the items
var pendingEntries = existingPendingItems.Any()
? existingPendingItems
: new List<Item> { Mapper.Map<ItemDto, Item>(itemToAdd) };
foreach (var entry in pendingEntries)
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
Save(entry);
}
The idea here is that you set yourself up for success before iterating. What are you going to iterate over? Either the existing entries, if there are any, or just a new entry otherwise.
By handling this up front, so you know you've got something with which to work, your loop stays very clean.

Categories

Resources