简体   繁体   中英

Unexpected Foreach behavior C#

The foreach loop have an unexpected behavior when the condition is true in fact after all instruction inside if statement , the loop breaks. I tried to comment the if statement and all works fine (iterate all the elements into the ienumerable). Someone can explain me why?

var allRef = projDefinition
    .Element(msbuild + "Project")
    .Elements(msbuild + "ItemGroup")
    .Elements(msbuild + "COMReference")
    .Where(refElem => find.Any(f => refElem.FirstAttribute.Value.ToLower().Contains(f)))
    .Select(refElem => refElem);


foreach (var xElement in allRef)
{
    var name = xElement.FirstAttribute.Value;
    var dllPath = dllFiles.FirstOrDefault(dll => dll.ToLower().Contains(name.ToLower()));

    if (dllPath != null)
    {
        var dllName = dllPath.Substring(dllPath.LastIndexOf('\\') + 1, dllPath.LastIndexOf('.') - dllPath.LastIndexOf('\\') - 1);
        xElement.Remove();
        XElement elem = new XElement(msbuild + "Reference", new XAttribute("Include", dllName));
        elem.Add(new XElement(msbuild + "HintPath", HintPath.GetHintPath(dllPath)));
        elem.Add(new XElement(msbuild + "EmbedInteropTypes", "False"));
        projDefinition.Root.Elements(msbuild + "ItemGroup").ElementAt(0).Add(elem);
    }

}

projDefinition.Save(fullProjectPath);

You're looping over allRef while at the same time you're removing from it with xElement.Remove , which messes with loop iterator.

Basicaly foreach creates IEnumerator under the hood and uses it to move from current element to next. You're disturbing it by removing element from collection while still going over it.

It will not cause exception, but your loop will cause unexpected behaviour like not going over all elements.

You may want to read how does foreach works

Simple solution for this kind of problem is adding .ToList() (as @Ivan Stoev noted in comments). It works because it creates new list so foreach iterator is created to that new list instead of your allRef . Which means that you can safely remove elements from allRef .

For some more detail on how this works specifically, an XDocument is essentially similar to 'linked list' of nodes. Each element has a reference to its sibling and to its parent.

The various queries (like Elements ) all use iterator blocks and are lazily evaluated. You can follow this in the reference source .

Once you call Remove on the current loop variable, the element is removed from the document tree. The references it had to its parent and its sibling are now both null . When the iterator resumes on the call to MoveNext as part of your foreach loop, there's nowhere to go; iteration complete.

As explained in the comments & other answers, a simple call to ToList before iterating over that will resolve the issue as it will iterate through your query and cache it in a new list. Removing nodes from the document later cannot affect the content of your list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM