some weird struct behavior

J

J.Marsch

Ok, I think that what I'm seeing is due to some implicit boxing/unboxing
going on, but I'd like to understand this a little better.

I have a collection of structs. If I try to iterate the collection of
structs with a foreach() loop, and in the loop I try to change the value of
one of the fields in the struct, I get a compile time error "The left-hand
side of an assignment must be a variable, property, or indexer". (a very
contrived code example at the end of the email)

I _think_ that I understand what the issue is, but I'd like some validation.

I think that the problem is that the collection that I am using uses type
object as its inherent storage type, so when you add a struct to the
collection, it is being boxed implicitly. Then, when you retrieve the
struct from the collection in a foreach, that struct is being implicitly
unboxed. If you try to change a member, the compiler realizes that the
value will never make it back to the copy of the struct in the collection
(because what you have is a copy), so you get a compile time error.

Is that pretty close?

Here's a little demo code. I left out some things like the enumerator and
the definition of the Add method, just to try to keep it concise:

public class Class1 :
System.Collections.Specialized.NameObjectCollectionBase
{

public void Load()
{
for(int count = 0; count < 11; count++)
{
TestStruct test = new TestStruct();
test.Processed = false;
test.Property2 = true;
this.Add(count.ToString(), test);
}
}

public void Process()
{
foreach(TestStruct test in this)
test.Processed = true; // causes compile error
}
}

public struct TestStruct
{
public bool Processed;
public bool Property2;
}
 
C

Chris R. Timmons

Ok, I think that what I'm seeing is due to some implicit
boxing/unboxing going on, but I'd like to understand this a
little better.

I have a collection of structs. If I try to iterate the
collection of structs with a foreach() loop, and in the loop I
try to change the value of one of the fields in the struct, I
get a compile time error "The left-hand side of an assignment
must be a variable, property, or indexer". (a very contrived
code example at the end of the email)

I _think_ that I understand what the issue is, but I'd like some
validation.

I think that the problem is that the collection that I am using
uses type object as its inherent storage type, so when you add a
struct to the collection, it is being boxed implicitly. Then,
when you retrieve the struct from the collection in a foreach,
that struct is being implicitly unboxed. If you try to change a
member, the compiler realizes that the value will never make it
back to the copy of the struct in the collection (because what
you have is a copy), so you get a compile time error.

Is that pretty close?

When retrieving an object from the underlying collection, you need to
explicitly cast the object to the appropriate type:

public void Process()
{
foreach (Object o in this)
{
TestStruct test = (TestStruct) o;
test.Processed = true;
}
}


Hope this helps.

Chris.
 
I

Ignacio Machin

Hi,

What are you returning in the iterator?

I think that it there where the error is. You can change any member of the
collection if you want, the fact that it's a copy has nothing to do, if you
think about it you realize that you may want do some process on it and it
can be changed during this process.
It's you ( not the compiler_ who have to realize that the modification you
do does not goes back to the element in the collection.


Cheers,
 
1

100

Hi J.Marsch,
All your understanding about the implicit boxing/unboxing are completely
correct.
The answer of the question why you cannot change the foreach loop's variable
can be found in the c# spec.

the following is a quote form MSDN about the foreach loop:
"...identifier - The iteration variable that represents the collection
element. If the iteration variable is a value type, it is effectively a
read-only variable that cannot be modified..."

As long structs are value type changing a field of the value type is
considered as changing the variable itself.
If you try this with reference type of course it won't fail.
Anyway you can call a struct's method, which in turn can change the value.
Compiler won't complain. However because of boxing/unboxig going on it
doesn't make a lot of sense.

HTH
B\rgds
100
 
1

100

When retrieving an object from the underlying collection, you need to
explicitly cast the object to the appropriate type:

public void Process()
{
foreach (Object o in this)
{
TestStruct test = (TestStruct) o;
test.Processed = true;
}
}

It is not necessary. You always can write the foreach loop specifying the
exact type of the elements in the collection.
foreach (TestStruct test in this)
{
test.Processed = true;
}

This is exactly the code that can be found in the original post.
Your code won't cause the compiler-time error, but because of the boxing
you won't change the value in the collection.
 
J

J.Marsch

Actually in 2003, this code does cause a compile time error, the same error
message that was in my original post. That's what initially attracted my
attention to the issue. Then I broke out the code that I posted into a test
project and tried compiling. Here's the complete .cs file. Load it up and
try to build.


using System;

namespace foreachbug
{
/// <summary>
/// Summary description for Class1.
/// </summary>
public class Class1 :
System.Collections.Specialized.NameObjectCollectionBase
{
public void Add(string id, TestStruct testStruct)
{
this.BaseAdd(id, testStruct);
}

public void Load()
{
for(int count = 0; count < 11; count++)
{
TestStruct test = new TestStruct();
test.Processed = false;
test.Property2 = true;
this.Add(count.ToString(), test);
}
}

public void Process()
{
foreach(TestStruct test in this)
test.Processed = true; // causes compile error
}
}

public struct TestStruct
{
public bool Processed;
public bool Property2;
}
}
 
1

100

Hi J.Marsch,

Your code will cause compiler error because of what I explained in my
previous post.
What I said is that the compiler will compile Chris' code
 
J

J.Marsch

Ah, gotcha.

Regards

-- Jeremy


100 said:
Hi J.Marsch,

Your code will cause compiler error because of what I explained in my
previous post.
What I said is that the compiler will compile Chris' code
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top