Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Readonly local variable cannot be used as an assignment target

Tags:

c#

If I have:

var myObjects = new ConcurrentBag<object>();

And try to remove objects via:

foreach (var myObject in myObjects.ToArray())
{
    myObjects.TryTake(out myObject);
}

The compiler complains with: "Readonly local variable cannot be used as an assignment target"

Yet, if I add a local reference within the foreach it compiles:

foreach (var myObject in myObjects.ToArray())
{
    var localReference = myObject;
    myObjects.TryTake(out localReference);
}

What exactly is going on here?

like image 352
Eric Dahlvang Avatar asked Jun 28 '12 19:06

Eric Dahlvang


2 Answers

The iteration variable in a foreach (i.e. myObject) cannot be assigned a new value inside the foreach. It is not allowed.

In the first scenario, the out tries to do this. In the second scenario, you *never try to reassign to myObject, so it is fine.

To quote from the ECMA specification, 15.8.4, emphasis mine:

  1. The type and identifier of a foreach statement declare the iteration variable of the statement.

  2. The iteration variable corresponds to a read-only local variable with a scope that extends over the embedded statement.

  3. During execution of a foreach statement, the iteration variable represents the collection element for which an iteration is currently being performed.

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

like image 94
Marc Gravell Avatar answered Nov 03 '22 23:11

Marc Gravell


If you want to assign the retrieved object to the array, use a for-statement instead, since you cannot assign a value to the loop variable

object[] arr = myObjects.ToArray();
for (int i = 0; i < arr.Length; i++) {
    myObjects.TryTake(out arr[i]); 
}

Now the array contains the retrieved objects or null where no object could be taken.


UPDATE

After having read about ConcurrentBag<T>, I think I am beginning to understand what you have in mind. My first solution above runs into race conditions in multithreaded scenarios (the very and only reson for using a ConcurrentBag<T>). Try this instead

var removedObjects = new List<object>();
object obj;
while (myObjects.TryTake(out obj)) {
    removedObjects.Add(obj);
}

Note: Assigning the objects of the bag to an array and then looping through it is not a good idea, since other threads could have added or removed objects meanwhile. Therefore you must use this direct approach.


If you only want to remove the objects without retrieving them at the same time:

object obj;
while (myObjects.TryTake(out obj)) {
}
like image 6
Olivier Jacot-Descombes Avatar answered Nov 03 '22 21:11

Olivier Jacot-Descombes