Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the tuple-type list element's value cannot be modified?

Tags:

c#

In C# 8.0, I can modify the value inside a tuple directly by accessing the field name:

(string name, int score) student = ("Tom", 100);
student.name = "Jack";
Console.WriteLine(student);

And I can modify the list element's property as follow:

var list = new List<Student>();  // assume I have a Student class which has a Name property
list.Add(new Student { Name = "Tom" });
list[0].Name = "Jack";
Console.WriteLine(list[0]);

But why can't I modify the tuple-type element's value like this?

var list = new List<(string name, int score)>();
list.Add(("Tom", 100));
list[0].name = "Jack"; // Error!
Console.WriteLine(list[0]);
like image 363
Obsidian Avatar asked Mar 28 '21 09:03

Obsidian


People also ask

Why we Cannot change tuple in Python?

tuples , like strings , are immutable objects in that their values cannot be changed once they have been created. You usually use tuples when you want to store a list of values that you wont be editing, maybe they are constants. If you will be editing, resizing or appending elements to the tuple , use lists instead.

Can tuple be modified?

Once a tuple is created, you cannot change its values. Tuples are unchangeable, or immutable as it also is called.

Why do we need tuple?

Tuples are used to store multiple items in a single variable. Tuple is one of 4 built-in data types in Python used to store collections of data, the other 3 are List, Set, and Dictionary, all with different qualities and usage. A tuple is a collection which is ordered and unchangeable.


Video Answer


2 Answers

A tuple (ValueTuple) is a struct. Rather than returning a reference to the value as is the case with your Student example, you would actually recieve a copy of the tuple.

Changes to that copy wouldn't be reflected in the list and would be discarded. The compiler is smart enough to recognize this and stops you from doing it.

If it did compile, it would be to something similar to the following:

var list = new List<(string name, int score)>(); list.Add(("Tom", 100));
var copy = list[0];
copy.name = "Jack"; 
Console.WriteLine(copy.name);    // Jack
Console.WriteLine(list[0].name); // still Tom

Mutable structs can be dangerous if you don't use them properly. The compiler is simply doing its job.

You can work around this with the following:

var list = new List<(string name, int score)>(); list.Add(("Tom", 100));
var copy = list[0];
copy.name = "Jack"; 
list[0] = copy;                  // put it back
Console.WriteLine(copy.name);    // Jack
Console.WriteLine(list[0].name); // Jack

Try It Online

If you use an array (string, int)[] instead of a List<(string, int)>, this isn't a problem due to the way array element access works:

var arr = new (string name, int score) [] { ( "Tom", 10 ) };
arr[0].name = "Jack";
Console.WriteLine(arr[0].name); // Jack

Try It Online

This behavior is not unique to List or your tuple type. You'll experience this issue with any collection where the element is a Value Type (unless of course they offer a ref element accessor).

Note that there are similar issues when having a readonly field of a mutable value type that mutates via method calls. This can be much more insidious as no error or warning is emitted:

struct MutableStruct { 
   public int Val; 
   public void Mutate(int newVal) {
     Val = newVal;
   } 
} 
class Test {
    private readonly MutableStruct msReadonly;
    private MutableStruct msNormal;
    public Test() {
      msNormal = msReadonly = new MutableStruct(){ Val=5 };
    } 
    public void MutateReadonly() {
         Console.WriteLine(msReadonly.Val); // 5
         msReadonly.Mutate(66);             // defensive copy! 
         Console.WriteLine(msReadonly.Val); // still 5!!! 
    } 
    public void MutateNormal() {
         Console.WriteLine(msNormal.Val);   // 5
         msNormal.Mutate(66);
         Console.WriteLine(msNormal.Val);   // 66
    } 
}
new Test().MutateReadonly();
new Test().MutateNormal();

Try It Online

ValueTuple is a great addition to the framework and language. But there's a reason you'll often hear that [Mutable] structs are evil. In the majority of cases you shouldn't hit these restrictions. If you find yourself falling into this pattern a lot, I suggest moving over to a record, which is a reference type (thus not suffering these issues) and can be reduced to a tuple-like syntax.

like image 91
pinkfloydx33 Avatar answered Oct 07 '22 09:10

pinkfloydx33


Mutable value types are evil, it's hard to see why this prints "Tom" not "Jack":

(string name, int score) student = ("Tom", 100);
(string name, int score) student2 = student;
student.name = "Jack";
Console.WriteLine(student2);

The reason is that you always create a copy. Because it's not obvious you should avoid mutable value types. To avoid that people will fall into that trap the compiler just allows to modify the object directly via properties(like above). But if you try to do it via a method call you get a compiler error "Cannot modify the return value of ... because it is not a variable".

So this is not allowed:

list[0].name = "Jack";

It would create a new copy of the ValueTuple, assigns a value but doesn't use or store it anywhere.

This compiles because you assign it to a new variable and modify it via property:

(string name, int score) x = list[0];
x.name = "Jack"; // Compiles 

So it compiles but gives you again a suprising result:

Console.WriteLine(list[0]);  // Tom

Read more about it here: Do Not Define Mutable Value Types

like image 29
Tim Schmelter Avatar answered Oct 07 '22 08:10

Tim Schmelter