You can't assign to an array element within a For Each loop! [modified]
-
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 useArray.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.
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.
-
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.
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.
-
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.
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.
-
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.
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.
-
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.
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 backThe 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
-
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 backThe 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
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.
-
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.
-
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
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.
-
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.
Thanks :) By the way, they don't disallow all code to change "the foreach value". For example, if you have an array of
Rectangle
s you can still callInflate
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..
-
Thanks :) By the way, they don't disallow all code to change "the foreach value". For example, if you have an array of
Rectangle
s you can still callInflate
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..
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)