assignment to field in foreach

  • Thread starter Thread starter Brad Williams
  • Start date Start date
B

Brad Williams

I'm trying to get clearer on limitations of assignment/modifications within
a foreach. Why does the following gives a compilation error if MyType is a
struct, but it does not if MyType is a class?

public struct MyType // change to class, and it compiles clean
{
public int f;
}

public void foo()
{
MyType[] a = new MyType[3];

for (int n = 0; n < 3; n++)
a[n] = new MyType();

foreach (MyType c in a)
{
c.f = 77; // compilation error: The left-hand side of assignment
must be a variable, property or indexer
this.WriteLine(c.f.ToString());
}
}
 
Brad,

I can't be sure without more investigation, but if a is an array of
reference types, what exactly is the variable c? It's a reference to an
instance of that type, and, duh, a reference to an instance that's in the
array you're iterating throught. So there shouldn't be any problem in
accessing a member of the instance by means of the reference. So what is c
if a is an array of value types? It's almost certainly a COPY of an instance
in the array. Even if this compiled, it probably wouldn't work, i.e., you'd
be assigning 77 to the f field of a copy, which is probably not what you
intended.

Tony
 
You could also create an array of System.Object, then (implicitly) box your
value types when you insert them, and explicitly cast them inside the
loop...no, that wouldn't work either, because you'd be unboxing a value into
a copy.
 
...
I'm trying to get clearer on limitations of
assignment/modifications within a foreach.
Why does the following gives a compilation
error if MyType is a struct, but it does not
if MyType is a class?

Because a struct is a value type, and those are "read-only" in a
foreach-loop (see below).
public struct MyType
{
public int f;
}

public void foo()
{
MyType[] a = new MyType[3];

for (int n = 0; n < 3; n++)
a[n] = new MyType();

foreach (MyType c in a)
{
c.f = 77; // compilation error:
this.WriteLine(c.f.ToString());
}
}

From the documentation:

foreach (type identifier in expression) statement

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


// Bjorn A
 
Good points all.

In either case, whether the type is struct or class, the foreach
element-instance variable is "read-only" according to the language spec, so
it comes down to how readonly affects struct variables versus class
variables. I guess it makes sense that a read-only struct variable *is* the
whole struct, thus all of its fields are read-only too. Whereas a read-only
class object's fields are not part of the class *variable*, so they don't
fall under the read-only-ness of the variable. As confirmation, readonly
acts this way in another test:

public class foo
{
public int x;
}

public struct bar
{
public int y;
}

public class quux
{
public readonly foo f = new foo();
public readonly bar b = new bar();

public void Test()
{
f.x = 1; // compiles
b.y = 2; // doesn't compile
}
}

Brad Williams
 
This is not allowed in C#, You cant modify with in a for-each loop.

From C# Lang specification (8.8.4) :

The foreach statement enumerates the elements of a collection,
executing an embedded statement for each element of the collection.

foreach-statement:

foreach ( type identifier in expression )
embedded-statement
The type and identifier of a foreach statement declare the iteration
variable of the statement. The iteration variable corresponds to a
read-only local variable with a scope that extends over the embedded
statement. During execution of a foreach statement, the iteration
variable represents the collection element for which an iteration is
currently being performed. A compile-time error occurs if the embedded
statement attempts to modify the iteration variable (via assignment or
the ++ and operators) or pass the iteration variable as a ref or
out parameter.
 
Back
Top