Having trouble wrapping my head around the concept of modifying these two different lists. So when using a regular for loop to iterate over each one, we can directly modify the list of integers, but cannot modify a specific item in the list of ValueTuples.
Consider this example
var test = new List<int>() { 1, 2, 3, 4 };
for (int i = 0; i < test.Count; i++)
{
test[i] += 1;
}
var test2 = new List<(int, int)>() { (1, 2), (2, 3), (3, 4) };
for(int i = 0; i < test2.Count; i++)
{
test2[i].Item1 += 1;
}
So in this, we can successfully add 1 to each integer value in the first list. However, with the second list we actually get a compiler error of CS1612 which states "Cannot modify the return value of 'List<(int, int)>.this[int]' because it is not a variable."
I read into the error on the official docs, and it makes sense that in the second example we are returning a copy of the ValueTuple, therefore we are not modifying the actual one in the list. But then why does the integer example work?
Feel like I might just be overcomplicating this, but wanted to ask here and see where I could be going wrong.
To understand why test[i] += 1
compiles but test2[i].Item1 += 1
doesn't, let's examine how the C# compiler transforms them into simpler statements.
test[i] += 1
is transformed as follows:
var x = test.get_Item(i); // Make a copy of test[i].
var y = x + 1;
test.set_Item(i, y); // Replace test[i] with y.
// Or more concisely:
test.set_Item(i, test.get_Item(i) + 1);
The get_Item
and set_Item
methods refer to the get
and set
accessors of List<T>
's indexer. I'm using get_Item
and set_Item
here instead of test[i]
to clarify whether test[i]
refers to a get or a set.
test2[i].Item1 += 1
is transformed as follows:
var tuple = test2.get_Item(i); // Make a copy of test2[i].
var x = tuple.Item1;
var y = x + 1;
tuple.Item1 = y; // Mutate the copy. NOT ALLOWED (CS1612)
// Or more concisely:
var tuple = test2.get_Item(i);
tuple.Item1 = tuple.Item1 + 1;
Notice two important points:
set_Item
. With ValueTuple, there isn't.Item1
occurs on a temporary copy of test2[i]
, so it ultimately has no observable effect. This is why the compiler reports error CS1612: to prevent you from writing code that doesn't do what you think it does.You can get your code to compile in a couple ways:
Use an array instead of List
. If test2
were an array, then test2[i]
would be a mutable reference to the element at index i
, not a copy. Array indexers are special in this regard.
Replace the entire element test2[i]
:
var tuple = test2[i]; // test2.get_Item(i)
tuple.Item1 += 1;
test2[i] = tuple; // test2.set_Item(i, tuple)
The last statement invokes set_Item
because the left-hand side of the assignment is an indexer expression and nothing else. (That's just the way the C# language works.)