I have a foreach loop that iterates different levels of a treeView in C# that its simplified version looks like this:
foreach (TreeNode childNode in currentNode.Nodes)
{
if (!someCondition)
{
currentNode.Remove();
}
}
but the problem is that when a node (for example from a list of node1, node2, node3 and node4) is removed the list becomes shorter and the foreach loop skips one iteration (for example, say if node2 was to be removed the list becomes node1, node3 and node4 and the next node that the foreach loop considers will be node4 instead of node3). This is because the framework is storing these nodes in an array/list so I would like to know if it is possible to make a foreach loop go back one iteration when I want to remove a node from the tree.
I'm pretty new in .NET framework so your help is really appreciated.
The desired result can perhaps be achieved using Linq by setting
currentNode.Nodes = currentNode.Nodes.Where( n => SomeCondition( n ) ).ToList();
or something similar, so no explicit iteration is necessary. A less elegant solution is using an explicit for-loop running backwards, so that the loop index cannot become invalid. However I would consider this bad practice when a more structural approach is available.
You can use for loop here:
// Pay attention to reversed order:
// each currentNode.Remove() changes currentNode.Nodes.Count
for (int i = currentNode.Nodes.Count - 1; i >= 0; --i) {
TreeNode childNode = currentNode.Nodes[i];
if (!someCondition) {
currentNode.Remove();
}
}
No this is not possible because the iterations of a foreach loop aren't "indexed" in a strict sense.
A for loop is, however, indexed because you provide it with a counting mechanism yourself. There you can change your counter.
Usually it's not a great idea to modify the collection that you are iterating through within a foreach. You should consider using a for loop instead and manually keep track of the current index.
Related
In my code I got an ConcurrentDictionary now I want to iterate over each element in the Dictionary, but if a condition is true I want to remove an element from this Dictionary so I can't use a foreach loop. Also it might happen that the Dictionary will get a new element while in the loop or get one removed from a different thread. After some research I ended up using ElementAt.
Now my question is if the ConcurrentDictionary will release the indexes again like a List does. So that the first element will always have the index 0.
This is what my code looks like, CommandHandler.Timeouter is from ConcurrentDictionary:
int current = 0;
while (CommandHandler.Timeouter.Count() > current)
{
var info = CommandHandler.Timeouter.ElementAt(current);
var timeoutcooldown = info.Value.LastCommandTime.AddMinutes(1);
if (timeoutcooldown < DateTime.UtcNow)
{
CommandHandler.Timeouter.TryRemove(info.Key, out _);
}
else current++;
}
ElementAt just treats the dictionary as an IEnumerable<KeyValuePair<TKey, TValue>>. Dictionaries are not ordered. The index is therefore meaningless. Think of the elements coming back in random order each time. Also, ElementAt has no way to make this thread safe.
It seems you want to implement cache expiration. Consider just using lock to access a normal dictionary. If there is not much contention this will be the simplest solution and very fast.
An alternative code pattern to this loop would be this:
var itemsToExpire = myDict.Where(/* compute expiration */).ToList();
foreach (var item in itemsToExpire)
myDict.Remove(item);
No need for any complicated looping.
I have a List<Object>. I want to iterate over this list, but I want the order to be inverted, so when I use the object, it will start from the last to the first. How can I do it, minimal code?
Thanks.
P.S. I'm using C#, WinForms.
Use the extension method Enumerable<T>.Reverse. This will iterate through the list in a reverse order and leave the original list intact.
foreach(var item in list.AsEnumerable().Reverse())
{
}
Reverse , however, traverses the list and caches your items in a reverse order when iteration starts. In 90% of the cases this is fine, because it's still a O(n) operation, but if you want to avoid this cache just use a plain old for
for(int i = list.Count - 1; i >= 0; i--) { }
I'd be surprised if anyone can explain this, but it'd be interesting to know if others can reproduce the weirdness I'm experiencing...
We've got a thing based on InfoPath that processes a lot of forms. Form data should conform to an XSD, but InfoPath keeps adding its own metadata in the form of so-called "my-fields". We would like to remove the my-fields, and I wrote this simple method:
string StripMyFields(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
var matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n => n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/"));
Dbug("Found {0} nodes to remove.", matches.Count());
foreach (var m in matches)
m.ParentNode.RemoveChild(m);
return doc.OuterXml;
}
Now comes the really weird stuff! When I run this code it behaves as I expect it to, removing any nodes that are in InfoPath namespaces. However, if I comment out the call to Dbug, the code completes, but one "my-field" remains in the XML.
I've even commented out the content of the convenient Dbug method, and it still behaves this same way:
void Dbug(string s, params object[] args)
{
//if (args.Length > 0)
// s = string.Format(s, args);
//Debug.WriteLine(s);
}
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<skjema xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-03-03T22:25:25" xml:lang="en-us">
<Field-1643 orid="1643">data.</Field-1643>
<my:myFields>
<my:field1>Al</my:field1>
<my:group1>
<my:group2>
<my:field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2009-01-01</my:field2>
<Field-1611 orid="1611">More data.</Field-1611>
<my:field3>true</my:field3>
</my:group2>
<my:group2>
<my:field2>2009-01-31</my:field2>
<my:field3>false</my:field3>
</my:group2>
</my:group1>
</my:myFields>
<Field-1612 orid="1612">Even more data.</Field-1612>
<my:field3>Blah blah</my:field3>
</skjema>
The "my:field3" element (at the bottom, text "Blah blah") is not removed unless I invoke Dbug.
Clearly the universe is not supposed to be like this, but I would be interested to know if others are able to reproduce.
I'm using VS2012 Premium (11.0.50727.1 RTMREL) and FW 4.5.50709 on Win8 Enterprise 6.2.9200.
First things first. LINQ uses concept known as deferred execution. This means no results are fetched until you actually materialize query (for example via enumeration).
Why would it matter with your nodes removal issue? Let's see what happens in your code:
SelectNodes creates XPathNodeIterator, which is used by XPathNavigator which feeds data to XmlNodeList returned by SelectNodes
XPathNodeIterator walks xml document tree basing on XPath expression provided
The Cast and Where simply decide whether node returned by XPathNodeIterator should participate in final result
We arrive right before DBug method call. For a moment, assume it's not there. At this point, nothing have actually happened just yet. We only got unmaterialized LINQ query.
Things change when we start iterating. All the iterators (Cast and Where got their own iterators too) start rolling. WhereIterator asks CastIterator for item, which then asks XPathNodeIterator which finally returns first node (Field-1643). Unfortunately, this one fails the Where test, so we ask for next one. More luck with my:myFields, it is a match - we remove it.
We quickly proceed to my:field1 (again, WhereIterator → CastIterator → XPathNodeIterator), which is also removed. Stop here for a moment. Removing my:field1 detaches it from its parent, which results in setting its (my:field1) siblings to null (there's no other nodes before/after removed node).
What's the current state of things? XPathNodeIterator knows its current element is my:field1 node, which just got removed. Removed as in detached from parent, but iterator still holds reference. Sounds great, let's ask it for next node. What XPathNodeIterator does? Checks its Current item, and asks for NextSibling (since it has no children to walk first) - which is null, given we just performed detachment. And this means iteration is over. Job done.
As a result, by altering collection structure during iteration, you only removed two nodes from your document (while in reality only one, as the second removed node was child of the one already removed).
Same behavior can be observed with much simpler XML:
<Root>
<James>Bond</James>
<Jason>Bourne</Jason>
<Jimmy>Keen</Jimmy>
<Tom />
<Bob />
</Root>
Suppose we want to get rid of nodes starting with J, resulting in document containing only honest man names:
var doc = new XmlDocument();
doc.LoadXml(xml);
var matches = doc
.SelectNodes("//node()")
.Cast<XmlNode>()
.Where(n => n.Name.StartsWith("J"));
foreach (var node in matches)
{
node.ParentNode.RemoveChild(node);
}
Console.WriteLine(doc.InnerXml);
Unfortunately, Jason and Jimmy remain. James' next sibling (the one to be returned by iterator) was originally meant to be Jason, but as soon as we detached James from tree there's no siblings and iteration ends.
Now, why it works with DBug? Count call materializes query. Iterators have run, we got access to all nodes we need when we start looping. The same things happens with ToList called right after Where or if you inspect results during debug (VS even notifies you inspecting results will enumerate collection).
I think this is down to the schrodinger's cat problem that Where will not actually compile the results of the query until you view or act upon it. Meaning, until you call Count() (or any other function for getting the results) or view it in debugger, the results don't exist. As a test, try put it as such:
if (matches.Any())
foreach (var m in matches)
m.ParentNode.RemoveChild(m);
Very strange, its only when you actually view the results while debugging that it removes the last node. Incidentally, converting the result to a List and then looping through it also works.
List<XmlNode> matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n => n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/")).ToList();
foreach (var m in matches)
{
m.ParentNode.RemoveChild(m);
}
jimmy_keen's solution worked for me. I had just a simple
//d is an XmlDocument
XmlNodeList t = d.SelectNodes(xpath);
foreach (XmlNode x in t)
{
x.ParentNode.RemoveChild(x);
}
d.Save(outputpath);
this would remove only 3 nodes while stepping through in debug mode would remove 1000+ nodes.
Just adding a Count before the foreach solved the problem:
var count = t.Count;
I'm doing some procesing in a treeview, I don't use neither a stack or a queue to process all the nodes, I just do this:
void somemethod(TreeNode root){
foreach(TreeNode item in root.Nodes)
{
//doSomething on item
somemethod(item);
}
}
I'm a litle block right know (can't think with clarity) and I can't see what kind of tree processing I'm doing. Is this BFS or DFS or neither of them?
My clue was DFS but wasn't sure. The CLR don't do anything weird like process two siblings before passing down taking advantage of multiprocessing? That weird tough comes to my mind that clouded my judgment
You are doing a DFS (Depth first search/traversal) right now using recursion.
Its depth first because recursion works the same way as a stack would - you process the children of the current node before you process the next node - so you go for depth first instead of breadth.
Edit:
In response to your comment / updated question: your code will be processed sequentially item by item, there will be no parallel processing, no "magic" involved. The traversal using recursion is equivalent to using a stack (LIFO = last in, first out) - it is just implicit. So your method could also have been written like the following, which produces the same order of traversal:
public void SomeMethod(TreeNode root)
{
Stack<TreeNode> nodeStack = new Stack<TreeNode>();
nodeStack.Push(root);
while (nodeStack.Count > 0)
{
TreeNode node = nodeStack.Pop();
//do something on item
//need to push children in reverse order, so first child is pushed last
foreach (TreeNode item in node.Nodes.Reverse())
nodeStack.Push(item);
}
}
I hope this makes it clearer what is going on - it might be useful for you to write out the nodes to the console as they are being processed or actually walk through step by step with a debugger.
(Also both the recursive method and the one using a stack assume there is no cycle and don't test for that - so the assumption is this is a tree and not any graph. For the later DFS introduces a visited flag to mark nodes already seen)
Im pretty sure your example corresponds to "Depth first search", because the nodes on which you "do something" increase in depth before breadth.
This question already has answers here:
Closed 12 years ago.
Possible Duplicates:
Exception during iteration on collection and remove items from that collection
How to remove elements from a generic list while iterating around it?
Better way to remove matched items from a list
// tmpClientList is List<Client> type
if (txtboxClientName.Text != "")
foreach (Client cli in tmpClientList)
if (cli.Name != txtboxClientName.Text)
tmpClientList.Remove(cli);
Error: "Collection was modified; enumeration operation may not execute."
How can i remove items from the list, in some simple way, without saving indexes of these items in another list or array, and removing them in another place in the code. Tried also RemoveAt(index) but it's exactly the same situation, modifying when loop runs.
Move backwards through the list.. that way removing an item does not affect the next item.
for(var i=tmpClientList.Count-1;i>=0;i--)
{
if (tmpClientList[i].Name != txtboxClientName.Text)
tmpClientList.RemoveAt(i);
}
On a List<T>, there is a RemoveAll method that takes a delegate to indicate whether to remove the item. You can use it like this:
tmpCLientList.RemoveAll(cli => cli.Name != txtboxClientName.Text);
Either use a for/while loop, or tmpClientList.RemoveAll(a => a.Name == txtboxClientName.Text). As you didn't specify which c# version you are using, ymmw.
Don't use foreach. Use for and descend the list (i.e. start from the end), using RemoveAt.
So,
// tmpClientList is List<Client> type
if (txtboxClientName.Text != "")
foreach (int pos = tmpClientList.Length - 1; pos >= 0; pos--)
{
Client cli = tmpClientList[pos];
if (cli.Name != txtboxClientName.Text)
tmpClientList.RemoveAt(pos);
}
The problem is that you are trying the modify the list in a foreach iteration. Replace that with a for and you should be ok.
Also, since you seem to be using user input for the name, consider cleaning up the input a bit, at least with a Trim() to remove extra white spaces. If you don't, 'John ' and 'John' will be two different things.
Same for the initial != "" check.
You can create another list with the items you want to delete and iterate the new list to remove items from your "txtboxClientName" list.
Actually, foreach uses Enumerators to iterate through given Item-Collections. Going further the System.Collections.Generic.List<T> implements the IEnumarable-Interface to provide a Class, that knows how to iterate through the items of the list, i.e. the Enumerator. Now if you iterate through that list by using foreach the Enumerator keeps track of the current position, how to reach the next position and some other stuff. The internal logic could be something like storing the number of items in a variable n and then access all objects from 0 to n-1. As you may notice if any object is removed between the iteration steps we shall end in a NullReferenceException when the Enumerator tries to deliver the last object of the list. So to prevent any iteration failures, the list itself is not allowed to be modified during Enumeration.
Hope I was able to state that out at least a little bit comprehensively. :-)