I have the following code where I am checking if some elements are not matching in my dictionary then I want to remove the unmatching elements from the local item. The problem is, When a value is removed from the collection, for some reason it also modifies the parental structure.
My other problem is, for example if I have list as "A","B","B", using the Except is only giving me the single B but not the other. Please help.
public void AddLogs(IEnumerable<ReportGenerationTypes> subElements)
{
var changeDetails = new Dictionary<AuditSaveHeader, List<string>>();
List<string> AuditableItems = null;
List<string> subItems = new List<string>();
foreach (var item in subElements)
{
subItems.Add(item.ToString());
}
foreach (var item in auditLogData?.AuditHeaders)
{
if (!changeDetails.ContainsKey(item))
{
changeDetails.Add(item, null);
}
AuditableItems = new List<string>();
foreach (var inner in item.AuditChangeValues)
{
AuditableItems.Add(inner.Auditable.ToString());
}
changeDetails[item] = AuditableItems;
}
for (int i = 0; i < changeDetails.Count; i++)
{
var result = kp.Value.Except(subItems);
Auditable AuditItem = Auditable.Unassigned;
//I think the problem lies with the below code not sure.
if (result != null && result.Count() > 0)
{
foreach (var item in result)
{
Enum.TryParse(item, out AuditItem);
var itemToRemove = kp.Key.AuditChangeValues.Where(x => x.Auditable == AuditItem).FirstOrDefault();
//The following line effects the AuditChangeValues object and not just my dictionary.
kp.Key.AuditChangeValues.Remove(itemToRemove);
}
}
}
}
Promoting my comment to answer:
You are using some vars that are not shown, like kp, auditLogData, etc. and overall is not clear what you want to achieve.
Anyway I agree the problem is you are editing the reference to an object. You could try cloning the objects, etc. But without really understanding the code is hard to tell.
Related
How can I delete the current element of an array inside a foreach-loop?
My program gets data form a DB and sends it to a new one via HTTP requests. Now I want to post a JSON string to my new DB. If it was a success I want to delete the current array item which I'm working with. Something like this.
foreach(var item in array)
{
bool decide = method.DoSomething();
if(decide == true)
{
//delete current item
}
}
since you cannot delete items from an array and change the size of it here is a loop approach using a second collection
List<itemClass> keepCollection = new List<itemClass>();
foreach(var item in array)
{
bool decide = method.DoSomething();
if(decide == false)
{
keepCollection.Add(item);
}
}
If you need it again in array form just call ToArray()
var finalResult = keepCollection.ToArray();
appraoch with Linq which creates a new array with valid elements and overwrites the existing array
array = array.Where(x => !method.DoSomething(x)).ToArray(); //select valid elements
there are 2 ways (both were tested)
foreach (var item in array.ToList())
{
bool decide = method.DoSomething();
if (decide == true)
{
item.Remove();
}
}
and
for ( i=0; i < array.Length; i++)
{
bool decide = method.DoSomething();
if (decide == true)
{
array[i].Remove();
}
}
Whenever you want to delete entries from a collection, you should never loop through that collection from beginning to end, but always from end back to beginning.
By the way, C# does not allow you deleting entries from a collection while looping through that collection using a foreach loop.
I need to remove the data from an ICollection if the ID of the collection is in a List of ID's.
What I have so far:
foreach (var notSelectedToolId in notSelectedToolIds)
{
for (int i = 0; i < matchingHoleInfoVm.ToolHeader.Count; i++)
{
if (matchingHoleInfoVm.ToolHeader.ElementAt(i).ToolID == notSelectedToolId)
{
matchingHoleInfoVm.ToolHeader.ElementAt(i) = new ToolHeaderViewModel();
}
}
}
The error I get: "This expression cannot be used as an assignment target".
How can I make this work?
You can't, basically - not with just ICollection (or even ICollection<T>). Neither of them allow you to replace an existing element with a new one.
With IList<T>, it's pretty easy:
var list = matchingHoleInfoVm.ToolHeader;
for (int i = 0; i < list.Count; i++)
{
if (list[i].ToolID == notSelectedToolId)
{
list[i] = new ToolHeaderViewModel();
}
}
In most cases where you've already got an ICollection<T>, you'll find it already implements IList<T>. If it doesn't, you need to look carefully at what the collection actually is, and consider whether "replace at the same index" even makes sense. (If it's a HashSet<,>, for example, you can just remove the existing item and add a new one...)
EDIT: If you only need to remove the items, you can just use the Remove method:
var itemsToRemove = collection.Where(c => c.ToolID == notSelectedToolId).ToList();
.ToList();
foreach (var item in itemsToRemove)
{
collection.Remove(item);
}
Additionally, you don't need nested loops - you can use something like:
var itemsToRemove = collection.Where(c => notSelectedToolIds.Contains(c.ToolID))
to find all of the ones to remove in a single pass (then remove them as above).
You could apply a filter to the matchingHoleInfoVm.ToolHeader using LINQ to return a new list. Something like
var toolsExlcuded = matchingHoleInfoVm.ToolHeader.Where(x => !notSelectedToolIds.Contains(x.ToolId)).ToList();
I need to iterate over a list (or whatever enumeration), but I'd like to add values into the list in the course of the iteration.
This is an example.
public static void RunSnippet()
{
List<int> hello = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
}
I expect to get "1,2,3,100", but instead I got this error.
How can I iterate over a list that is changing in the process?
ADDED
What I want to accomplish is that I iterate over elements to process something. The thing is that some of the elements needs to be decomposed into sub elements on and on.
public static void RunSnippet()
{
List<Element> hello = new List<Element>();
hello.Add(Element); hello.Add(Element); hello.Add(Element);
foreach (var x in hello)
{
List<Element> decomposed;
decomposed = Decompose(x);
if (decomposed != null) {
foreach (var y in decomposed)
{
hello.Add(y);
}
}
}
}
You can't, basically. Not with a foreach loop, anyway. You can use a straight for loop:
for (int i = 0; i < hello.Count; i++)
{
int x = hello[i];
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
I would personally try to avoid doing it in the first place though - it can get very hard to reason about whether you'll ever complete, or if you'll skip items (if you're removing instead of adding, or adding before your current position).
You can't. You should create a new list and store the values in there.
public static void RunSnippet()
{
List<int> hello = new List<int>();
List<int> additions = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
additions.Add(100);
}
}
hello.AddRange(additions);
}
Use a snapshot of it instead:
foreach (var x in hello.ToArray())
{
// whatever here
}
Problem solved! Well, in a way. Items added during iteration would not be included.
No, you can't iterate over a list and modify them in the same iteration. Use a new list instead.
I found that there is stack in C#. I guess I could use stack.
public static void RunSnippet()
{
Stack<int> hello = new Stack<int>();
hello.Push(1); hello.Push(2); hello.Push(3);
while (hello.Count > 0)
{
int x = hello.Pop();
Console.WriteLine(x);
if (x == 1) {
hello.Push(100);
}
}
}
Using foreach you can't! You could use a for-loop, but it's very very bad style to do things like this. Things like this make your code very error prone, unpredictable and hard to debug.
There are answers that claims what you want cannot be achieved with foreach. That claim is wrong, all you need to do is to write a custom class with a custom enumerator.
public class CustomList : IEnumerable<int>
{
readonly List<int> list = new List<int>{1,2,3,4};
private int now = 0;
public void Add(int n)
{
list.Add(n);
}
public IEnumerator<int> GetEnumerator()
{
while (now<list.Count)
{
yield return list[now];
now++;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Now the following piece of code will print 1,2,3,4 and 100 to the screen:
var list = new CustomList();
foreach (int n in list)
{
if(n==1)
list.Add(100);
Console.WriteLine(n);
}
But I write this only as a proof of concept. You don't want to do this. If you will only add new items on the back, use Queue as others has said. If you will always add new items on the front, use Stack. If you will need both, write a custom LinkedList class with Dequeue (=Pop), Enqueue and Push operations, use something like :
while(list.notEmpty())
var item = list.Dequeue();
//bla bla
and you are all set. (You could even write a custom Enumerator again, to use with foreach, but we are destructing the list as we go, so it is against the spirit of Enumerations, and why bother in any case)
I had learnt by reading your great answers here, that it is not good practice deleting items from within a foreach loop, as it is (and I quote) "Sawing off the branch you're sitting on".
My code currently removes the text from the dropdownlist, but the actual item remains (just without text displayed).
In other words, it isn't deleting, and probably can't because you can't delete from within a foreach loop.
After hours of trying I am unable to get my head around a way of doing it.
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
//this is the foreach loop
foreach (ToolStripItem mItem in favoritesToolStripMenuItem.DropDownItems)
{
//This rules out seperators
if (mItem is ToolStripMenuItem)
{
ToolStripMenuItem menuItem = mItem as ToolStripMenuItem;
//This matches the dropdownitems text to the CheckedItems String
if (((ToolStripMenuItem)mItem).Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
//And deletes the item
menuItem.DropDownItems.Remove(mItem);
}
}
}
}
But it isn't deleting because it is within a foreach loop!
I would greatly appreciate your help, and be truly amazed if anyone can get their head around this code :)
Kind Regards
Fun with LINQ!
// Loop through the checked items, same as you did.
foreach (var checkedItem in this.organizeFav.CheckedItems)
{
// Cast from IEnumerable to IEnumerable<T> so we can abuse LINQ
var matches = favoritesToolStripMenuItem.DropDownItems.Cast<ToolStripItem>()
// Only items that the Text match
.Where(item => item.Text == checkedItem.Text)
// Don't match separators
.Where(item => item is ToolStripMenuItem)
// Select the keys for the later .Remove call
.Select(item => item.Name);
// Loop through all matches
foreach (var key in matches)
{
// Remove them with the Remove(string key) overload.
favoritesToolStripMenuItem.Remove(key);
}
}
You don't need a foreach loop - just use a regular loop but go in reverse, start at the end and go to the beginning.
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
//this *replaces* the foreach loop
for(int j = favoritesToolStripMenuItem.DropDownItems.Count - 1; j >= 0; j--)
{
ToolStripMenuItem menuItem = favoritesToolStripMenuItem.DropDownItems[j] as ToolStripMenuItem;
//This rules out seperators
if (menuItem != null)
{
//This matches the dropdownitems text to the CheckedItems String
if (menuItem.Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
favoritesToolStripMenuItem.DropDownItems.Remove(menuItem);
}
}
}
}
this was #Kurresmack's code rearranged, i just coded it directly here in the page so excuse any small syntax error or anything obvious i overlooked (disclaimer: it is a sample!!)
You can still treat favoritesToolStripMenuItem.DropDownItems as a collection like you were, but you don't have to enumerate over it using a foreach. This cuts down on a few lines of code, and it works because you are iterating it in reverse order, you will not get an index out of bounds exception.
Try something like this:
//For each checked box, run the delete code
for (int i = 0; i < this.organizeFav.CheckedItems.Count; i++)
{
List<ToolStripItem> toRemove = new List<ToolStripItem>();
//this is the foreach loop
foreach (ToolStripItem mItem in favoritesToolStripMenuItem.DropDownItems)
{
//This rules out seperators
if (mItem is ToolStripMenuItem)
{
ToolStripMenuItem menuItem = mItem as ToolStripMenuItem;
//This matches the dropdownitems text to the CheckedItems String
if (((ToolStripMenuItem)mItem).Text.ToString() == organizeFav.CheckedItems[i].ToString())
{
toRemove.Add(mItem);
}
}
}
foreach(var item in toRemove)
{
favoritesToolStripMenuItem.DropDownItems.Remove(item);
}
}
To my mind, the way to make the code work is:
1. Create an instance of the type the favoritesToolStripMenuItem.DropDownItems collection is.
2. In the foreach loop, add all items, you do not want to be removed, to that collection.
3. Make favoritesToolStripMenuItem.DropDownItems to point to the new collection. Or clear favoritesToolStripMenuItem.DropDownItems and load the items from the new collection to it.
Hope this helps
Instead of a foreach use a reverse for-Loop:
for(int reverseIndex = myList.Count - 1; reverseIndex >= 0; reverseIndex--)
{
var currentItem = myList[reverseIndex];
if(MatchMyCondition(currentItem))
{
myList.Remove(currentItem);
}
}
I am trying to do this:
foreach (Settings sets in MySets)
{
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
sets = ss.getSettings();
}
}
return;
}
}
But since the sets spawned variable is readonly, I cant override it.
I would also like to do something like this
foreach (Settings sets in MySets)
{
if(sets.pName == someName)
sets.RemoveFromList();
}
How can I accomplish this? Lists have a very nice Add() method, but they forgot the rest :(
You can use:
MySets.RemoveAll(sets => sets.pName == someName);
to remove all the items that satisfy a specific condition.
If you want to grab all the items satisfying a condition without touching the original list, you can try:
List<Settings> selectedItems = MySets.FindAll(sets => sets.pName == someName);
foreach loops don't work here as trying to change the underlying list will cause an exception in the next iteration of the loop. Of course, you can use a for loop and manually index the list. However, you should be very careful not to miss any items in the process of removing an item from the list (since the index of all the following items will get decremented if an element is removed):
for (int i = 0; i < MySets.Count; ++i) {
var sets = MySets[i]; // simulate `foreach` current variable
// The rest of the code will be pretty much unchanged.
// Now, you can set `MySets[i]` to a new object if you wish so:
// MySets[i] = new Settings();
//
// If you need to remove the item from a list and need to continue processing
// the next item: (decrementing the index var is important here)
// MySets.RemoveAt(i--);
// continue;
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
// Assigning to `sets` is not useful. Directly modify the list:
MySets[i] = ss.getSettings();
}
}
return;
}
}
You can't do it in a 'regular' for loop?