none
Foreach Loop after collection items changes throwing Exception RRS feed

  • Question

  • Foreach Loop after collection items changes throwing Exception 

     foreach (TextFrame txtfrm in grp.TextFrames)
    {
    grp.Ungroup();
    var myRectangle = doc.Pages[i].Rectangles.Add();                                 
    groupobjects(myRectangle, exportFrame);
    }
    
    After Ungroup & Group, Next Iteration will throw a Exception...
    
    


    Requirement :

     Loop all grp.TextFrames... if I Ungroup and Group it should not throw an exception

    Tuesday, September 12, 2017 1:37 PM

Answers

  • You cannot modify the contents while enumerating. I don't really know how many different ways we can repeat the same thing - it is not allowed. Doesn't matter whether you're using a foreach or a for loop, you're going to fail enumeration. If you need to enumerate a set of values while being able to modify them then you have to capture the list separately first.

    I'm going to attempt to unwrap what you're trying to do but honestly it isn't clear what your code is doing. I can see you're enumerating the groups of a page. Within each group you're then enumerating the text frames. For each one you're ungrouping the group. Why are you doing this for each text frame? When you ungroup the group the group is gone so you don't need to do it each time.

    After you ungroup, you're adding a rectangle? You then are calling some grouping function with the rectangle and some export frame stuff that isn't defined here. I have no idea what all this does but I'm going to assume that the ungroup and groupobjects calls are going to change the available groups. Therefore you cannot enumerate them.

    //Capture the groups in an array so you can enumerate the array without worrying about the changes in the groups
    //If ToArray is not available then add a call to OfType<T> before it where T is the type of the group variable
    var groups = doc.Pages[i].Groups.ToArray();
    foreach (var group in groups)
    {
       //Same thing here
       var frames = grp.TextFrames.ToArray();
    
       //Your inner foreach loop isn't doing anything with the
       //text frame so you really don't need to enumerate it
       //at all
       group.Ungroup();
       var rect = doc.Pages[i].Rectangles.Add();
       groupobjects(rect, exportFrame); 
    };

    In the above code the inner foreach loop is gone. You aren't using the textframe you're enumerating so loop doesn't change the behavior other than wasted cycles. Now if you needed that textframe in the groupobjects call then it would be different but in that case each loop would undo the previous loops behavior so you still wouldn't need the inner loop, just the last textframe. The only process that would make sense to me is if you ungrouped first, then enumerated the text frames and added a new rect and grouped those. In that case the var... code would be inside a foreach loop for the group's textframes. This may alter that collection so you'd want to capture the textframes in an array first. Something like this.

    //Capture the groups in an array so you can enumerate the array without worrying about the changes in the groups
    //If ToArray is not available then add a call to OfType<T> before it where T is the type of the group variable
    var groups = doc.Pages[i].Groups.ToArray();
    foreach (var group in groups)
    {
       //Same thing here, need to do this before ungrouping
       var frames = grp.TextFrames.ToArray();
    
       //Ungroup first
       group.Ungroup();
    
       //Now regroup each text frame
       foreach (var frame in frames)
       {
          var rect = doc.Pages[i].Rectangles.Add();
          groupobjects(rect, frame); 
       };
    };

    • Marked as answer by ID GO Wednesday, September 13, 2017 2:16 PM
    Wednesday, September 13, 2017 2:13 PM

