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.
  • 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