c# - ¿Por qué el valor del elemento de lista tipo tupla no se puede modificar?

CorePress2024-01-25  9

En C# 8.0, puedo modificar el valor dentro de una tupla directamente accediendo al nombre del campo:

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

Y puedo modificar la propiedad del elemento de la lista de la siguiente manera:

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]);

Pero ¿por qué no puedo modificar el valor del elemento tipo tupla de esta manera?

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

6

Debido a que las tuplas son estructuras y obtienes una copia del valor original, la modificación sería en la copia, no en el valor original. El compilador te impide hacer algo que no funcionaría en primer lugar

- Pinkfloydx33

28/03/2021 a las 10:13



------------------------------------

Una tupla (ValueTuple) es una estructura. En lugar de devolver una referencia al valor como es el caso del ejemplo de Estudiante, en realidad recibirás una copia de la tupla.

Los cambios realizados en esa copia no se reflejarían en la lista y se descartarían. El compilador es lo suficientemente inteligente como para reconocer esto y le impide hacerlo.

Si se compilara, sería algo similar a lo siguiente:

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

Las estructuras mutables pueden ser peligrosas si no las usas correctamente. El compilador simplemente está haciendo su trabajo.

Puedes solucionar este problema con lo siguiente:

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

Pruébelo en línea

Si usa una matriz (string, int)[] en lugar de una Lista<(string, int)>, esto no es un problema debido a la forma en que funciona el acceso a los elementos de la matriz:

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

Pruébelo en línea

Este comportamiento no es exclusivo de List o de su tipo de tupla. Experimentarás este problema con cualquier colección donde el elemento sea un tipo de valor (a menos, por supuesto, que ofrezcan un descriptor de acceso de elemento de referencia).

Tenga en cuenta que existen problemas similares cuando se tiene un campo de solo lectura de un tipo de valor mutable que muta mediante llamadas a métodos. Esto puede ser mucho más insidioso ya que no se emite ningún error o advertencia:

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();

Pruébelo en línea

ValueTuple es una gran adición al marco y al lenguaje. Pero hay una razón por la que escucharás a menudo que las estructuras [mutables] son ​​malas. En la mayoría de los casos no deberías alcanzar estas restricciones.. Si caes con frecuencia en este patrón, te sugiero que pases a un registro, que es un tipo de referencia (por lo que no sufre estos problemas) y se puede reducir a una sintaxis similar a una tupla.



------------------------------------

Los tipos de valores mutables son malos, es difícil ver por qué esto imprime "Tom" no "Jack":

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

La razón es que siempre creas una copia. Debido a que no es obvio, debes evitar los tipos de valores mutables. Para evitar que la gente caiga en esa trampa, el compilador simplemente permite modificar el objeto directamente a través de propiedades (como arriba). Pero si intentas hacerlo a través de míAl realizar esta llamada, aparece el error del compilador "No se puede modificar el valor de retorno de... porque no es una variable".

Entonces esto no está permitido:

list[0].name = "Jack";

Crearía una nueva copia de ValueTuple, asigna un valor pero no lo usa ni lo almacena en ningún lugar.

Esto se compila porque lo asignas a una nueva variable y la modificas mediante la propiedad:

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

Así que se compila pero te da nuevamente un resultado sorprendente:

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

Lea más sobre esto aquí: No definir tipos de valores mutables

2

Pasé un rato escribiendo miResponde en el móvil y cuando terminé, ya habías publicado el tuyo. Mantuve mi respuesta, pero tengo un voto positivo

- Pinkfloydx33

28 de marzo de 2021 a las 10:54

1

@pinkfloydx33: gracias, OP aceptó la respuesta correcta

- Tim Schmelter

28 de marzo de 2021 a las 10:56

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare