Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. .NET (Core and Framework)
  4. You can't assign to an array element within a For Each loop! [modified]

You can't assign to an array element within a For Each loop! [modified]

Scheduled Pinned Locked Moved .NET (Core and Framework)
csharphtmldata-structureshelp
12 Posts 4 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Offline
    P Offline
    Peter R Fletcher
    wrote on last edited by
    #1

    It is possible that I am the only person in the world who didn't already know this :) , but there is one very important difference between the behavior of a For Each loop that iterates through an array (or an array of Structures, which was the context that has just bitten me!) and the corresponding For Next loop. When you use For Each, the item that is passed into the loop is effectively passed by Value. Any assignment that you make to it within the loop is lost as soon as you leave it. If you put exactly the same code within an equivalent For Next loop, the assignments "stick". The following very short Console Application demonstrates this:

    Module Module1
    Dim intArray(10) As Integer
    Sub Main()
    Dim intI As Integer
    For intI = 0 To 10 ' fill the array
    intArray(intI) = intI
    Next

        For Each item In intArray ' show that the data has been stored
            Console.Write(item.ToString & ", ")
        Next
        Console.Write(vbCrLf)
    
        intI = 10
        For Each item In intArray ' fill it again in reverse order
            item = intI
            Console.Write(item.ToString & ", ") ' show that contents of the "local" copy of the array element has been changed
            intI -= 1
        Next
        Console.Write(vbCrLf)
    
        For Each item In intArray
            Console.Write(item.ToString & ", ") ' but the contents of the actual array have not!
        Next
        Console.Write(vbCrLf)
        Console.ReadLine()
    End Sub
    

    End Module

    I was (eventually) able to find documentation of this behavior in the VB .Net docs (ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vbalr/html/ebce3120-95c3-42b1-b70b-fa7da40c75e2.htm), but I could not find any explicit mention of it in any of the books I have or in any other on-line resources, even though all of the latter do only show For Each being used in ways that work. I am hoping that this note may help others to avoid the head-scratching that I went through before if figured out why my code was not working.

    modified on Monday, September 13, 2010 11:27 AM

    L 1 Reply Last reply
    0
    • P Peter R Fletcher

      It is possible that I am the only person in the world who didn't already know this :) , but there is one very important difference between the behavior of a For Each loop that iterates through an array (or an array of Structures, which was the context that has just bitten me!) and the corresponding For Next loop. When you use For Each, the item that is passed into the loop is effectively passed by Value. Any assignment that you make to it within the loop is lost as soon as you leave it. If you put exactly the same code within an equivalent For Next loop, the assignments "stick". The following very short Console Application demonstrates this:

      Module Module1
      Dim intArray(10) As Integer
      Sub Main()
      Dim intI As Integer
      For intI = 0 To 10 ' fill the array
      intArray(intI) = intI
      Next

          For Each item In intArray ' show that the data has been stored
              Console.Write(item.ToString & ", ")
          Next
          Console.Write(vbCrLf)
      
          intI = 10
          For Each item In intArray ' fill it again in reverse order
              item = intI
              Console.Write(item.ToString & ", ") ' show that contents of the "local" copy of the array element has been changed
              intI -= 1
          Next
          Console.Write(vbCrLf)
      
          For Each item In intArray
              Console.Write(item.ToString & ", ") ' but the contents of the actual array have not!
          Next
          Console.Write(vbCrLf)
          Console.ReadLine()
      End Sub
      

      End Module

      I was (eventually) able to find documentation of this behavior in the VB .Net docs (ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vbalr/html/ebce3120-95c3-42b1-b70b-fa7da40c75e2.htm), but I could not find any explicit mention of it in any of the books I have or in any other on-line resources, even though all of the latter do only show For Each being used in ways that work. I am hoping that this note may help others to avoid the head-scratching that I went through before if figured out why my code was not working.

      modified on Monday, September 13, 2010 11:27 AM

      L Offline
      L Offline
      Luc Pattyn
      wrote on last edited by
      #2

      I'm not sure I ever used a For Each in VB.NET; however the same is true in C#, and there the compiler would not even let you modify the variable, so I was surprised to see VB.NET lets you do item = intI at all. BTW: I trust the snippet is just an example of the phenomenon. If you want to turn an array upside down, you could as well use Array.Reverse() :)

      Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

      Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

      P 1 Reply Last reply
      0
      • L Luc Pattyn

        I'm not sure I ever used a For Each in VB.NET; however the same is true in C#, and there the compiler would not even let you modify the variable, so I was surprised to see VB.NET lets you do item = intI at all. BTW: I trust the snippet is just an example of the phenomenon. If you want to turn an array upside down, you could as well use Array.Reverse() :)

        Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

        Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

        P Offline
        P Offline
        Peter R Fletcher
        wrote on last edited by
        #3

        Yes, it was just an intentionally trivial example. I wouldn't have minded (much :) ), and I certainly wouldn't have wasted the time I ended up spending on the issue, if the compiler had flagged the assignment as an error. I don't see why this restriction should be imposed (presumably) by the compiler writers. For an array of substantial structures, it would seem to add complexity and cycles (copying everything instead of passing pointers) to no good purpose.

        L 1 Reply Last reply
        0
        • P Peter R Fletcher

          Yes, it was just an intentionally trivial example. I wouldn't have minded (much :) ), and I certainly wouldn't have wasted the time I ended up spending on the issue, if the compiler had flagged the assignment as an error. I don't see why this restriction should be imposed (presumably) by the compiler writers. For an array of substantial structures, it would seem to add complexity and cycles (copying everything instead of passing pointers) to no good purpose.

          L Offline
          L Offline
          Luc Pattyn
          wrote on last edited by
          #4

          There is no willful copying. If it were an array of Forms, you'd get a reference to each Form in turn; as it is an array of ints, you get each int in turn (and not a pointer to an array element). It would be exactly the same if the body of your foreach loop were a separate method, value types get passed by value, reference types get also passed by value, i.e. you get their reference (not a pointer to their reference). That works fine, except for large value types (but then you are always given the advice not to come up with big structs). I'm not absolutely sure why the languages were designed like that; I realize the collection (the array in your example) could be virtual and could be read-only; all foreach requires is something that is enumarable, so arays, lists, an anything implementing IEnumerable should do. Elements of some of those would not be modifiable by sending a new value through some pointer. :)

          Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

          Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

          P 1 Reply Last reply
          0
          • L Luc Pattyn

            There is no willful copying. If it were an array of Forms, you'd get a reference to each Form in turn; as it is an array of ints, you get each int in turn (and not a pointer to an array element). It would be exactly the same if the body of your foreach loop were a separate method, value types get passed by value, reference types get also passed by value, i.e. you get their reference (not a pointer to their reference). That works fine, except for large value types (but then you are always given the advice not to come up with big structs). I'm not absolutely sure why the languages were designed like that; I realize the collection (the array in your example) could be virtual and could be read-only; all foreach requires is something that is enumarable, so arays, lists, an anything implementing IEnumerable should do. Elements of some of those would not be modifiable by sending a new value through some pointer. :)

            Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

            Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

            P Offline
            P Offline
            Peter R Fletcher
            wrote on last edited by
            #5

            If I were calling a Sub, I would find it natural (and well-documented!) that I was getting a Value rather than a Reference in the Sub (and I could explicitly change that, if I wanted to). I think that is the in-line nature of and the almost exact parallel between the For Each and For Next syntax and operation, except for this one important detail, that makes the former's behavior feel wrong. For a simple, one-dimensional, array of any simple value or object type, you are right - the "pass by value" approach will result in no increase in code size or complexity - but even for an array of relatively small structs, a lot of copying will be necessary to (in effect) pass each struct item by value.

            L 1 Reply Last reply
            0
            • P Peter R Fletcher

              If I were calling a Sub, I would find it natural (and well-documented!) that I was getting a Value rather than a Reference in the Sub (and I could explicitly change that, if I wanted to). I think that is the in-line nature of and the almost exact parallel between the For Each and For Next syntax and operation, except for this one important detail, that makes the former's behavior feel wrong. For a simple, one-dimensional, array of any simple value or object type, you are right - the "pass by value" approach will result in no increase in code size or complexity - but even for an array of relatively small structs, a lot of copying will be necessary to (in effect) pass each struct item by value.

              L Offline
              L Offline
              Luc Pattyn
              wrote on last edited by
              #6

              Peter R. Fletcher wrote:

              a lot of copying will be necessary to (in effect) pass each struct item by value.

              And that makes me think it is the reason why C# does not accept any changes to it: they probably don't copy but use the real stuff, which is fine if they disallow any code to change it. :)

              Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

              Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

              L 1 Reply Last reply
              0
              • L Luc Pattyn

                Peter R. Fletcher wrote:

                a lot of copying will be necessary to (in effect) pass each struct item by value.

                And that makes me think it is the reason why C# does not accept any changes to it: they probably don't copy but use the real stuff, which is fine if they disallow any code to change it. :)

                Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

                Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

                L Offline
                L Offline
                Lost User
                wrote on last edited by
                #7

                Actually, they do make a copy, even in the optimized code. In fact, two copies are made, at least in the code I tested. See here:

                shl rcx,4
                mov eax,dword ptr [rbx+rcx+10h]
                mov dword ptr [rsp+20h],eax
                mov eax,dword ptr [rbx+rcx+14h]
                mov dword ptr [rsp+24h],eax
                mov eax,dword ptr [rbx+rcx+18h]
                mov dword ptr [rsp+28h],eax
                mov eax,dword ptr [rbx+rcx+1Ch]
                mov dword ptr [rsp+2Ch],eax
                lea rcx,[rsp+20h]
                mov rax,qword ptr [rcx]
                mov qword ptr [rsp+40h],rax
                mov rax,qword ptr [rcx+8]
                mov qword ptr [rsp+48h],rax
                lea rax,[rsp+40h]
                movss xmm5,dword ptr [rax]
                ; the second copy is read back

                The C# code:

                static void Normalize(Float3[] array)
                {
                Float3 x = new Float3();
                foreach (Float3 f in array)
                {
                x = f.Normalize();
                }
                if (array.Length != 0)
                throw new Exception(x.ToString()); //to make it easier to attach the debugger
                }

                edit: made a slight mistake here, whatever. If you change "the foreach value" (indirectly, of course), the second copy is affected but not the first. The first copy is never used again. For primitive types, the code is actually sane.

                modified on Monday, September 13, 2010 5:39 PM

                L 1 Reply Last reply
                0
                • L Lost User

                  Actually, they do make a copy, even in the optimized code. In fact, two copies are made, at least in the code I tested. See here:

                  shl rcx,4
                  mov eax,dword ptr [rbx+rcx+10h]
                  mov dword ptr [rsp+20h],eax
                  mov eax,dword ptr [rbx+rcx+14h]
                  mov dword ptr [rsp+24h],eax
                  mov eax,dword ptr [rbx+rcx+18h]
                  mov dword ptr [rsp+28h],eax
                  mov eax,dword ptr [rbx+rcx+1Ch]
                  mov dword ptr [rsp+2Ch],eax
                  lea rcx,[rsp+20h]
                  mov rax,qword ptr [rcx]
                  mov qword ptr [rsp+40h],rax
                  mov rax,qword ptr [rcx+8]
                  mov qword ptr [rsp+48h],rax
                  lea rax,[rsp+40h]
                  movss xmm5,dword ptr [rax]
                  ; the second copy is read back

                  The C# code:

                  static void Normalize(Float3[] array)
                  {
                  Float3 x = new Float3();
                  foreach (Float3 f in array)
                  {
                  x = f.Normalize();
                  }
                  if (array.Length != 0)
                  throw new Exception(x.ToString()); //to make it easier to attach the debugger
                  }

                  edit: made a slight mistake here, whatever. If you change "the foreach value" (indirectly, of course), the second copy is affected but not the first. The first copy is never used again. For primitive types, the code is actually sane.

                  modified on Monday, September 13, 2010 5:39 PM

                  L Offline
                  L Offline
                  Luc Pattyn
                  wrote on last edited by
                  #8

                  You're sure that is a release situation? doesn't look very good then. :)

                  Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

                  Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

                  L 1 Reply Last reply
                  0
                  • L Luc Pattyn

                    You're sure that is a release situation? doesn't look very good then. :)

                    Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

                    Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

                    L Offline
                    L Offline
                    Lost User
                    wrote on last edited by
                    #9

                    Release without debugger attached, then attach it later, I know :) And yes, it's quite horrible.. edit: for primitive types they just read the value from the array and use it

                    L 1 Reply Last reply
                    0
                    • L Lost User

                      Release without debugger attached, then attach it later, I know :) And yes, it's quite horrible.. edit: for primitive types they just read the value from the array and use it

                      L Offline
                      L Offline
                      Luc Pattyn
                      wrote on last edited by
                      #10

                      harold aptroot wrote:

                      for primitive types they just read the value from the array and use it

                      OK, one more reason to tip the class/struct balance in favor of struct then. BTW: great info! :)

                      Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

                      Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

                      L 1 Reply Last reply
                      0
                      • L Luc Pattyn

                        harold aptroot wrote:

                        for primitive types they just read the value from the array and use it

                        OK, one more reason to tip the class/struct balance in favor of struct then. BTW: great info! :)

                        Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum

                        Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.

                        L Offline
                        L Offline
                        Lost User
                        wrote on last edited by
                        #11

                        Thanks :) By the way, they don't disallow all code to change "the foreach value". For example, if you have an array of Rectangles you can still call Inflate on them. Code like that has the effect of changing the second copy, and that's the same as changing "the foreach value". This snippet shows that behaviour:

                        Rectangle[] aBunchOfRectangles = new Rectangle[10];
                        foreach (Rectangle rect in aBunchOfRectangles)
                        {
                        Console.WriteLine(rect);
                        rect.Inflate(1, 1);
                        Console.WriteLine(rect);
                        }

                        That would explain why the struct is copied from the array to the stack once (though it is also done when the method doesn't change the instance), the other copy is still a mystery to me..

                        D 1 Reply Last reply
                        0
                        • L Lost User

                          Thanks :) By the way, they don't disallow all code to change "the foreach value". For example, if you have an array of Rectangles you can still call Inflate on them. Code like that has the effect of changing the second copy, and that's the same as changing "the foreach value". This snippet shows that behaviour:

                          Rectangle[] aBunchOfRectangles = new Rectangle[10];
                          foreach (Rectangle rect in aBunchOfRectangles)
                          {
                          Console.WriteLine(rect);
                          rect.Inflate(1, 1);
                          Console.WriteLine(rect);
                          }

                          That would explain why the struct is copied from the array to the stack once (though it is also done when the method doesn't change the instance), the other copy is still a mystery to me..

                          D Offline
                          D Offline
                          DaveyM69
                          wrote on last edited by
                          #12

                          harold aptroot wrote:

                          if you have an array of Rectangles you can still call Inflate on them

                          Yeah, Rectangle is a mutable struct! I assume they didn't make it immutable for some backward compatibility reason. I had never considered the implications of this (apart from the normal) until seeing your previous post - interesting :thumbsup:

                          Dave
                          Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum. Astonish us. Be exceptional. (Pete O'Hanlon)
                          BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)

                          1 Reply Last reply
                          0
                          Reply
                          • Reply as topic
                          Log in to reply
                          • Oldest to Newest
                          • Newest to Oldest
                          • Most Votes


                          • Login

                          • Don't have an account? Register

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • World
                          • Users
                          • Groups