All replies

  • I can't see what the Ungroup/Group methods are doing in your code example, but in general you cannot change the items in a collection from within a foreach loop.

    This is a subject that has come up many times before! (E.g. see here, but you could probably find lots of other examples)

    Basically because the foreach loop makes use of an 'enumerator' that tracks its position in the collection. And the enumerator will lose its place if some other code messes about with the collection at the same time.

    Tuesday, September 12, 2017 1:44 PM
  • c# to Indesign 

    Images and Rectangles Grouping...

    for (int index = 0; index < grp.TextFrames.Count; index++)

                               {

    Tried above Not working

    Tuesday, September 12, 2017 1:52 PM
  • What is the actual exception that you are getting?

    Tuesday, September 12, 2017 1:56 PM
  • You cannot change the collection being enumerated while inside a foreach loop. You can technically do it using a for loop but you are making a potentially dangerous assumption that the iteration hasn't changed which is exactly why you aren't allowed to do it in a foreach loop. 

    var values = new List<int>() { 1, 2, 3, 4, 5 };
    
    foreach (var value in values)
    {
       //Remove an element
       values.Remove(3);
    
       //Add an element to the beginning
       values.Insert(1, 2);
    }
    
    

    What set of values do you expect to get from this enumeration?

    You really need to clarify why you must enumerate through a list you are changing. I suspect you don't actually want to do that at all. More likely you need to enumerate a list and identify things that need to change. Then make those changes separately.

    Michael Taylor
    http://www.michaeltaylorp3.net

    Tuesday, September 12, 2017 1:57 PM

    • Edited by ID GO Tuesday, September 12, 2017 2:06 PM
    Tuesday, September 12, 2017 2:01 PM
  • Its a Foreach inside another for each...
    Tuesday, September 12, 2017 2:09 PM
  •    grp.Ungroup();
       txtfrm.Delete();
       var myRectangle = doc.Pages[i].Rectangles.Add();
       groupobjects(myRectangle, exportFrame);

    If I Comment below 2 lines for loop will work fine, But I need to Ungroup and Group 2 Different Objects.

      // grp.Ungroup();
       txtfrm.Delete();
       var myRectangle = doc.Pages[i].Rectangles.Add();
      // groupobjects(myRectangle, exportFrame);

    Tuesday, September 12, 2017 2:13 PM
  • "If I Comment below 2 lines for loop will work fine, But I need to Ungroup and Group 2 Different Objects."

    You can't. Let's think about this logically for a minute. Imagine that you are checking email. As you read each email something behind the scenes can completely alter the list of emails. It could remove emails you have or haven't read yet. It could add emails before emails you've already read or add them after. It could even delete the email you're currently. How can you guarantee that you've read all the emails that have been sent to you and ensure that you don't read the same email more than once?

    That is the problem you have with a foreach. Foreach is going to allow you to enumerate the items one by one. Each item will be seen once and no items will be missed. If that cannot be guaranteed then it is a runtime error. That is exactly what foreach does - it blows up when it can no longer make those guarantees.

    We don't understand your process so we cannot really provide a solution but you clearly cannot enumerate something that you're going to regroup. You need to rethink the problem and figure out how to enumerate through the list and make your changes. There are several options available including making a single pass through (using foreach) and identifying the items to change and then making the changes. The alternative is to start enumerating, detect that a change is made, make the change and then restart the enumeration process from the beginning. Depending upon how often things change determines how well each solution performs. Personally I would go with the former solution as it tends to work better in my experience.

    Tuesday, September 12, 2017 3:50 PM
  • Try looping in reverse, from the end of the collection to the beginning, so the index numbers are always valid.

    Paul

    Tuesday, September 12, 2017 10:27 PM
  •    for (int index = grp.TextFrames.Count -1; index >= 0;  index--)
                            {
                                TextFrame txtfrm = grp.TextFrames[index];

    I tried Reverse as well ,Nope Not working...

    err:
     foreach (var grp in doc.Pages[i].Groups)
    {
     foreach (TextFrame txtfrm in grp.TextFrames)
    {
    grp.Ungroup();
    var myRectangle = doc.Pages[i].Rectangles.Add();                                 
    groupobjects(myRectangle, exportFrame);
    goto err;
    }
    }

    Above code is Working...But I dont want to use "Goto statement" ...

    Can Some one Upgrade the above code in a Better Way... I don't want Goto.

    Note : I need in "foreach" because for Loop not working.


    • Edited by ID GO Wednesday, September 13, 2017 6:50 AM
    Wednesday, September 13, 2017 6:36 AM
  • Hi ID GO,

    Thank you for posting here.

    According to your question is more related to office, I will move it to General Office Development forum for suitable support.

    This forum discuss and ask the C# programming language, IDE, libraries, samples and tools.

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, September 13, 2017 7:55 AM
  • You cannot modify the contents while enumerating. I don't really know how many different ways we can repeat the same thing - it is not allowed. Doesn't matter whether you're using a foreach or a for loop, you're going to fail enumeration. If you need to enumerate a set of values while being able to modify them then you have to capture the list separately first.

    I'm going to attempt to unwrap what you're trying to do but honestly it isn't clear what your code is doing. I can see you're enumerating the groups of a page. Within each group you're then enumerating the text frames. For each one you're ungrouping the group. Why are you doing this for each text frame? When you ungroup the group the group is gone so you don't need to do it each time.

    After you ungroup, you're adding a rectangle? You then are calling some grouping function with the rectangle and some export frame stuff that isn't defined here. I have no idea what all this does but I'm going to assume that the ungroup and groupobjects calls are going to change the available groups. Therefore you cannot enumerate them.

    //Capture the groups in an array so you can enumerate the array without worrying about the changes in the groups
    //If ToArray is not available then add a call to OfType<T> before it where T is the type of the group variable
    var groups = doc.Pages[i].Groups.ToArray();
    foreach (var group in groups)
    {
       //Same thing here
       var frames = grp.TextFrames.ToArray();
    
       //Your inner foreach loop isn't doing anything with the
       //text frame so you really don't need to enumerate it
       //at all
       group.Ungroup();
       var rect = doc.Pages[i].Rectangles.Add();
       groupobjects(rect, exportFrame); 
    };

    In the above code the inner foreach loop is gone. You aren't using the textframe you're enumerating so loop doesn't change the behavior other than wasted cycles. Now if you needed that textframe in the groupobjects call then it would be different but in that case each loop would undo the previous loops behavior so you still wouldn't need the inner loop, just the last textframe. The only process that would make sense to me is if you ungrouped first, then enumerated the text frames and added a new rect and grouped those. In that case the var... code would be inside a foreach loop for the group's textframes. This may alter that collection so you'd want to capture the textframes in an array first. Something like this.

    //Capture the groups in an array so you can enumerate the array without worrying about the changes in the groups
    //If ToArray is not available then add a call to OfType<T> before it where T is the type of the group variable
    var groups = doc.Pages[i].Groups.ToArray();
    foreach (var group in groups)
    {
       //Same thing here, need to do this before ungrouping
       var frames = grp.TextFrames.ToArray();
    
       //Ungroup first
       group.Ungroup();
    
       //Now regroup each text frame
       foreach (var frame in frames)
       {
          var rect = doc.Pages[i].Rectangles.Add();
          groupobjects(rect, frame); 
       };
    };

    • Marked as answer by ID GO Wednesday, September 13, 2017 2:16 PM
    Wednesday, September 13, 2017 2:13 PM
  • Thanks Cool
    Wednesday, September 13, 2017 2:16 PM